@usevalt/cli 0.7.3 → 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +886 -76
  2. 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 existsSync3, readdirSync } from "fs";
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 (existsSync3(path)) {
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 (!existsSync3(transcriptPath)) return result;
1602
+ if (!existsSync4(transcriptPath)) return result;
1276
1603
  try {
1277
- const rl = createInterface({
1278
- input: createReadStream(transcriptPath, "utf-8"),
1604
+ const rl = createInterface2({
1605
+ input: createReadStream2(transcriptPath, "utf-8"),
1279
1606
  crlfDelay: Infinity
1280
1607
  });
1281
1608
  for await (const line of rl) {
@@ -1311,6 +1638,51 @@ async function parseTranscript(transcriptPath) {
1311
1638
  }
1312
1639
  return result;
1313
1640
  }
1641
+ function parseTranscriptIncremental(transcriptPath, fromByteOffset, model) {
1642
+ const result = {
1643
+ deltaPromptTokens: 0,
1644
+ deltaCompletionTokens: 0,
1645
+ deltaCacheReadTokens: 0,
1646
+ deltaCostUsd: 0,
1647
+ newByteOffset: fromByteOffset
1648
+ };
1649
+ try {
1650
+ const fileSize = statSync(transcriptPath).size;
1651
+ if (fileSize <= fromByteOffset) return result;
1652
+ const bytesToRead = fileSize - fromByteOffset;
1653
+ const buffer = Buffer.alloc(bytesToRead);
1654
+ const fd = openSync(transcriptPath, "r");
1655
+ try {
1656
+ readSync(fd, buffer, 0, bytesToRead, fromByteOffset);
1657
+ } finally {
1658
+ closeSync(fd);
1659
+ }
1660
+ const chunk = buffer.toString("utf-8");
1661
+ const lines = chunk.split("\n");
1662
+ for (const line of lines) {
1663
+ if (!line.trim()) continue;
1664
+ try {
1665
+ const entry = JSON.parse(line);
1666
+ const usage = entry.message?.usage ?? entry.usage;
1667
+ if (usage) {
1668
+ result.deltaPromptTokens += usage.input_tokens ?? 0;
1669
+ result.deltaCompletionTokens += usage.output_tokens ?? 0;
1670
+ result.deltaCacheReadTokens += usage.cache_read_input_tokens ?? 0;
1671
+ }
1672
+ } catch {
1673
+ }
1674
+ }
1675
+ result.newByteOffset = fileSize;
1676
+ result.deltaCostUsd = calculateCost(
1677
+ model,
1678
+ result.deltaPromptTokens,
1679
+ result.deltaCompletionTokens,
1680
+ result.deltaCacheReadTokens
1681
+ );
1682
+ } catch {
1683
+ }
1684
+ return result;
1685
+ }
1314
1686
  var sessionStartCommand = new Command13("session-start").description("Hook: called when a Claude Code session starts").action(async () => {
1315
1687
  try {
1316
1688
  const { apiKey, endpoint, apiEndpoint } = resolveConfig();
@@ -1322,6 +1694,7 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
1322
1694
  const model = hookData["model"] || process.env["CLAUDE_MODEL"] || "unknown";
1323
1695
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1324
1696
  const cwd = hookData["cwd"] || process.cwd();
1697
+ const transcriptPath = hookData["transcript_path"] || void 0;
1325
1698
  const state = {
1326
1699
  sessionId,
1327
1700
  claudeSessionId,
@@ -1331,7 +1704,8 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
1331
1704
  apiEndpoint,
1332
1705
  projectSlug,
1333
1706
  model,
1334
- cwd
1707
+ cwd,
1708
+ transcriptPath
1335
1709
  };
1336
1710
  writeFileSync3(getSessionFile(claudeSessionId), JSON.stringify(state, null, 2), { mode: 384 });
1337
1711
  let gitCommitBefore;
@@ -1350,6 +1724,7 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
1350
1724
  session_id: sessionId,
1351
1725
  event_type: "session.start",
1352
1726
  timestamp: startedAt,
1727
+ content: `Session started \u2014 ${model} on ${projectSlug}`,
1353
1728
  tool: "claude-code",
1354
1729
  model,
1355
1730
  metadata: {
@@ -1421,6 +1796,8 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
1421
1796
  }
1422
1797
  }
1423
1798
  const { eventType, filePath, command } = classifyToolCall(toolName, toolInput);
1799
+ const contentSummary = summarizeToolInput(toolName, toolInput);
1800
+ const toolInputSummaryMeta = JSON.stringify(toolInput).slice(0, 1024);
1424
1801
  const events = [];
1425
1802
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1426
1803
  events.push({
@@ -1428,20 +1805,23 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
1428
1805
  session_id: session.sessionId,
1429
1806
  event_type: "tool.call",
1430
1807
  timestamp,
1808
+ content: contentSummary,
1431
1809
  tool: "claude-code",
1432
1810
  tool_name: toolName,
1433
1811
  metadata: {
1434
1812
  tool_name: toolName,
1435
1813
  file_path: filePath,
1436
- command
1814
+ command,
1815
+ tool_input_summary: toolInputSummaryMeta
1437
1816
  }
1438
1817
  });
1439
1818
  if (eventType !== "tool.call") {
1440
- events.push({
1819
+ const classifiedEvent = {
1441
1820
  event_id: crypto.randomUUID(),
1442
1821
  session_id: session.sessionId,
1443
1822
  event_type: eventType,
1444
1823
  timestamp,
1824
+ content: contentSummary,
1445
1825
  tool: "claude-code",
1446
1826
  tool_name: toolName,
1447
1827
  file_path: filePath,
@@ -1450,7 +1830,11 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
1450
1830
  ...filePath ? { file_path: filePath } : {},
1451
1831
  ...command ? { command } : {}
1452
1832
  }
1453
- });
1833
+ };
1834
+ if (eventType === "command.execute" && command) {
1835
+ classifiedEvent["command_category"] = classifyCommand(command);
1836
+ }
1837
+ events.push(classifiedEvent);
1454
1838
  }
1455
1839
  await sendEvents(session.endpoint, session.apiKey, events);
1456
1840
  } catch {
@@ -1463,19 +1847,24 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
1463
1847
  const session = readSessionFile(claudeSessionId);
1464
1848
  if (!session) return;
1465
1849
  const toolName = hookData["tool_name"] ?? "unknown";
1850
+ const toolInput = hookData["tool_input"] ?? {};
1466
1851
  const toolResponse = hookData["tool_response"];
1467
1852
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1853
+ const resultContent = summarizeToolResponse(toolName, toolInput, toolResponse ?? {});
1854
+ const toolResponseSummaryMeta = JSON.stringify(toolResponse ?? {}).slice(0, 1024);
1468
1855
  const events = [{
1469
1856
  event_id: crypto.randomUUID(),
1470
1857
  session_id: session.sessionId,
1471
1858
  event_type: "tool.result",
1472
1859
  timestamp,
1860
+ content: resultContent,
1473
1861
  tool: "claude-code",
1474
1862
  tool_name: toolName,
1475
1863
  metadata: {
1476
1864
  tool_name: toolName,
1477
1865
  exit_code: toolResponse?.["exit_code"],
1478
- success: toolResponse?.["success"] ?? true
1866
+ success: toolResponse?.["success"] ?? true,
1867
+ tool_response_summary: toolResponseSummaryMeta
1479
1868
  }
1480
1869
  }];
1481
1870
  if (toolName === "Bash" && toolResponse) {
@@ -1483,11 +1872,13 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
1483
1872
  if (stdout) {
1484
1873
  const testResults = parseTestResults(stdout);
1485
1874
  if (testResults) {
1875
+ const testContent = `Tests: ${testResults.testsPassed}/${testResults.testsRun} passed (${testResults.framework}) ${testResults.testsFailed === 0 ? "PASS" : "FAIL"}`;
1486
1876
  events.push({
1487
1877
  event_id: crypto.randomUUID(),
1488
1878
  session_id: session.sessionId,
1489
1879
  event_type: "test.run",
1490
1880
  timestamp,
1881
+ content: testContent,
1491
1882
  tool: "claude-code",
1492
1883
  metadata: {
1493
1884
  tests_run: testResults.testsRun,
@@ -1500,6 +1891,45 @@ var postToolUseCommand = new Command13("post-tool-use").description("Hook: calle
1500
1891
  }
1501
1892
  }
1502
1893
  }
1894
+ try {
1895
+ const tPath = hookData["transcript_path"] ?? session.transcriptPath;
1896
+ if (tPath && existsSync4(tPath)) {
1897
+ if (!session.transcriptPath) session.transcriptPath = tPath;
1898
+ const deltas = parseTranscriptIncremental(
1899
+ tPath,
1900
+ session.transcriptByteOffset ?? 0,
1901
+ session.model ?? "unknown"
1902
+ );
1903
+ if (deltas.deltaPromptTokens > 0 || deltas.deltaCompletionTokens > 0) {
1904
+ events.push({
1905
+ event_id: crypto.randomUUID(),
1906
+ session_id: session.sessionId,
1907
+ event_type: "cost",
1908
+ timestamp,
1909
+ tool: "claude-code",
1910
+ tokens_prompt: deltas.deltaPromptTokens,
1911
+ tokens_completion: deltas.deltaCompletionTokens,
1912
+ cost_usd: deltas.deltaCostUsd,
1913
+ metadata: {
1914
+ model: session.model,
1915
+ source: "incremental",
1916
+ cache_read_tokens: deltas.deltaCacheReadTokens
1917
+ }
1918
+ });
1919
+ }
1920
+ session.transcriptByteOffset = deltas.newByteOffset;
1921
+ session.runningPromptTokens = (session.runningPromptTokens ?? 0) + deltas.deltaPromptTokens;
1922
+ session.runningCompletionTokens = (session.runningCompletionTokens ?? 0) + deltas.deltaCompletionTokens;
1923
+ session.runningCacheReadTokens = (session.runningCacheReadTokens ?? 0) + deltas.deltaCacheReadTokens;
1924
+ session.runningCostUsd = (session.runningCostUsd ?? 0) + deltas.deltaCostUsd;
1925
+ writeFileSync3(
1926
+ getSessionFile(session.claudeSessionId),
1927
+ JSON.stringify(session, null, 2),
1928
+ { mode: 384 }
1929
+ );
1930
+ }
1931
+ } catch {
1932
+ }
1503
1933
  await sendEvents(session.endpoint, session.apiKey, events);
1504
1934
  } catch {
1505
1935
  }
@@ -1518,6 +1948,7 @@ var toolErrorCommand = new Command13("tool-error").description("Hook: called whe
1518
1948
  session_id: session.sessionId,
1519
1949
  event_type: "tool.error",
1520
1950
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1951
+ content: `Error: ${toolName} \u2014 ${truncateContent(errorMsg, 200)}`,
1521
1952
  tool: "claude-code",
1522
1953
  tool_name: toolName,
1523
1954
  metadata: {
@@ -1535,15 +1966,17 @@ var subagentStartCommand = new Command13("subagent-start").description("Hook: ca
1535
1966
  const claudeSessionId = hookData["session_id"];
1536
1967
  const session = readSessionFile(claudeSessionId);
1537
1968
  if (!session) return;
1969
+ const agentType = hookData["agent_type"] ?? "unknown";
1538
1970
  await sendEvents(session.endpoint, session.apiKey, [{
1539
1971
  event_id: crypto.randomUUID(),
1540
1972
  session_id: session.sessionId,
1541
1973
  event_type: "subagent.start",
1542
1974
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1975
+ content: `Subagent started: ${agentType}`,
1543
1976
  tool: "claude-code",
1544
1977
  metadata: {
1545
1978
  agent_id: hookData["agent_id"],
1546
- agent_type: hookData["agent_type"]
1979
+ agent_type: agentType
1547
1980
  }
1548
1981
  }]);
1549
1982
  } catch {
@@ -1555,15 +1988,17 @@ var subagentStopCommand = new Command13("subagent-stop").description("Hook: call
1555
1988
  const claudeSessionId = hookData["session_id"];
1556
1989
  const session = readSessionFile(claudeSessionId);
1557
1990
  if (!session) return;
1991
+ const stopAgentType = hookData["agent_type"] ?? "unknown";
1558
1992
  await sendEvents(session.endpoint, session.apiKey, [{
1559
1993
  event_id: crypto.randomUUID(),
1560
1994
  session_id: session.sessionId,
1561
1995
  event_type: "subagent.stop",
1562
1996
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1997
+ content: `Subagent stopped: ${stopAgentType}`,
1563
1998
  tool: "claude-code",
1564
1999
  metadata: {
1565
2000
  agent_id: hookData["agent_id"],
1566
- agent_type: hookData["agent_type"],
2001
+ agent_type: stopAgentType,
1567
2002
  transcript_path: hookData["agent_transcript_path"]
1568
2003
  }
1569
2004
  }]);
@@ -1583,6 +2018,7 @@ var promptSubmitCommand = new Command13("prompt-submit").description("Hook: call
1583
2018
  session_id: session.sessionId,
1584
2019
  event_type: "prompt.submit",
1585
2020
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2021
+ content: `Prompt submitted (${promptText.length} chars)`,
1586
2022
  tool: "claude-code",
1587
2023
  metadata: {
1588
2024
  prompt_hash: promptHashValue,
@@ -1626,6 +2062,12 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
1626
2062
  const durationMs = now.getTime() - startedAt.getTime();
1627
2063
  const transcriptPath = hookData["transcript_path"];
1628
2064
  const metrics = transcriptPath ? await parseTranscript(transcriptPath) : null;
2065
+ if (metrics && (session.runningPromptTokens || session.runningCostUsd)) {
2066
+ metrics.promptTokens = Math.max(0, metrics.promptTokens - (session.runningPromptTokens ?? 0));
2067
+ metrics.completionTokens = Math.max(0, metrics.completionTokens - (session.runningCompletionTokens ?? 0));
2068
+ metrics.cacheReadTokens = Math.max(0, metrics.cacheReadTokens - (session.runningCacheReadTokens ?? 0));
2069
+ metrics.totalCostUsd = Math.max(0, metrics.totalCostUsd - (session.runningCostUsd ?? 0));
2070
+ }
1629
2071
  let gitCommitAfter;
1630
2072
  try {
1631
2073
  gitCommitAfter = execSync("git rev-parse HEAD", {
@@ -1636,12 +2078,15 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
1636
2078
  }).trim();
1637
2079
  } catch {
1638
2080
  }
2081
+ const durationSec = Math.round(durationMs / 1e3);
2082
+ const costStr = metrics?.totalCostUsd ? `$${metrics.totalCostUsd.toFixed(4)}` : "$0";
1639
2083
  const events = [
1640
2084
  {
1641
2085
  event_id: crypto.randomUUID(),
1642
2086
  session_id: session.sessionId,
1643
2087
  event_type: "session.end",
1644
2088
  timestamp: now.toISOString(),
2089
+ content: `Session ended \u2014 ${durationSec}s, ${costStr}`,
1645
2090
  duration_ms: durationMs,
1646
2091
  tool: "claude-code",
1647
2092
  model: metrics?.model ?? session.model,
@@ -1660,11 +2105,13 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
1660
2105
  }
1661
2106
  ];
1662
2107
  if (metrics && (metrics.totalCostUsd > 0 || metrics.promptTokens > 0)) {
2108
+ const totalTokens = metrics.promptTokens + metrics.completionTokens;
1663
2109
  events.push({
1664
2110
  event_id: crypto.randomUUID(),
1665
2111
  session_id: session.sessionId,
1666
2112
  event_type: "cost",
1667
2113
  timestamp: now.toISOString(),
2114
+ content: `Cost: $${metrics.totalCostUsd.toFixed(4)} (${totalTokens} tokens)`,
1668
2115
  tool: "claude-code",
1669
2116
  tokens_prompt: metrics.promptTokens,
1670
2117
  tokens_completion: metrics.completionTokens,
@@ -1678,23 +2125,342 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
1678
2125
  }
1679
2126
  sendEvents(session.endpoint, session.apiKey, events).catch(() => {
1680
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
+ }
1681
2161
  if (sessionFilePath) {
1682
2162
  try {
1683
2163
  unlinkSync2(sessionFilePath);
1684
2164
  } catch {
1685
2165
  }
1686
2166
  }
1687
- const costStr = metrics?.totalCostUsd ? ` $${metrics.totalCostUsd.toFixed(4)}` : "";
1688
- const tokenStr = metrics?.promptTokens ? ` ${metrics.promptTokens + metrics.completionTokens} tokens` : "";
1689
- success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s${costStr}${tokenStr})`);
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})`);
2170
+ } catch {
2171
+ }
2172
+ });
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
+ }]);
1690
2209
  } catch {
1691
2210
  }
1692
2211
  });
1693
- 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);
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);
1694
2460
 
1695
2461
  // src/commands/setup.ts
1696
2462
  import { Command as Command14 } from "commander";
1697
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
2463
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync5, unlinkSync as unlinkSync3 } from "fs";
1698
2464
  import { join as join2 } from "path";
1699
2465
  import os3 from "os";
1700
2466
  var CLAUDE_DIR = join2(os3.homedir(), ".claude");
@@ -1703,89 +2469,133 @@ var LEGACY_HOOKS_FILE = join2(CLAUDE_DIR, "hooks.json");
1703
2469
  var HOOK_PREFIX = "npx --yes @usevalt/cli";
1704
2470
  function getValtHooks() {
1705
2471
  return {
2472
+ Setup: [
2473
+ {
2474
+ matcher: "*",
2475
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook setup-hook` }]
2476
+ }
2477
+ ],
1706
2478
  SessionStart: [
1707
2479
  {
1708
- hooks: [
1709
- {
1710
- type: "command",
1711
- command: `${HOOK_PREFIX} hook session-start`
1712
- }
1713
- ]
2480
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook session-start` }]
2481
+ }
2482
+ ],
2483
+ InstructionsLoaded: [
2484
+ {
2485
+ matcher: "*",
2486
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook config-loaded` }]
2487
+ }
2488
+ ],
2489
+ UserPromptSubmit: [
2490
+ {
2491
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook prompt-submit` }]
1714
2492
  }
1715
2493
  ],
1716
2494
  PreToolUse: [
1717
2495
  {
1718
2496
  matcher: "*",
1719
- hooks: [
1720
- {
1721
- type: "command",
1722
- command: `${HOOK_PREFIX} hook tool-call`
1723
- }
1724
- ]
2497
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook tool-call` }]
2498
+ }
2499
+ ],
2500
+ PermissionRequest: [
2501
+ {
2502
+ matcher: "*",
2503
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook permission-request` }]
1725
2504
  }
