@usevalt/cli 0.3.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +93 -26
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -281,7 +281,7 @@ var loginCommand = new Command2("login").description("Authenticate with Valt (op
281
281
  error(`Failed to initiate login: ${err instanceof Error ? err.message : String(err)}`);
282
282
  process.exit(1);
283
283
  }
284
- const verificationUrl = `${endpoint}/auth/device?code=${deviceData.user_code}`;
284
+ const verificationUrl = `${endpoint}/device?code=${deviceData.user_code}`;
285
285
  info("");
286
286
  info(`Your verification code: ${bold(deviceData.user_code)}`);
287
287
  info("");
@@ -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: process.env["CLAUDE_MODEL"] ?? "unknown",
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
- let stdinData = "";
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 sent = await sendEvents(session.endpoint, session.apiKey, [
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
- success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s)`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usevalt/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.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/eslint-config": "0.0.0",
43
- "@usevalt/typescript-config": "0.0.0"
42
+ "@usevalt/typescript-config": "0.0.0",
43
+ "@usevalt/eslint-config": "0.0.0"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsup",