@usevalt/cli 0.7.4 → 0.9.0
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/dist/index.js +799 -78
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1035,13 +1035,340 @@ proxyCommand.command("stop").description("Stop the MCP proxy").action(() => {
|
|
|
1035
1035
|
|
|
1036
1036
|
// src/commands/hook.ts
|
|
1037
1037
|
import { Command as Command13 } from "commander";
|
|
1038
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as
|
|
1039
|
-
import { createReadStream } from "fs";
|
|
1040
|
-
import { createInterface } from "readline";
|
|
1038
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync4, readdirSync, statSync, openSync, readSync, closeSync } from "fs";
|
|
1039
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
1040
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1041
1041
|
import { execSync } from "child_process";
|
|
1042
1042
|
import { basename, join } from "path";
|
|
1043
1043
|
import { createHash } from "crypto";
|
|
1044
1044
|
import os2 from "os";
|
|
1045
|
+
|
|
1046
|
+
// src/lib/content.ts
|
|
1047
|
+
import { basename as pathBasename } from "path";
|
|
1048
|
+
var MAX_CONTENT_LENGTH = 2048;
|
|
1049
|
+
function truncateContent(s, max = MAX_CONTENT_LENGTH) {
|
|
1050
|
+
if (s.length <= max) return s;
|
|
1051
|
+
return s.slice(0, max - 3) + "...";
|
|
1052
|
+
}
|
|
1053
|
+
function fileBasename(p) {
|
|
1054
|
+
if (!p) return "unknown";
|
|
1055
|
+
return pathBasename(p);
|
|
1056
|
+
}
|
|
1057
|
+
var COMMAND_PATTERNS = [
|
|
1058
|
+
["test", /vitest|jest|pytest|go\s+test|cargo\s+test|npm\s+test|pnpm\s+test|mocha|ava/i],
|
|
1059
|
+
["typecheck", /tsc\s+--noEmit|mypy|pyright|pnpm\s+typecheck/i],
|
|
1060
|
+
["build", /tsc(?!\s+--noEmit)|webpack|vite\s+build|cargo\s+build|go\s+build|npm\s+run\s+build|pnpm\s+build|esbuild|tsup/i],
|
|
1061
|
+
["lint", /eslint|biome(?!\s+format)|ruff|clippy|golangci-lint|pylint|oxlint/i],
|
|
1062
|
+
["format", /prettier|black|gofmt|rustfmt|biome\s+format/i],
|
|
1063
|
+
["git", /^git\s/i],
|
|
1064
|
+
["deploy", /vercel|fly\s|docker\s+push|kubectl|railway/i],
|
|
1065
|
+
["install", /npm\s+install|pnpm\s+(add|install)|pip\s+install|cargo\s+add|yarn\s+add|bun\s+add/i],
|
|
1066
|
+
["dev", /npm\s+run\s+dev|pnpm\s+dev|cargo\s+run|go\s+run|bun\s+dev/i],
|
|
1067
|
+
["search", /grep|rg\s|find\s|fd\s|ag\s/i]
|
|
1068
|
+
];
|
|
1069
|
+
function classifyCommand(command) {
|
|
1070
|
+
for (const [category, pattern] of COMMAND_PATTERNS) {
|
|
1071
|
+
if (pattern.test(command)) return category;
|
|
1072
|
+
}
|
|
1073
|
+
return "other";
|
|
1074
|
+
}
|
|
1075
|
+
function summarizeToolInput(toolName, toolInput) {
|
|
1076
|
+
switch (toolName) {
|
|
1077
|
+
case "Read":
|
|
1078
|
+
return `Read ${fileBasename(toolInput["file_path"])}`;
|
|
1079
|
+
case "Write": {
|
|
1080
|
+
const content = toolInput["content"];
|
|
1081
|
+
const chars = content ? ` (${content.length} chars)` : "";
|
|
1082
|
+
return `Write ${fileBasename(toolInput["file_path"])}${chars}`;
|
|
1083
|
+
}
|
|
1084
|
+
case "Edit":
|
|
1085
|
+
return `Edit ${fileBasename(toolInput["file_path"])}`;
|
|
1086
|
+
case "NotebookEdit":
|
|
1087
|
+
return `Edit notebook ${fileBasename(toolInput["notebook_path"])}`;
|
|
1088
|
+
case "Bash": {
|
|
1089
|
+
const cmd = toolInput["command"] ?? "";
|
|
1090
|
+
const desc = toolInput["description"];
|
|
1091
|
+
const category = classifyCommand(cmd);
|
|
1092
|
+
const label = desc || truncateContent(cmd, 200);
|
|
1093
|
+
return `[${category}] ${label}`;
|
|
1094
|
+
}
|
|
1095
|
+
case "Glob":
|
|
1096
|
+
return `Search files: ${toolInput["pattern"] ?? "*"}`;
|
|
1097
|
+
case "Grep":
|
|
1098
|
+
return `Search content: /${toolInput["pattern"] ?? ""}/ in ${fileBasename(toolInput["path"])}`;
|
|
1099
|
+
case "WebFetch":
|
|
1100
|
+
return `Fetch: ${truncateContent(toolInput["url"] ?? "", 120)}`;
|
|
1101
|
+
case "WebSearch":
|
|
1102
|
+
return `Search web: ${truncateContent(toolInput["query"] ?? "", 120)}`;
|
|
1103
|
+
case "Task":
|
|
1104
|
+
return `Subagent: ${toolInput["description"] ?? "task"}`;
|
|
1105
|
+
case "TaskCreate":
|
|
1106
|
+
return `Create task: ${toolInput["subject"] ?? ""}`;
|
|
1107
|
+
case "TaskUpdate":
|
|
1108
|
+
return `Update task #${toolInput["taskId"] ?? "?"}: ${toolInput["status"] ?? ""}`;
|
|
1109
|
+
case "Skill":
|
|
1110
|
+
return `Skill: ${toolInput["skill"] ?? ""}`;
|
|
1111
|
+
case "AskUserQuestion": {
|
|
1112
|
+
const questions = toolInput["questions"];
|
|
1113
|
+
const q = questions?.[0]?.question ?? "";
|
|
1114
|
+
return `Ask user: ${truncateContent(q, 120)}`;
|
|
1115
|
+
}
|
|
1116
|
+
default:
|
|
1117
|
+
return `Tool: ${toolName}`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function summarizeToolResponse(toolName, _toolInput, toolResponse) {
|
|
1121
|
+
switch (toolName) {
|
|
1122
|
+
case "Bash": {
|
|
1123
|
+
const exitCode = toolResponse["exit_code"] ?? toolResponse["exitCode"] ?? 0;
|
|
1124
|
+
const stdout = toolResponse["stdout"] ?? toolResponse["output"] ?? "";
|
|
1125
|
+
const stderr = toolResponse["stderr"] ?? "";
|
|
1126
|
+
const output = Number(exitCode) !== 0 ? stderr || stdout : stdout;
|
|
1127
|
+
return `Result: exit=${exitCode} \u2014 ${lastLine(output)}`;
|
|
1128
|
+
}
|
|
1129
|
+
case "Read": {
|
|
1130
|
+
const content = toolResponse["content"] ?? "";
|
|
1131
|
+
const lines = content ? content.split("\n").length : 0;
|
|
1132
|
+
return `Result: Read \u2014 ${lines} lines`;
|
|
1133
|
+
}
|
|
1134
|
+
case "Write":
|
|
1135
|
+
return "Result: Write \u2014 success";
|
|
1136
|
+
case "Edit":
|
|
1137
|
+
return "Result: Edit \u2014 success";
|
|
1138
|
+
case "Glob": {
|
|
1139
|
+
const files = toolResponse["files"];
|
|
1140
|
+
const numFiles = toolResponse["numFiles"] ?? files?.length ?? 0;
|
|
1141
|
+
return `Result: Glob \u2014 ${numFiles} matches`;
|
|
1142
|
+
}
|
|
1143
|
+
case "Grep": {
|
|
1144
|
+
const numFiles = toolResponse["numFiles"] ?? toolResponse["numLines"] ?? 0;
|
|
1145
|
+
return `Result: Grep \u2014 ${numFiles} matches`;
|
|
1146
|
+
}
|
|
1147
|
+
case "WebFetch": {
|
|
1148
|
+
const content = toolResponse["content"] ?? "";
|
|
1149
|
+
return `Result: Fetch \u2014 ${content.length} chars`;
|
|
1150
|
+
}
|
|
1151
|
+
case "WebSearch": {
|
|
1152
|
+
const results = toolResponse["results"];
|
|
1153
|
+
const numResults = toolResponse["numResults"] ?? results?.length ?? 0;
|
|
1154
|
+
return `Result: Search \u2014 ${numResults} results`;
|
|
1155
|
+
}
|
|
1156
|
+
case "Task": {
|
|
1157
|
+
const status = toolResponse["status"] ?? "done";
|
|
1158
|
+
return `Result: Subagent \u2014 ${status}`;
|
|
1159
|
+
}
|
|
1160
|
+
default:
|
|
1161
|
+
return `Result: ${toolName} \u2014 success`;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function lastLine(text) {
|
|
1165
|
+
if (!text) return "";
|
|
1166
|
+
const lines = text.split("\n").filter((l) => l.trim() !== "");
|
|
1167
|
+
const line = lines[lines.length - 1] ?? "";
|
|
1168
|
+
return truncateContent(line, 120);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/lib/transcriptParser.ts
|
|
1172
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1173
|
+
import { createReadStream } from "fs";
|
|
1174
|
+
import { createInterface } from "readline";
|
|
1175
|
+
import { randomUUID } from "crypto";
|
|
1176
|
+
var MAX_TOOL_DATA_LENGTH = 2048;
|
|
1177
|
+
var MAX_THINKING_LENGTH = 500;
|
|
1178
|
+
var MAX_TEXT_LENGTH = 500;
|
|
1179
|
+
function truncateObject(obj, maxLen) {
|
|
1180
|
+
const str = JSON.stringify(obj);
|
|
1181
|
+
if (str.length <= maxLen) return obj;
|
|
1182
|
+
const result = {};
|
|
1183
|
+
let currentLen = 2;
|
|
1184
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1185
|
+
const valStr = typeof value === "string" ? `"${value.slice(0, 200)}"` : JSON.stringify(value)?.slice(0, 200) ?? "null";
|
|
1186
|
+
const entryLen = key.length + valStr.length + 4;
|
|
1187
|
+
if (currentLen + entryLen > maxLen) {
|
|
1188
|
+
result["_truncated"] = true;
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
result[key] = typeof value === "string" && value.length > 200 ? value.slice(0, 200) + "..." : value;
|
|
1192
|
+
currentLen += entryLen;
|
|
1193
|
+
}
|
|
1194
|
+
return result;
|
|
1195
|
+
}
|
|
1196
|
+
async function extractTranscriptEvents(transcriptPath) {
|
|
1197
|
+
const events = [];
|
|
1198
|
+
if (!existsSync3(transcriptPath)) return events;
|
|
1199
|
+
const seenMessageIds = /* @__PURE__ */ new Map();
|
|
1200
|
+
const toolUseIdMap = /* @__PURE__ */ new Map();
|
|
1201
|
+
try {
|
|
1202
|
+
const rl = createInterface({
|
|
1203
|
+
input: createReadStream(transcriptPath, "utf-8"),
|
|
1204
|
+
crlfDelay: Infinity
|
|
1205
|
+
});
|
|
1206
|
+
for await (const line of rl) {
|
|
1207
|
+
if (!line.trim()) continue;
|
|
1208
|
+
try {
|
|
1209
|
+
const entry = JSON.parse(line);
|
|
1210
|
+
const entryType = entry.type ?? entry.role ?? "";
|
|
1211
|
+
const subtype = entry.subtype;
|
|
1212
|
+
if (["file-history-snapshot", "progress", "queue-operation"].includes(entryType)) {
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
const message = entry.message;
|
|
1216
|
+
const messageId = message?.id;
|
|
1217
|
+
const timestamp = entry.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1218
|
+
let usage;
|
|
1219
|
+
let model;
|
|
1220
|
+
if (message) {
|
|
1221
|
+
const rawUsage = message.usage;
|
|
1222
|
+
if (rawUsage) {
|
|
1223
|
+
usage = {
|
|
1224
|
+
inputTokens: rawUsage.input_tokens ?? 0,
|
|
1225
|
+
outputTokens: rawUsage.output_tokens ?? 0,
|
|
1226
|
+
cacheReadTokens: rawUsage.cache_read_input_tokens ?? 0,
|
|
1227
|
+
cacheCreationTokens: rawUsage.cache_creation_input_tokens ?? 0
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
model = message.model;
|
|
1231
|
+
if (messageId) {
|
|
1232
|
+
seenMessageIds.set(messageId, { usage, model });
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
const content = message?.content ?? entry.content;
|
|
1236
|
+
if (entryType === "assistant") {
|
|
1237
|
+
if (!Array.isArray(content)) continue;
|
|
1238
|
+
for (const block of content) {
|
|
1239
|
+
if (typeof block !== "object" || block === null) continue;
|
|
1240
|
+
const blockRec = block;
|
|
1241
|
+
const blockType = blockRec.type;
|
|
1242
|
+
if (blockType === "tool_use") {
|
|
1243
|
+
const toolName = blockRec.name ?? "unknown";
|
|
1244
|
+
const toolUseId = blockRec.id;
|
|
1245
|
+
const toolInput = truncateObject(
|
|
1246
|
+
blockRec.input ?? {},
|
|
1247
|
+
MAX_TOOL_DATA_LENGTH
|
|
1248
|
+
);
|
|
1249
|
+
const contentSummary = summarizeToolInput(toolName, toolInput);
|
|
1250
|
+
const eventUuid = randomUUID();
|
|
1251
|
+
if (toolUseId) {
|
|
1252
|
+
toolUseIdMap.set(toolUseId, eventUuid);
|
|
1253
|
+
}
|
|
1254
|
+
events.push({
|
|
1255
|
+
uuid: eventUuid,
|
|
1256
|
+
parentUuid: null,
|
|
1257
|
+
timestamp,
|
|
1258
|
+
type: "tool_use",
|
|
1259
|
+
toolName,
|
|
1260
|
+
toolUseId,
|
|
1261
|
+
toolInput,
|
|
1262
|
+
usage,
|
|
1263
|
+
model,
|
|
1264
|
+
content: contentSummary
|
|
1265
|
+
});
|
|
1266
|
+
} else if (blockType === "thinking") {
|
|
1267
|
+
const thinkingText = blockRec.thinking ?? "";
|
|
1268
|
+
events.push({
|
|
1269
|
+
uuid: randomUUID(),
|
|
1270
|
+
parentUuid: null,
|
|
1271
|
+
timestamp,
|
|
1272
|
+
type: "thinking",
|
|
1273
|
+
thinkingText: thinkingText.slice(0, MAX_THINKING_LENGTH),
|
|
1274
|
+
usage,
|
|
1275
|
+
model,
|
|
1276
|
+
content: `Thinking: ${truncateContent(thinkingText, 120)}`
|
|
1277
|
+
});
|
|
1278
|
+
} else if (blockType === "text") {
|
|
1279
|
+
const text = blockRec.text ?? "";
|
|
1280
|
+
events.push({
|
|
1281
|
+
uuid: randomUUID(),
|
|
1282
|
+
parentUuid: null,
|
|
1283
|
+
timestamp,
|
|
1284
|
+
type: "text",
|
|
1285
|
+
text: text.slice(0, MAX_TEXT_LENGTH),
|
|
1286
|
+
usage,
|
|
1287
|
+
model,
|
|
1288
|
+
content: `Text: ${truncateContent(text, 120)}`
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (entryType === "human" || entryType === "user") {
|
|
1294
|
+
if (!Array.isArray(content)) continue;
|
|
1295
|
+
for (const block of content) {
|
|
1296
|
+
if (typeof block !== "object" || block === null) continue;
|
|
1297
|
+
const blockRec = block;
|
|
1298
|
+
if (blockRec.type !== "tool_result") continue;
|
|
1299
|
+
const toolUseId = blockRec.tool_use_id;
|
|
1300
|
+
const isError = blockRec.is_error ?? false;
|
|
1301
|
+
const resultContent = blockRec.content;
|
|
1302
|
+
const toolResponse = truncateObject(
|
|
1303
|
+
typeof resultContent === "string" ? { output: resultContent.slice(0, MAX_TOOL_DATA_LENGTH) } : resultContent ?? {},
|
|
1304
|
+
MAX_TOOL_DATA_LENGTH
|
|
1305
|
+
);
|
|
1306
|
+
const parentUuid = toolUseId ? toolUseIdMap.get(toolUseId) ?? null : null;
|
|
1307
|
+
let toolName = "unknown";
|
|
1308
|
+
if (parentUuid) {
|
|
1309
|
+
const parent = events.find((e) => e.uuid === parentUuid);
|
|
1310
|
+
if (parent?.toolName) toolName = parent.toolName;
|
|
1311
|
+
}
|
|
1312
|
+
const contentSummary = isError ? `Error: ${toolName} \u2014 ${truncateContent(typeof resultContent === "string" ? resultContent : "failed", 200)}` : summarizeToolResponse(toolName, {}, toolResponse);
|
|
1313
|
+
events.push({
|
|
1314
|
+
uuid: randomUUID(),
|
|
1315
|
+
parentUuid,
|
|
1316
|
+
timestamp,
|
|
1317
|
+
type: "tool_result",
|
|
1318
|
+
toolUseId,
|
|
1319
|
+
toolName,
|
|
1320
|
+
toolResponse,
|
|
1321
|
+
isError,
|
|
1322
|
+
content: contentSummary
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (entryType === "system") {
|
|
1327
|
+
if (subtype === "compact_boundary") {
|
|
1328
|
+
const trigger = entry.trigger ?? "auto";
|
|
1329
|
+
const preTokens = entry.pre_tokens;
|
|
1330
|
+
events.push({
|
|
1331
|
+
uuid: randomUUID(),
|
|
1332
|
+
parentUuid: null,
|
|
1333
|
+
timestamp,
|
|
1334
|
+
type: "compact",
|
|
1335
|
+
compactTrigger: trigger,
|
|
1336
|
+
preTokens,
|
|
1337
|
+
content: `Context compacted (${trigger}${preTokens ? `, ${preTokens} tokens` : ""})`
|
|
1338
|
+
});
|
|
1339
|
+
} else if (subtype === "api_error") {
|
|
1340
|
+
const errorCause = entry.error ?? entry.message ?? "unknown";
|
|
1341
|
+
const retryAttempt = entry.retry_attempt;
|
|
1342
|
+
events.push({
|
|
1343
|
+
uuid: randomUUID(),
|
|
1344
|
+
parentUuid: null,
|
|
1345
|
+
timestamp,
|
|
1346
|
+
type: "api_error",
|
|
1347
|
+
errorCause: typeof errorCause === "string" ? errorCause : "unknown",
|
|
1348
|
+
retryAttempt,
|
|
1349
|
+
content: `API error: ${truncateContent(typeof errorCause === "string" ? errorCause : "unknown", 200)}`
|
|
1350
|
+
});
|
|
1351
|
+
} else if (subtype === "turn_duration") {
|
|
1352
|
+
const durationMs = entry.duration_ms;
|
|
1353
|
+
events.push({
|
|
1354
|
+
uuid: randomUUID(),
|
|
1355
|
+
parentUuid: null,
|
|
1356
|
+
timestamp,
|
|
1357
|
+
type: "turn_duration",
|
|
1358
|
+
durationMs,
|
|
1359
|
+
content: `Turn duration: ${durationMs ?? 0}ms`
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
} catch {
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
} catch {
|
|
1367
|
+
}
|
|
1368
|
+
return events;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/commands/hook.ts
|
|
1045
1372
|
function getSessionFile(claudeSessionId) {
|
|
1046
1373
|
return join(os2.tmpdir(), `valt-session-${claudeSessionId}.json`);
|
|
1047
1374
|
}
|
|
@@ -1144,7 +1471,7 @@ function readSessionFile(claudeSessionId) {
|
|
|
1144
1471
|
if (claudeSessionId) {
|
|
1145
1472
|
const path = getSessionFile(claudeSessionId);
|
|
1146
1473
|
try {
|
|
1147
|
-
if (
|
|
1474
|
+
if (existsSync4(path)) {
|
|
1148
1475
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
1149
1476
|
}
|
|
1150
1477
|
} catch {
|
|
@@ -1272,10 +1599,10 @@ async function parseTranscript(transcriptPath) {
|
|
|
1272
1599
|
cacheCreationTokens: 0,
|
|
1273
1600
|
model: "unknown"
|
|
1274
1601
|
};
|
|
1275
|
-
if (!
|
|
1602
|
+
if (!existsSync4(transcriptPath)) return result;
|
|
1276
1603
|
try {
|
|
1277
|
-
const rl =
|
|
1278
|
-
input:
|
|
1604
|
+
const rl = createInterface2({
|
|
1605
|
+
input: createReadStream2(transcriptPath, "utf-8"),
|
|
1279
1606
|
crlfDelay: Infinity
|
|
1280
1607
|
});
|
|
1281
1608
|
for await (const line of rl) {
|
|
@@ -1397,6 +1724,7 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
|
|
|
1397
1724
|
session_id: sessionId,
|
|
1398
1725
|
event_type: "session.start",
|
|
1399
1726
|
timestamp: startedAt,
|
|
1727
|
+
content: `Session started \u2014 ${model} on ${projectSlug}`,
|
|
1400
1728
|
tool: "claude-code",
|
|
1401
1729
|
model,
|
|
1402
1730
|
metadata: {
|
|
@@ -1468,6 +1796,8 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
|
|
|
1468
1796
|
}
|
|
1469
1797
|
}
|
|
1470
1798
|
const { eventType, filePath, command } = classifyToolCall(toolName, toolInput);
|
|
1799
|
+
const contentSummary = summarizeToolInput(toolName, toolInput);
|
|
1800
|
+
const toolInputSummaryMeta = JSON.stringify(toolInput).slice(0, 1024);
|
|
1471
1801
|
const events = [];
|
|
1472
1802
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1473
1803
|
events.push({
|
|
@@ -1475,20 +1805,23 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
|
|
|
1475
1805
|
session_id: session.sessionId,
|
|
1476
1806
|
event_type: "tool.call",
|
|
1477
1807
|
timestamp,
|
|
1808
|
+
content: contentSummary,
|
|
1478
1809
|
tool: "claude-code",
|
|
1479
1810
|
tool_name: toolName,
|
|
1480
1811
|
metadata: {
|
|
1481
1812
|
tool_name: toolName,
|
|
1482
1813
|
file_path: filePath,
|
|
1483
|
-
command
|
|
1814
|
+
command,
|
|
1815
|
+
tool_input_summary: toolInputSummaryMeta
|
|
1484
1816
|
}
|
|
1485
1817
|
});
|
|
1486
1818
|
if (eventType !== "tool.call") {
|
|
1487
|
-
|
|
1819
|
+
const classifiedEvent = {
|
|
1488
1820
|
event_id: crypto.randomUUID(),
|
|
1489
1821
|
session_id: session.sessionId,
|
|
1490
1822
|
event_type: eventType,
|
|
1491
1823
|
timestamp,
|
|
1824
|
+
content: contentSummary,
|
|
1492
1825
|
tool: "claude-code",
|
|
1493
1826
|
tool_name: toolName,
|
|
1494
1827
|
file_path: filePath,
|
|
@@ -1497,7 +1830,11 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
|
|
|
1497
1830
|
...filePath ? { file_path: filePath } : {},
|
|
1498
1831
|
...command ? { command } : {}
|
|
1499
1832
|
}
|
|
1500
|
-
}
|
|
1833
|
+
};
|
|
1834
|
+
if (eventType === "command.execute" && command) {
|
|
1835
|
+
classifiedEvent["command_category"] = classifyCommand(command);
|
|
1836
|
+
}
|
|
1837
|
+
events.push(classifiedEvent);
|
|
1501
1838
|
}
|
|
1502
1839
|
await sendEvents(session.endpoint, session.apiKey, events);
|
|
1503
1840
|
} catch {
|
|
@@ -1510,19 +1847,24 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
|
|
|
1510
1847
|
const session = readSessionFile(claudeSessionId);
|
|
1511
1848
|
if (!session) return;
|
|
1512
1849
|
const toolName = hookData["tool_name"] ?? "unknown";
|
|
1850
|
+
const toolInput = hookData["tool_input"] ?? {};
|
|
1513
1851
|
const toolResponse = hookData["tool_response"];
|
|
1514
1852
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1853
|
+
const resultContent = summarizeToolResponse(toolName, toolInput, toolResponse ?? {});
|
|
1854
|
+
const toolResponseSummaryMeta = JSON.stringify(toolResponse ?? {}).slice(0, 1024);
|
|
1515
1855
|
const events = [{
|
|
1516
1856
|
event_id: crypto.randomUUID(),
|
|
1517
1857
|
session_id: session.sessionId,
|
|
1518
1858
|
event_type: "tool.result",
|
|
1519
1859
|
timestamp,
|
|
1860
|
+
content: resultContent,
|
|
1520
1861
|
tool: "claude-code",
|
|
1521
1862
|
tool_name: toolName,
|
|
1522
1863
|
metadata: {
|
|
1523
1864
|
tool_name: toolName,
|
|
1524
1865
|
exit_code: toolResponse?.["exit_code"],
|
|
1525
|
-
success: toolResponse?.["success"] ?? true
|
|
1866
|
+
success: toolResponse?.["success"] ?? true,
|
|
1867
|
+
tool_response_summary: toolResponseSummaryMeta
|
|
1526
1868
|
}
|
|
1527
1869
|
}];
|
|
1528
1870
|
if (toolName === "Bash" && toolResponse) {
|
|
@@ -1530,11 +1872,13 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
|
|
|
1530
1872
|
if (stdout) {
|
|
1531
1873
|
const testResults = parseTestResults(stdout);
|
|
1532
1874
|
if (testResults) {
|
|
1875
|
+
const testContent = `Tests: ${testResults.testsPassed}/${testResults.testsRun} passed (${testResults.framework}) ${testResults.testsFailed === 0 ? "PASS" : "FAIL"}`;
|
|
1533
1876
|
events.push({
|
|
1534
1877
|
event_id: crypto.randomUUID(),
|
|
1535
1878
|
session_id: session.sessionId,
|
|
1536
1879
|
event_type: "test.run",
|
|
1537
1880
|
timestamp,
|
|
1881
|
+
content: testContent,
|
|
1538
1882
|
tool: "claude-code",
|
|
1539
1883
|
metadata: {
|
|
1540
1884
|
tests_run: testResults.testsRun,
|
|
@@ -1549,7 +1893,7 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
|
|
|
1549
1893
|
}
|
|
1550
1894
|
try {
|
|
1551
1895
|
const tPath = hookData["transcript_path"] ?? session.transcriptPath;
|
|
1552
|
-
if (tPath &&
|
|
1896
|
+
if (tPath && existsSync4(tPath)) {
|
|
1553
1897
|
if (!session.transcriptPath) session.transcriptPath = tPath;
|
|
1554
1898
|
const deltas = parseTranscriptIncremental(
|
|
1555
1899
|
tPath,
|
|
@@ -1604,6 +1948,7 @@ var toolErrorCommand = new Command13("tool-error").description("Hook: called whe
|
|
|
1604
1948
|
session_id: session.sessionId,
|
|
1605
1949
|
event_type: "tool.error",
|
|
1606
1950
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1951
|
+
content: `Error: ${toolName} \u2014 ${truncateContent(errorMsg, 200)}`,
|
|
1607
1952
|
tool: "claude-code",
|
|
1608
1953
|
tool_name: toolName,
|
|
1609
1954
|
metadata: {
|
|
@@ -1621,15 +1966,17 @@ var subagentStartCommand = new Command13("subagent-start").description("Hook: ca
|
|
|
1621
1966
|
const claudeSessionId = hookData["session_id"];
|
|
1622
1967
|
const session = readSessionFile(claudeSessionId);
|
|
1623
1968
|
if (!session) return;
|
|
1969
|
+
const agentType = hookData["agent_type"] ?? "unknown";
|
|
1624
1970
|
await sendEvents(session.endpoint, session.apiKey, [{
|
|
1625
1971
|
event_id: crypto.randomUUID(),
|
|
1626
1972
|
session_id: session.sessionId,
|
|
1627
1973
|
event_type: "subagent.start",
|
|
1628
1974
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1975
|
+
content: `Subagent started: ${agentType}`,
|
|
1629
1976
|
tool: "claude-code",
|
|
1630
1977
|
metadata: {
|
|
1631
1978
|
agent_id: hookData["agent_id"],
|
|
1632
|
-
agent_type:
|
|
1979
|
+
agent_type: agentType
|
|
1633
1980
|
}
|
|
1634
1981
|
}]);
|
|
1635
1982
|
} catch {
|
|
@@ -1641,15 +1988,17 @@ var subagentStopCommand = new Command13("subagent-stop").description("Hook: call
|
|
|
1641
1988
|
const claudeSessionId = hookData["session_id"];
|
|
1642
1989
|
const session = readSessionFile(claudeSessionId);
|
|
1643
1990
|
if (!session) return;
|
|
1991
|
+
const stopAgentType = hookData["agent_type"] ?? "unknown";
|
|
1644
1992
|
await sendEvents(session.endpoint, session.apiKey, [{
|
|
1645
1993
|
event_id: crypto.randomUUID(),
|
|
1646
1994
|
session_id: session.sessionId,
|
|
1647
1995
|
event_type: "subagent.stop",
|
|
1648
1996
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1997
|
+
content: `Subagent stopped: ${stopAgentType}`,
|
|
1649
1998
|
tool: "claude-code",
|
|
1650
1999
|
metadata: {
|
|
1651
2000
|
agent_id: hookData["agent_id"],
|
|
1652
|
-
agent_type:
|
|
2001
|
+
agent_type: stopAgentType,
|
|
1653
2002
|
transcript_path: hookData["agent_transcript_path"]
|
|
1654
2003
|
}
|
|
1655
2004
|
}]);
|
|
@@ -1669,6 +2018,7 @@ var promptSubmitCommand = new Command13("prompt-submit").description("Hook: call
|
|
|
1669
2018
|
session_id: session.sessionId,
|
|
1670
2019
|
event_type: "prompt.submit",
|
|
1671
2020
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2021
|
+
content: `Prompt submitted (${promptText.length} chars)`,
|
|
1672
2022
|
tool: "claude-code",
|
|
1673
2023
|
metadata: {
|
|
1674
2024
|
prompt_hash: promptHashValue,
|
|
@@ -1728,12 +2078,15 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
|
|
|
1728
2078
|
}).trim();
|
|
1729
2079
|
} catch {
|
|
1730
2080
|
}
|
|
2081
|
+
const durationSec = Math.round(durationMs / 1e3);
|
|
2082
|
+
const costStr = metrics?.totalCostUsd ? `$${metrics.totalCostUsd.toFixed(4)}` : "$0";
|
|
1731
2083
|
const events = [
|
|
1732
2084
|
{
|
|
1733
2085
|
event_id: crypto.randomUUID(),
|
|
1734
2086
|
session_id: session.sessionId,
|
|
1735
2087
|
event_type: "session.end",
|
|
1736
2088
|
timestamp: now.toISOString(),
|
|
2089
|
+
content: `Session ended \u2014 ${durationSec}s, ${costStr}`,
|
|
1737
2090
|
duration_ms: durationMs,
|
|
1738
2091
|
tool: "claude-code",
|
|
1739
2092
|
model: metrics?.model ?? session.model,
|
|
@@ -1752,11 +2105,13 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
|
|
|
1752
2105
|
}
|
|
1753
2106
|
];
|
|
1754
2107
|
if (metrics && (metrics.totalCostUsd > 0 || metrics.promptTokens > 0)) {
|
|
2108
|
+
const totalTokens = metrics.promptTokens + metrics.completionTokens;
|
|
1755
2109
|
events.push({
|
|
1756
2110
|
event_id: crypto.randomUUID(),
|
|
1757
2111
|
session_id: session.sessionId,
|
|
1758
2112
|
event_type: "cost",
|
|
1759
2113
|
timestamp: now.toISOString(),
|
|
2114
|
+
content: `Cost: $${metrics.totalCostUsd.toFixed(4)} (${totalTokens} tokens)`,
|
|
1760
2115
|
tool: "claude-code",
|
|
1761
2116
|
tokens_prompt: metrics.promptTokens,
|
|
1762
2117
|
tokens_completion: metrics.completionTokens,
|
|
@@ -1770,114 +2125,480 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
|
|
|
1770
2125
|
}
|
|
1771
2126
|
sendEvents(session.endpoint, session.apiKey, events).catch(() => {
|
|
1772
2127
|
});
|
|
2128
|
+
if (transcriptPath) {
|
|
2129
|
+
extractTranscriptEvents(transcriptPath).then((transcriptEvents) => {
|
|
2130
|
+
if (transcriptEvents.length === 0) return;
|
|
2131
|
+
const valtEvents = transcriptEvents.map((te) => ({
|
|
2132
|
+
event_id: te.uuid,
|
|
2133
|
+
session_id: session.sessionId,
|
|
2134
|
+
event_type: te.type === "tool_use" ? "tool.call" : te.type === "tool_result" ? "tool.result" : te.type === "thinking" ? "reasoning" : te.type === "text" ? "completion" : te.type === "compact" ? "context.compact" : te.type === "api_error" ? "error" : "cost",
|
|
2135
|
+
event_subtype: `transcript.${te.type}`,
|
|
2136
|
+
timestamp: te.timestamp,
|
|
2137
|
+
content: te.content,
|
|
2138
|
+
tool: "claude-code",
|
|
2139
|
+
tool_name: te.toolName ?? "",
|
|
2140
|
+
model: te.model ?? "",
|
|
2141
|
+
tokens_prompt: te.usage?.inputTokens ?? 0,
|
|
2142
|
+
tokens_completion: te.usage?.outputTokens ?? 0,
|
|
2143
|
+
metadata: {
|
|
2144
|
+
source: "transcript",
|
|
2145
|
+
parent_uuid: te.parentUuid,
|
|
2146
|
+
tool_use_id: te.toolUseId,
|
|
2147
|
+
is_error: te.isError,
|
|
2148
|
+
cache_read_tokens: te.usage?.cacheReadTokens,
|
|
2149
|
+
cache_creation_tokens: te.usage?.cacheCreationTokens
|
|
2150
|
+
}
|
|
2151
|
+
}));
|
|
2152
|
+
const BATCH_SIZE = 100;
|
|
2153
|
+
for (let i = 0; i < valtEvents.length; i += BATCH_SIZE) {
|
|
2154
|
+
const batch = valtEvents.slice(i, i + BATCH_SIZE);
|
|
2155
|
+
sendEvents(session.endpoint, session.apiKey, batch).catch(() => {
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
}).catch(() => {
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
1773
2161
|
if (sessionFilePath) {
|
|
1774
2162
|
try {
|
|
1775
2163
|
unlinkSync2(sessionFilePath);
|
|
1776
2164
|
} catch {
|
|
1777
2165
|
}
|
|
1778
2166
|
}
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s${
|
|
2167
|
+
const logCostStr = metrics?.totalCostUsd ? ` $${metrics.totalCostUsd.toFixed(4)}` : "";
|
|
2168
|
+
const logTokenStr = metrics?.promptTokens ? ` ${metrics.promptTokens + metrics.completionTokens} tokens` : "";
|
|
2169
|
+
success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s${logCostStr}${logTokenStr})`);
|
|
1782
2170
|
} catch {
|
|
1783
2171
|
}
|
|
1784
2172
|
});
|
|
1785
|
-
var
|
|
2173
|
+
var stopCommand = new Command13("stop").description("Hook: called when the agent stops responding (Stop)").action(async () => {
|
|
2174
|
+
try {
|
|
2175
|
+
const hookData = await readStdin();
|
|
2176
|
+
const claudeSessionId = hookData["session_id"];
|
|
2177
|
+
const session = readSessionFile(claudeSessionId);
|
|
2178
|
+
if (!session) return;
|
|
2179
|
+
const lastMsg = truncateContent(hookData["last_assistant_message"] ?? "", 120);
|
|
2180
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2181
|
+
event_id: crypto.randomUUID(),
|
|
2182
|
+
session_id: session.sessionId,
|
|
2183
|
+
event_type: "session.stop",
|
|
2184
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2185
|
+
content: `Agent stopping \u2014 "${lastMsg}"`,
|
|
2186
|
+
tool: "claude-code",
|
|
2187
|
+
metadata: { ...hookData }
|
|
2188
|
+
}]);
|
|
2189
|
+
} catch {
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
var stopFailureCommand = new Command13("stop-failure").description("Hook: called when a stop attempt fails (StopFailure)").action(async () => {
|
|
2193
|
+
try {
|
|
2194
|
+
const hookData = await readStdin();
|
|
2195
|
+
const claudeSessionId = hookData["session_id"];
|
|
2196
|
+
const session = readSessionFile(claudeSessionId);
|
|
2197
|
+
if (!session) return;
|
|
2198
|
+
const err = hookData["error"] ?? "unknown";
|
|
2199
|
+
const details = hookData["error_details"] ?? "";
|
|
2200
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2201
|
+
event_id: crypto.randomUUID(),
|
|
2202
|
+
session_id: session.sessionId,
|
|
2203
|
+
event_type: "session.stop_failure",
|
|
2204
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2205
|
+
content: `Stop failed: ${err} \u2014 ${truncateContent(details, 150)}`,
|
|
2206
|
+
tool: "claude-code",
|
|
2207
|
+
metadata: { ...hookData }
|
|
2208
|
+
}]);
|
|
2209
|
+
} catch {
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
var compactCommand = new Command13("compact").description("Hook: called on context compaction (PreCompact/PostCompact)").action(async () => {
|
|
2213
|
+
try {
|
|
2214
|
+
const hookData = await readStdin();
|
|
2215
|
+
const claudeSessionId = hookData["session_id"];
|
|
2216
|
+
const session = readSessionFile(claudeSessionId);
|
|
2217
|
+
if (!session) return;
|
|
2218
|
+
const hookEventName = hookData["hook_event_name"] ?? "";
|
|
2219
|
+
const isPost = hookEventName.toLowerCase().includes("post");
|
|
2220
|
+
const trigger = hookData["trigger"] ?? "auto";
|
|
2221
|
+
const preTokens = hookData["pre_tokens"];
|
|
2222
|
+
const summary = hookData["summary"] ?? "";
|
|
2223
|
+
const content = isPost ? `Context compacted (${trigger}) \u2014 ${truncateContent(summary, 150)}` : `Context compacting (${trigger}${preTokens ? `, ${preTokens} tokens` : ""})`;
|
|
2224
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2225
|
+
event_id: crypto.randomUUID(),
|
|
2226
|
+
session_id: session.sessionId,
|
|
2227
|
+
event_type: "context.compact",
|
|
2228
|
+
event_subtype: isPost ? "post" : "pre",
|
|
2229
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2230
|
+
content,
|
|
2231
|
+
tool: "claude-code",
|
|
2232
|
+
metadata: { trigger, pre_tokens: preTokens, ...hookData }
|
|
2233
|
+
}]);
|
|
2234
|
+
} catch {
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
var notificationCommand = new Command13("notification").description("Hook: called on agent notifications (Notification)").action(async () => {
|
|
2238
|
+
try {
|
|
2239
|
+
const hookData = await readStdin();
|
|
2240
|
+
const claudeSessionId = hookData["session_id"];
|
|
2241
|
+
const session = readSessionFile(claudeSessionId);
|
|
2242
|
+
if (!session) return;
|
|
2243
|
+
const nType = hookData["type"] ?? "info";
|
|
2244
|
+
const title = hookData["title"] ?? hookData["message"] ?? "";
|
|
2245
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2246
|
+
event_id: crypto.randomUUID(),
|
|
2247
|
+
session_id: session.sessionId,
|
|
2248
|
+
event_type: "notification",
|
|
2249
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2250
|
+
content: `Notification [${nType}]: ${truncateContent(title, 200)}`,
|
|
2251
|
+
tool: "claude-code",
|
|
2252
|
+
metadata: { ...hookData }
|
|
2253
|
+
}]);
|
|
2254
|
+
} catch {
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
var permissionRequestCommand = new Command13("permission-request").description("Hook: called when a permission dialog appears (PermissionRequest)").action(async () => {
|
|
2258
|
+
try {
|
|
2259
|
+
const hookData = await readStdin();
|
|
2260
|
+
const claudeSessionId = hookData["session_id"];
|
|
2261
|
+
const session = readSessionFile(claudeSessionId);
|
|
2262
|
+
if (!session) return;
|
|
2263
|
+
const toolName = hookData["tool_name"] ?? "unknown";
|
|
2264
|
+
const toolInput = hookData["tool_input"] ?? {};
|
|
2265
|
+
const inputSummary = summarizeToolInput(toolName, toolInput);
|
|
2266
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2267
|
+
event_id: crypto.randomUUID(),
|
|
2268
|
+
session_id: session.sessionId,
|
|
2269
|
+
event_type: "permission.request",
|
|
2270
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2271
|
+
content: `Permission requested: ${toolName} \u2014 ${inputSummary}`,
|
|
2272
|
+
tool: "claude-code",
|
|
2273
|
+
tool_name: toolName,
|
|
2274
|
+
metadata: { ...hookData, tool_input_summary: JSON.stringify(toolInput).slice(0, 500) }
|
|
2275
|
+
}]);
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
var configLoadedCommand = new Command13("config-loaded").description("Hook: called when instructions are loaded (InstructionsLoaded)").action(async () => {
|
|
2280
|
+
try {
|
|
2281
|
+
const hookData = await readStdin();
|
|
2282
|
+
const claudeSessionId = hookData["session_id"];
|
|
2283
|
+
const session = readSessionFile(claudeSessionId);
|
|
2284
|
+
if (!session) return;
|
|
2285
|
+
const filePath = hookData["file_path"] ?? "";
|
|
2286
|
+
const memoryType = hookData["memory_type"] ?? "";
|
|
2287
|
+
const loadReason = hookData["load_reason"] ?? "";
|
|
2288
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2289
|
+
event_id: crypto.randomUUID(),
|
|
2290
|
+
session_id: session.sessionId,
|
|
2291
|
+
event_type: "config.loaded",
|
|
2292
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2293
|
+
content: `Instructions loaded: ${fileBasename(filePath)} (${memoryType}, ${loadReason})`,
|
|
2294
|
+
tool: "claude-code",
|
|
2295
|
+
metadata: { ...hookData }
|
|
2296
|
+
}]);
|
|
2297
|
+
} catch {
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
var configChangeCommand = new Command13("config-change").description("Hook: called when config changes (ConfigChange)").action(async () => {
|
|
2301
|
+
try {
|
|
2302
|
+
const hookData = await readStdin();
|
|
2303
|
+
const claudeSessionId = hookData["session_id"];
|
|
2304
|
+
const session = readSessionFile(claudeSessionId);
|
|
2305
|
+
if (!session) return;
|
|
2306
|
+
const source = hookData["source"] ?? "unknown";
|
|
2307
|
+
const filePath = hookData["file_path"];
|
|
2308
|
+
const label = filePath ? `${source} (${fileBasename(filePath)})` : source;
|
|
2309
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2310
|
+
event_id: crypto.randomUUID(),
|
|
2311
|
+
session_id: session.sessionId,
|
|
2312
|
+
event_type: "config.change",
|
|
2313
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2314
|
+
content: `Config changed: ${label}`,
|
|
2315
|
+
tool: "claude-code",
|
|
2316
|
+
metadata: { ...hookData }
|
|
2317
|
+
}]);
|
|
2318
|
+
} catch {
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
var elicitationCommand = new Command13("elicitation").description("Hook: called on MCP elicitation (Elicitation)").action(async () => {
|
|
2322
|
+
try {
|
|
2323
|
+
const hookData = await readStdin();
|
|
2324
|
+
const claudeSessionId = hookData["session_id"];
|
|
2325
|
+
const session = readSessionFile(claudeSessionId);
|
|
2326
|
+
if (!session) return;
|
|
2327
|
+
const mode = hookData["mode"] ?? "unknown";
|
|
2328
|
+
const serverName = hookData["server_name"] ?? "unknown";
|
|
2329
|
+
const message = hookData["message"] ?? "";
|
|
2330
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2331
|
+
event_id: crypto.randomUUID(),
|
|
2332
|
+
session_id: session.sessionId,
|
|
2333
|
+
event_type: "mcp.elicitation",
|
|
2334
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2335
|
+
content: `MCP elicitation [${mode}]: ${serverName} \u2014 ${truncateContent(message, 150)}`,
|
|
2336
|
+
tool: "claude-code",
|
|
2337
|
+
metadata: { ...hookData }
|
|
2338
|
+
}]);
|
|
2339
|
+
} catch {
|
|
2340
|
+
}
|
|
2341
|
+
});
|
|
2342
|
+
var elicitationResultCommand = new Command13("elicitation-result").description("Hook: called on MCP elicitation result (ElicitationResult)").action(async () => {
|
|
2343
|
+
try {
|
|
2344
|
+
const hookData = await readStdin();
|
|
2345
|
+
const claudeSessionId = hookData["session_id"];
|
|
2346
|
+
const session = readSessionFile(claudeSessionId);
|
|
2347
|
+
if (!session) return;
|
|
2348
|
+
const serverName = hookData["server_name"] ?? "unknown";
|
|
2349
|
+
const action = hookData["action"] ?? "unknown";
|
|
2350
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2351
|
+
event_id: crypto.randomUUID(),
|
|
2352
|
+
session_id: session.sessionId,
|
|
2353
|
+
event_type: "mcp.elicitation_result",
|
|
2354
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2355
|
+
content: `MCP elicitation result: ${serverName} \u2014 ${action}`,
|
|
2356
|
+
tool: "claude-code",
|
|
2357
|
+
metadata: { ...hookData }
|
|
2358
|
+
}]);
|
|
2359
|
+
} catch {
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
var worktreeCreateCommand = new Command13("worktree-create").description("Hook: called when a worktree is created (WorktreeCreate)").action(async () => {
|
|
2363
|
+
try {
|
|
2364
|
+
const hookData = await readStdin();
|
|
2365
|
+
const claudeSessionId = hookData["session_id"];
|
|
2366
|
+
const session = readSessionFile(claudeSessionId);
|
|
2367
|
+
if (!session) return;
|
|
2368
|
+
const name = hookData["name"] ?? "unknown";
|
|
2369
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2370
|
+
event_id: crypto.randomUUID(),
|
|
2371
|
+
session_id: session.sessionId,
|
|
2372
|
+
event_type: "worktree.create",
|
|
2373
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2374
|
+
content: `Worktree creating: ${name}`,
|
|
2375
|
+
tool: "claude-code",
|
|
2376
|
+
metadata: { ...hookData }
|
|
2377
|
+
}]);
|
|
2378
|
+
} catch {
|
|
2379
|
+
}
|
|
2380
|
+
});
|
|
2381
|
+
var worktreeRemoveCommand = new Command13("worktree-remove").description("Hook: called when a worktree is removed (WorktreeRemove)").action(async () => {
|
|
2382
|
+
try {
|
|
2383
|
+
const hookData = await readStdin();
|
|
2384
|
+
const claudeSessionId = hookData["session_id"];
|
|
2385
|
+
const session = readSessionFile(claudeSessionId);
|
|
2386
|
+
if (!session) return;
|
|
2387
|
+
const worktreePath = hookData["worktree_path"] ?? "";
|
|
2388
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2389
|
+
event_id: crypto.randomUUID(),
|
|
2390
|
+
session_id: session.sessionId,
|
|
2391
|
+
event_type: "worktree.remove",
|
|
2392
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2393
|
+
content: `Worktree removing: ${fileBasename(worktreePath)}`,
|
|
2394
|
+
tool: "claude-code",
|
|
2395
|
+
metadata: { ...hookData }
|
|
2396
|
+
}]);
|
|
2397
|
+
} catch {
|
|
2398
|
+
}
|
|
2399
|
+
});
|
|
2400
|
+
var teammateIdleCommand = new Command13("teammate-idle").description("Hook: called when a teammate is idle (TeammateIdle)").action(async () => {
|
|
2401
|
+
try {
|
|
2402
|
+
const hookData = await readStdin();
|
|
2403
|
+
const claudeSessionId = hookData["session_id"];
|
|
2404
|
+
const session = readSessionFile(claudeSessionId);
|
|
2405
|
+
if (!session) return;
|
|
2406
|
+
const teammateName = hookData["teammate_name"] ?? "unknown";
|
|
2407
|
+
const teamName = hookData["team_name"] ?? "unknown";
|
|
2408
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2409
|
+
event_id: crypto.randomUUID(),
|
|
2410
|
+
session_id: session.sessionId,
|
|
2411
|
+
event_type: "agent.team",
|
|
2412
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2413
|
+
content: `Teammate idle: ${teammateName} in ${teamName}`,
|
|
2414
|
+
tool: "claude-code",
|
|
2415
|
+
metadata: { ...hookData }
|
|
2416
|
+
}]);
|
|
2417
|
+
} catch {
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
var taskCompletedCommand = new Command13("task-completed").description("Hook: called when a task completes (TaskCompleted)").action(async () => {
|
|
2421
|
+
try {
|
|
2422
|
+
const hookData = await readStdin();
|
|
2423
|
+
const claudeSessionId = hookData["session_id"];
|
|
2424
|
+
const session = readSessionFile(claudeSessionId);
|
|
2425
|
+
if (!session) return;
|
|
2426
|
+
const taskId = hookData["task_id"] ?? "?";
|
|
2427
|
+
const taskSubject = hookData["task_subject"] ?? "";
|
|
2428
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2429
|
+
event_id: crypto.randomUUID(),
|
|
2430
|
+
session_id: session.sessionId,
|
|
2431
|
+
event_type: "task.completed",
|
|
2432
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2433
|
+
content: `Task completed: #${taskId} \u2014 ${truncateContent(taskSubject, 150)}`,
|
|
2434
|
+
tool: "claude-code",
|
|
2435
|
+
metadata: { ...hookData }
|
|
2436
|
+
}]);
|
|
2437
|
+
} catch {
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
var setupHookCommand = new Command13("setup-hook").description("Hook: called during setup (Setup)").action(async () => {
|
|
2441
|
+
try {
|
|
2442
|
+
const hookData = await readStdin();
|
|
2443
|
+
const claudeSessionId = hookData["session_id"];
|
|
2444
|
+
const session = readSessionFile(claudeSessionId);
|
|
2445
|
+
if (!session) return;
|
|
2446
|
+
const source = hookData["source"] ?? "unknown";
|
|
2447
|
+
await sendEvents(session.endpoint, session.apiKey, [{
|
|
2448
|
+
event_id: crypto.randomUUID(),
|
|
2449
|
+
session_id: session.sessionId,
|
|
2450
|
+
event_type: "setup",
|
|
2451
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2452
|
+
content: `Setup: ${source}`,
|
|
2453
|
+
tool: "claude-code",
|
|
2454
|
+
metadata: { ...hookData }
|
|
2455
|
+
}]);
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
var hookCommand = new Command13("hook").description("Claude Code hook handlers for Valt session tracking").addCommand(sessionStartCommand).addCommand(toolCallCommand).addCommand(postToolUseCommand).addCommand(toolErrorCommand).addCommand(subagentStartCommand).addCommand(subagentStopCommand).addCommand(promptSubmitCommand).addCommand(sessionEndCommand).addCommand(stopCommand).addCommand(stopFailureCommand).addCommand(compactCommand).addCommand(notificationCommand).addCommand(permissionRequestCommand).addCommand(configLoadedCommand).addCommand(configChangeCommand).addCommand(elicitationCommand).addCommand(elicitationResultCommand).addCommand(worktreeCreateCommand).addCommand(worktreeRemoveCommand).addCommand(teammateIdleCommand).addCommand(taskCompletedCommand).addCommand(setupHookCommand);
|
|
1786
2460
|
|
|
1787
2461
|
// src/commands/setup.ts
|
|
1788
2462
|
import { Command as Command14 } from "commander";
|
|
1789
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as
|
|
1790
|
-
import { join as join2 } from "path";
|
|
2463
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
2464
|
+
import { join as join2, dirname } from "path";
|
|
2465
|
+
import { fileURLToPath } from "url";
|
|
1791
2466
|
import os3 from "os";
|
|
2467
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2468
|
+
var pkgVersion = JSON.parse(readFileSync4(join2(__dirname, "../../package.json"), "utf-8")).version;
|
|
1792
2469
|
var CLAUDE_DIR = join2(os3.homedir(), ".claude");
|
|
1793
2470
|
var SETTINGS_FILE = join2(CLAUDE_DIR, "settings.json");
|
|
1794
2471
|
var LEGACY_HOOKS_FILE = join2(CLAUDE_DIR, "hooks.json");
|
|
1795
|
-
var HOOK_PREFIX =
|
|
2472
|
+
var HOOK_PREFIX = `npx --yes @usevalt/cli@${pkgVersion}`;
|
|
1796
2473
|
function getValtHooks() {
|
|
1797
2474
|
return {
|
|
2475
|
+
Setup: [
|
|
2476
|
+
{
|
|
2477
|
+
matcher: "*",
|
|
2478
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook setup-hook` }]
|
|
2479
|
+
}
|
|
2480
|
+
],
|
|
1798
2481
|
SessionStart: [
|
|
1799
2482
|
{
|
|
1800
|
-
hooks: [
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2483
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook session-start` }]
|
|
2484
|
+
}
|
|
2485
|
+
],
|
|
2486
|
+
InstructionsLoaded: [
|
|
2487
|
+
{
|
|
2488
|
+
matcher: "*",
|
|
2489
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook config-loaded` }]
|
|
2490
|
+
}
|
|
2491
|
+
],
|
|
2492
|
+
UserPromptSubmit: [
|
|
2493
|
+
{
|
|
2494
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook prompt-submit` }]
|
|
1806
2495
|
}
|
|
1807
2496
|
],
|
|
1808
2497
|
PreToolUse: [
|
|
1809
2498
|
{
|
|
1810
2499
|
matcher: "*",
|
|
1811
|
-
hooks: [
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2500
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook tool-call` }]
|
|
2501
|
+
}
|
|
2502
|
+
],
|
|
2503
|
+
PermissionRequest: [
|
|
2504
|
+
{
|
|
2505
|
+
matcher: "*",
|
|
2506
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook permission-request` }]
|
|
1817
2507
|
}
|
|
1818
2508
|
],
|
|
1819
2509
|
PostToolUse: [
|
|
1820
2510
|
{
|
|
1821
2511
|
matcher: "*",
|
|
1822
|
-
hooks: [
|
|
1823
|
-
{
|
|
1824
|
-
type: "command",
|
|
1825
|
-
command: `${HOOK_PREFIX} hook post-tool-use`
|
|
1826
|
-
}
|
|
1827
|
-
]
|
|
2512
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook post-tool-use` }]
|
|
1828
2513
|
}
|
|
1829
2514
|
],
|
|
1830
2515
|
PostToolUseFailure: [
|
|
1831
2516
|
{
|
|
1832
2517
|
matcher: "*",
|
|
1833
|
-
hooks: [
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2518
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook tool-error` }]
|
|
2519
|
+
}
|
|
2520
|
+
],
|
|
2521
|
+
Notification: [
|
|
2522
|
+
{
|
|
2523
|
+
matcher: "*",
|
|
2524
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook notification` }]
|
|
1839
2525
|
}
|
|
1840
2526
|
],
|
|
1841
2527
|
SubagentStart: [
|
|
1842
2528
|
{
|
|
1843
|
-
hooks: [
|
|
1844
|
-
{
|
|
1845
|
-
type: "command",
|
|
1846
|
-
command: `${HOOK_PREFIX} hook subagent-start`
|
|
1847
|
-
}
|
|
1848
|
-
]
|
|
2529
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook subagent-start` }]
|
|
1849
2530
|
}
|
|
1850
2531
|
],
|
|
1851
2532
|
SubagentStop: [
|
|
1852
2533
|
{
|
|
1853
|
-
hooks: [
|
|
1854
|
-
{
|
|
1855
|
-
type: "command",
|
|
1856
|
-
command: `${HOOK_PREFIX} hook subagent-stop`
|
|
1857
|
-
}
|
|
1858
|
-
]
|
|
2534
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook subagent-stop` }]
|
|
1859
2535
|
}
|
|
1860
2536
|
],
|
|
1861
|
-
|
|
2537
|
+
Stop: [
|
|
1862
2538
|
{
|
|
1863
|
-
hooks: [
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2539
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook stop` }]
|
|
2540
|
+
}
|
|
2541
|
+
],
|
|
2542
|
+
StopFailure: [
|
|
2543
|
+
{
|
|
2544
|
+
matcher: "*",
|
|
2545
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook stop-failure` }]
|
|
2546
|
+
}
|
|
2547
|
+
],
|
|
2548
|
+
TeammateIdle: [
|
|
2549
|
+
{
|
|
2550
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook teammate-idle` }]
|
|
2551
|
+
}
|
|
2552
|
+
],
|
|
2553
|
+
TaskCompleted: [
|
|
2554
|
+
{
|
|
2555
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook task-completed` }]
|
|
2556
|
+
}
|
|
2557
|
+
],
|
|
2558
|
+
ConfigChange: [
|
|
2559
|
+
{
|
|
2560
|
+
matcher: "*",
|
|
2561
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook config-change` }]
|
|
2562
|
+
}
|
|
2563
|
+
],
|
|
2564
|
+
WorktreeCreate: [
|
|
2565
|
+
{
|
|
2566
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook worktree-create` }]
|
|
2567
|
+
}
|
|
2568
|
+
],
|
|
2569
|
+
WorktreeRemove: [
|
|
2570
|
+
{
|
|
2571
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook worktree-remove` }]
|
|
2572
|
+
}
|
|
2573
|
+
],
|
|
2574
|
+
PreCompact: [
|
|
2575
|
+
{
|
|
2576
|
+
matcher: "*",
|
|
2577
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook compact` }]
|
|
2578
|
+
}
|
|
2579
|
+
],
|
|
2580
|
+
PostCompact: [
|
|
2581
|
+
{
|
|
2582
|
+
matcher: "*",
|
|
2583
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook compact` }]
|
|
2584
|
+
}
|
|
2585
|
+
],
|
|
2586
|
+
Elicitation: [
|
|
2587
|
+
{
|
|
2588
|
+
matcher: "*",
|
|
2589
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook elicitation` }]
|
|
2590
|
+
}
|
|
2591
|
+
],
|
|
2592
|
+
ElicitationResult: [
|
|
2593
|
+
{
|
|
2594
|
+
matcher: "*",
|
|
2595
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook elicitation-result` }]
|
|
1869
2596
|
}
|
|
1870
2597
|
],
|
|
1871
2598
|
SessionEnd: [
|
|
1872
2599
|
{
|
|
1873
2600
|
matcher: "*",
|
|
1874
|
-
hooks: [
|
|
1875
|
-
{
|
|
1876
|
-
type: "command",
|
|
1877
|
-
command: `${HOOK_PREFIX} hook session-end`,
|
|
1878
|
-
timeout: 1e4
|
|
1879
|
-
}
|
|
1880
|
-
]
|
|
2601
|
+
hooks: [{ type: "command", command: `${HOOK_PREFIX} hook session-end`, timeout: 15e3 }]
|
|
1881
2602
|
}
|
|
1882
2603
|
]
|
|
1883
2604
|
};
|
|
@@ -1889,7 +2610,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
|
|
|
1889
2610
|
error("No API key found. Run `valt login` first.");
|
|
1890
2611
|
process.exit(1);
|
|
1891
2612
|
}
|
|
1892
|
-
if (!
|
|
2613
|
+
if (!existsSync5(CLAUDE_DIR)) {
|
|
1893
2614
|
mkdirSync2(CLAUDE_DIR, { recursive: true });
|
|
1894
2615
|
}
|
|
1895
2616
|
if (opts.remove) {
|
|
@@ -1897,7 +2618,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
|
|
|
1897
2618
|
return;
|
|
1898
2619
|
}
|
|
1899
2620
|
let config = {};
|
|
1900
|
-
if (
|
|
2621
|
+
if (existsSync5(SETTINGS_FILE)) {
|
|
1901
2622
|
try {
|
|
1902
2623
|
const raw = readFileSync4(SETTINGS_FILE, "utf-8");
|
|
1903
2624
|
config = JSON.parse(raw);
|
|
@@ -1923,7 +2644,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
|
|
|
1923
2644
|
}
|
|
1924
2645
|
}
|
|
1925
2646
|
writeFileSync4(SETTINGS_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1926
|
-
if (
|
|
2647
|
+
if (existsSync5(LEGACY_HOOKS_FILE)) {
|
|
1927
2648
|
try {
|
|
1928
2649
|
removeLegacyHooks();
|
|
1929
2650
|
info(`Migrated hooks from legacy hooks.json to settings.json.`);
|
|
@@ -1960,7 +2681,7 @@ function removeValtHooksFromConfig(config) {
|
|
|
1960
2681
|
}
|
|
1961
2682
|
}
|
|
1962
2683
|
function removeLegacyHooks() {
|
|
1963
|
-
if (!
|
|
2684
|
+
if (!existsSync5(LEGACY_HOOKS_FILE)) return;
|
|
1964
2685
|
try {
|
|
1965
2686
|
const raw = readFileSync4(LEGACY_HOOKS_FILE, "utf-8");
|
|
1966
2687
|
const config = JSON.parse(raw);
|
|
@@ -1979,7 +2700,7 @@ function removeLegacyHooks() {
|
|
|
1979
2700
|
}
|
|
1980
2701
|
}
|
|
1981
2702
|
function removeHooks() {
|
|
1982
|
-
if (
|
|
2703
|
+
if (existsSync5(SETTINGS_FILE)) {
|
|
1983
2704
|
try {
|
|
1984
2705
|
const raw = readFileSync4(SETTINGS_FILE, "utf-8");
|
|
1985
2706
|
const config = JSON.parse(raw);
|
|
@@ -1991,14 +2712,14 @@ function removeHooks() {
|
|
|
1991
2712
|
process.exit(1);
|
|
1992
2713
|
}
|
|
1993
2714
|
}
|
|
1994
|
-
if (
|
|
2715
|
+
if (existsSync5(LEGACY_HOOKS_FILE)) {
|
|
1995
2716
|
try {
|
|
1996
2717
|
removeLegacyHooks();
|
|
1997
2718
|
info("Legacy hooks.json cleaned up.");
|
|
1998
2719
|
} catch {
|
|
1999
2720
|
}
|
|
2000
2721
|
}
|
|
2001
|
-
if (!
|
|
2722
|
+
if (!existsSync5(SETTINGS_FILE) && !existsSync5(LEGACY_HOOKS_FILE)) {
|
|
2002
2723
|
info("No hooks configured. Nothing to remove.");
|
|
2003
2724
|
}
|
|
2004
2725
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usevalt/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Valt CLI — trust layer for AI-assisted development",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"tsup": "^8.4.0",
|
|
40
40
|
"typescript": "^5.7.0",
|
|
41
41
|
"vitest": "^3.2.0",
|
|
42
|
-
"@usevalt/
|
|
43
|
-
"@usevalt/
|
|
42
|
+
"@usevalt/eslint-config": "0.0.0",
|
|
43
|
+
"@usevalt/typescript-config": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsup",
|