1726
2505
  ],
1727
2506
  PostToolUse: [
1728
2507
  {
1729
2508
  matcher: "*",
1730
- hooks: [
1731
- {
1732
- type: "command",
1733
- command: `${HOOK_PREFIX} hook post-tool-use`
1734
- }
1735
- ]
2509
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook post-tool-use` }]
1736
2510
  }
1737
2511
  ],
1738
2512
  PostToolUseFailure: [
1739
2513
  {
1740
2514
  matcher: "*",
1741
- hooks: [
1742
- {
1743
- type: "command",
1744
- command: `${HOOK_PREFIX} hook tool-error`
1745
- }
1746
- ]
2515
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook tool-error` }]
2516
+ }
2517
+ ],
2518
+ Notification: [
2519
+ {
2520
+ matcher: "*",
2521
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook notification` }]
1747
2522
  }
1748
2523
  ],
1749
2524
  SubagentStart: [
1750
2525
  {
1751
- hooks: [
1752
- {
1753
- type: "command",
1754
- command: `${HOOK_PREFIX} hook subagent-start`
1755
- }
1756
- ]
2526
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook subagent-start` }]
1757
2527
  }
1758
2528
  ],
1759
2529
  SubagentStop: [
1760
2530
  {
1761
- hooks: [
1762
- {
1763
- type: "command",
1764
- command: `${HOOK_PREFIX} hook subagent-stop`
1765
- }
1766
- ]
2531
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook subagent-stop` }]
1767
2532
  }
1768
2533
  ],
1769
- UserPromptSubmit: [
2534
+ Stop: [
1770
2535
  {
1771
- hooks: [
1772
- {
1773
- type: "command",
1774
- command: `${HOOK_PREFIX} hook prompt-submit`
1775
- }
1776
- ]
2536
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook stop` }]
2537
+ }
2538
+ ],
2539
+ StopFailure: [
2540
+ {
2541
+ matcher: "*",
2542
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook stop-failure` }]
2543
+ }
2544
+ ],
2545
+ TeammateIdle: [
2546
+ {
2547
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook teammate-idle` }]
2548
+ }
2549
+ ],
2550
+ TaskCompleted: [
2551
+ {
2552
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook task-completed` }]
2553
+ }
2554
+ ],
2555
+ ConfigChange: [
2556
+ {
2557
+ matcher: "*",
2558
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook config-change` }]
2559
+ }
2560
+ ],
2561
+ WorktreeCreate: [
2562
+ {
2563
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook worktree-create` }]
2564
+ }
2565
+ ],
2566
+ WorktreeRemove: [
2567
+ {
2568
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook worktree-remove` }]
2569
+ }
2570
+ ],
2571
+ PreCompact: [
2572
+ {
2573
+ matcher: "*",
2574
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook compact` }]
2575
+ }
2576
+ ],
2577
+ PostCompact: [
2578
+ {
2579
+ matcher: "*",
2580
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook compact` }]
2581
+ }
2582
+ ],
2583
+ Elicitation: [
2584
+ {
2585
+ matcher: "*",
2586
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook elicitation` }]
2587
+ }
2588
+ ],
2589
+ ElicitationResult: [
2590
+ {
2591
+ matcher: "*",
2592
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook elicitation-result` }]
1777
2593
  }
