@usevalt/cli 0.3.2 → 0.4.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 +92 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -996,10 +996,27 @@ proxyCommand.command("stop").description("Stop the MCP proxy").action(() => {
|
|
|
996
996
|
// src/commands/hook.ts
|
|
997
997
|
import { Command as Command13 } from "commander";
|
|
998
998
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync3 } from "fs";
|
|
999
|
+
import { createReadStream } from "fs";
|
|
1000
|
+
import { createInterface } from "readline";
|
|
999
1001
|
import { execSync } from "child_process";
|
|
1000
1002
|
import { basename } from "path";
|
|
1001
1003
|
import os2 from "os";
|
|
1002
1004
|
var SESSION_FILE = `${os2.tmpdir()}/valt-session-${process.getuid?.() ?? "default"}.json`;
|
|
1005
|
+
async function readStdin() {
|
|
1006
|
+
if (process.stdin.isTTY) return {};
|
|
1007
|
+
try {
|
|
1008
|
+
const chunks = [];
|
|
1009
|
+
const data = await new Promise((resolve4) => {
|
|
1010
|
+
process.stdin.setEncoding("utf-8");
|
|
1011
|
+
process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
|
|
1012
|
+
process.stdin.on("end", () => resolve4(chunks.join("")));
|
|
1013
|
+
setTimeout(() => resolve4(chunks.join("")), 2e3);
|
|
1014
|
+
});
|
|
1015
|
+
return data.trim() ? JSON.parse(data) : {};
|
|
1016
|
+
} catch {
|
|
1017
|
+
return {};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1003
1020
|
function resolveConfig() {
|
|
1004
1021
|
const apiKey = getApiKey();
|
|
1005
1022
|
if (!apiKey) {
|
|
@@ -1050,13 +1067,50 @@ async function sendEvents(endpoint, apiKey, events) {
|
|
|
1050
1067
|
return false;
|
|
1051
1068
|
}
|
|
1052
1069
|
}
|
|
1070
|
+
async function parseTranscript(transcriptPath) {
|
|
1071
|
+
const result = {
|
|
1072
|
+
totalCostUsd: 0,
|
|
1073
|
+
promptTokens: 0,
|
|
1074
|
+
completionTokens: 0,
|
|
1075
|
+
cacheReadTokens: 0,
|
|
1076
|
+
cacheCreationTokens: 0
|
|
1077
|
+
};
|
|
1078
|
+
if (!existsSync3(transcriptPath)) return result;
|
|
1079
|
+
try {
|
|
1080
|
+
const rl = createInterface({
|
|
1081
|
+
input: createReadStream(transcriptPath, "utf-8"),
|
|
1082
|
+
crlfDelay: Infinity
|
|
1083
|
+
});
|
|
1084
|
+
for await (const line of rl) {
|
|
1085
|
+
if (!line.trim()) continue;
|
|
1086
|
+
try {
|
|
1087
|
+
const entry = JSON.parse(line);
|
|
1088
|
+
if (entry.usage) {
|
|
1089
|
+
result.promptTokens += entry.usage.input_tokens ?? 0;
|
|
1090
|
+
result.completionTokens += entry.usage.output_tokens ?? 0;
|
|
1091
|
+
result.cacheReadTokens += entry.usage.cache_read_input_tokens ?? 0;
|
|
1092
|
+
result.cacheCreationTokens += entry.usage.cache_creation_input_tokens ?? 0;
|
|
1093
|
+
}
|
|
1094
|
+
if (typeof entry.costUSD === "number") {
|
|
1095
|
+
result.totalCostUsd += entry.costUSD;
|
|
1096
|
+
}
|
|
1097
|
+
} catch {
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
} catch {
|
|
1101
|
+
}
|
|
1102
|
+
return result;
|
|
1103
|
+
}
|
|
1053
1104
|
var sessionStartCommand = new Command13("session-start").description("Hook: called when a Claude Code session starts").action(async () => {
|
|
1054
1105
|
try {
|
|
1055
1106
|
const { apiKey, endpoint, apiEndpoint } = resolveConfig();
|
|
1056
1107
|
const projectSlug = detectProjectSlug();
|
|
1108
|
+
const hookData = await readStdin();
|
|
1109
|
+
const claudeSessionId = hookData["session_id"];
|
|
1057
1110
|
const sessionId = crypto.randomUUID();
|
|
1111
|
+
const model = hookData["model"] || process.env["CLAUDE_MODEL"] || "unknown";
|
|
1058
1112
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1059
|
-
const state = { sessionId, startedAt, apiKey, endpoint, apiEndpoint, projectSlug };
|
|
1113
|
+
const state = { sessionId, startedAt, apiKey, endpoint, apiEndpoint, projectSlug, model };
|
|
1060
1114
|
writeFileSync3(SESSION_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1061
1115
|
const sent = await sendEvents(endpoint, apiKey, [
|
|
1062
1116
|
{
|
|
@@ -1065,13 +1119,15 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
|
|
|
1065
1119
|
event_type: "session.start",
|
|
1066
1120
|
timestamp: startedAt,
|
|
1067
1121
|
tool: "claude-code",
|
|
1122
|
+
model,
|
|
1068
1123
|
metadata: {
|
|
1069
1124
|
tool: "claude-code",
|
|
1070
|
-
model
|
|
1125
|
+
model,
|
|
1126
|
+
claude_session_id: claudeSessionId,
|
|
1071
1127
|
repository: process.env["CLAUDE_REPO"],
|
|
1072
1128
|
branch: process.env["CLAUDE_BRANCH"],
|
|
1073
1129
|
project_slug: projectSlug,
|
|
1074
|
-
cwd: process.cwd()
|
|
1130
|
+
cwd: hookData["cwd"] || process.cwd()
|
|
1075
1131
|
}
|
|
1076
1132
|
}
|
|
1077
1133
|
]);
|
|
@@ -1088,24 +1144,7 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
|
|
|
1088
1144
|
try {
|
|
1089
1145
|
const session = readSessionFile();
|
|
1090
1146
|
if (!session) return;
|
|
1091
|
-
|
|
1092
|
-
if (!process.stdin.isTTY) {
|
|
1093
|
-
stdinData = await new Promise((resolve4) => {
|
|
1094
|
-
const chunks = [];
|
|
1095
|
-
process.stdin.setEncoding("utf-8");
|
|
1096
|
-
process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
|
|
1097
|
-
process.stdin.on("end", () => resolve4(chunks.join("")));
|
|
1098
|
-
setTimeout(() => resolve4(chunks.join("")), 1e3);
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
let toolData = {};
|
|
1102
|
-
if (stdinData.trim()) {
|
|
1103
|
-
try {
|
|
1104
|
-
toolData = JSON.parse(stdinData);
|
|
1105
|
-
} catch {
|
|
1106
|
-
toolData = { raw_input: stdinData.trim() };
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1147
|
+
const toolData = await readStdin();
|
|
1109
1148
|
const toolName = toolData["tool_name"] ?? toolData["name"] ?? "unknown";
|
|
1110
1149
|
await sendEvents(session.endpoint, session.apiKey, [
|
|
1111
1150
|
{
|
|
@@ -1125,10 +1164,13 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
|
|
|
1125
1164
|
try {
|
|
1126
1165
|
const session = readSessionFile();
|
|
1127
1166
|
if (!session) return;
|
|
1167
|
+
const hookData = await readStdin();
|
|
1128
1168
|
const now = /* @__PURE__ */ new Date();
|
|
1129
1169
|
const startedAt = new Date(session.startedAt);
|
|
1130
1170
|
const durationMs = now.getTime() - startedAt.getTime();
|
|
1131
|
-
const
|
|
1171
|
+
const transcriptPath = hookData["transcript_path"];
|
|
1172
|
+
const metrics = transcriptPath ? await parseTranscript(transcriptPath) : null;
|
|
1173
|
+
const events = [
|
|
1132
1174
|
{
|
|
1133
1175
|
event_id: crypto.randomUUID(),
|
|
1134
1176
|
session_id: session.sessionId,
|
|
@@ -1138,16 +1180,41 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
|
|
|
1138
1180
|
tool: "claude-code",
|
|
1139
1181
|
metadata: {
|
|
1140
1182
|
started_at: session.startedAt,
|
|
1141
|
-
ended_at: now.toISOString()
|
|
1183
|
+
ended_at: now.toISOString(),
|
|
1184
|
+
reason: hookData["reason"] ?? "unknown",
|
|
1185
|
+
...metrics ? {
|
|
1186
|
+
prompt_tokens: metrics.promptTokens,
|
|
1187
|
+
completion_tokens: metrics.completionTokens,
|
|
1188
|
+
cache_read_tokens: metrics.cacheReadTokens,
|
|
1189
|
+
cache_creation_tokens: metrics.cacheCreationTokens
|
|
1190
|
+
} : {}
|
|
1142
1191
|
}
|
|
1143
1192
|
}
|
|
1144
|
-
]
|
|
1193
|
+
];
|
|
1194
|
+
if (metrics && (metrics.totalCostUsd > 0 || metrics.promptTokens > 0)) {
|
|
1195
|
+
events.push({
|
|
1196
|
+
event_id: crypto.randomUUID(),
|
|
1197
|
+
session_id: session.sessionId,
|
|
1198
|
+
event_type: "cost",
|
|
1199
|
+
timestamp: now.toISOString(),
|
|
1200
|
+
tool: "claude-code",
|
|
1201
|
+
tokens_prompt: metrics.promptTokens,
|
|
1202
|
+
tokens_completion: metrics.completionTokens,
|
|
1203
|
+
cost_usd: metrics.totalCostUsd,
|
|
1204
|
+
metadata: {
|
|
1205
|
+
cache_read_tokens: metrics.cacheReadTokens,
|
|
1206
|
+
cache_creation_tokens: metrics.cacheCreationTokens
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
const sent = await sendEvents(session.endpoint, session.apiKey, events);
|
|
1145
1211
|
try {
|
|
1146
1212
|
unlinkSync2(SESSION_FILE);
|
|
1147
1213
|
} catch {
|
|
1148
1214
|
}
|
|
1149
1215
|
if (sent) {
|
|
1150
|
-
|
|
1216
|
+
const costStr = metrics?.totalCostUsd ? ` $${metrics.totalCostUsd.toFixed(4)}` : "";
|
|
1217
|
+
success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s${costStr})`);
|
|
1151
1218
|
} else {
|
|
1152
1219
|
warn("Session ended locally but failed to send to Valt.");
|
|
1153
1220
|
}
|