1778
2594
  ],
1779
2595
  SessionEnd: [
1780
2596
  {
1781
2597
  matcher: "*",
1782
- hooks: [
1783
- {
1784
- type: "command",
1785
- command: `${HOOK_PREFIX} hook session-end`,
1786
- timeout: 1e4
1787
- }
1788
- ]
2598
+ hooks: [{ type: "command", command: `${HOOK_PREFIX} hook session-end`, timeout: 15e3 }]
1789
2599
  }
1790
2600
  ]
1791
2601
  };
@@ -1797,7 +2607,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
1797
2607
  error("No API key found. Run `valt login` first.");
1798
2608
  process.exit(1);
1799
2609
  }
1800
- if (!existsSync4(CLAUDE_DIR)) {
2610
+ if (!existsSync5(CLAUDE_DIR)) {
1801
2611
  mkdirSync2(CLAUDE_DIR, { recursive: true });
1802
2612
  }
1803
2613
  if (opts.remove) {
@@ -1805,7 +2615,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
1805
2615
  return;
1806
2616
  }
1807
2617
  let config = {};
1808
- if (existsSync4(SETTINGS_FILE)) {
2618
+ if (existsSync5(SETTINGS_FILE)) {
1809
2619
  try {
1810
2620
  const raw = readFileSync4(SETTINGS_FILE, "utf-8");
1811
2621
  config = JSON.parse(raw);
@@ -1831,7 +2641,7 @@ var setupCommand = new Command14("setup").description("Configure Claude Code hoo
1831
2641
  }
1832
2642
  }
1833
2643
  writeFileSync4(SETTINGS_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
1834
- if (existsSync4(LEGACY_HOOKS_FILE)) {
2644
+ if (existsSync5(LEGACY_HOOKS_FILE)) {
1835
2645
  try {
1836
2646
  removeLegacyHooks();
1837
2647
  info(`Migrated hooks from legacy hooks.json to settings.json.`);
@@ -1868,7 +2678,7 @@ function removeValtHooksFromConfig(config) {
1868
2678
  }
1869
2679
  }
1870
2680
  function removeLegacyHooks() {
1871
- if (!existsSync4(LEGACY_HOOKS_FILE)) return;
2681
+ if (!existsSync5(LEGACY_HOOKS_FILE)) return;
1872
2682
  try {
1873
2683
  const raw = readFileSync4(LEGACY_HOOKS_FILE, "utf-8");
1874
2684
  const config = JSON.parse(raw);
@@ -1887,7 +2697,7 @@ function removeLegacyHooks() {
1887
2697
  }
1888
2698
  }
1889
2699
  function removeHooks() {
1890
- if (existsSync4(SETTINGS_FILE)) {
2700
+ if (existsSync5(SETTINGS_FILE)) {
1891
2701
  try {
1892
2702
  const raw = readFileSync4(SETTINGS_FILE, "utf-8");
1893
2703
  const config = JSON.parse(raw);
@@ -1899,14 +2709,14 @@ function removeHooks() {
1899
2709
  process.exit(1);
1900
2710
  }
1901
2711
  }
1902
- if (existsSync4(LEGACY_HOOKS_FILE)) {
2712
+ if (existsSync5(LEGACY_HOOKS_FILE)) {
1903
2713
  try {
1904
2714
  removeLegacyHooks();
1905
2715
  info("Legacy hooks.json cleaned up.");
1906
2716
  } catch {
1907
2717
  }
1908
2718
  }
1909
- if (!existsSync4(SETTINGS_FILE) && !existsSync4(LEGACY_HOOKS_FILE)) {
2719
+ if (!existsSync5(SETTINGS_FILE) && !existsSync5(LEGACY_HOOKS_FILE)) {
1910
2720
  info("No hooks configured. Nothing to remove.");
1911
2721
  }
1912
2722
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usevalt/cli",
3
- "version": "0.7.3",
3
+ "version": "0.8.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/typescript-config": "0.0.0",
43
- "@usevalt/eslint-config": "0.0.0"
42
+ "@usevalt/eslint-config": "0.0.0",
43
+ "@usevalt/typescript-config": "0.0.0"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsup",