@usevalt/cli 0.2.1 → 0.3.1

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 +175 -64
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/index.ts
10
- import { Command as Command15 } from "commander";
10
+ import { Command as Command16 } from "commander";
11
11
 
12
12
  // src/commands/init.ts
13
13
  import { Command } from "commander";
@@ -996,18 +996,34 @@ 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 { execSync } from "child_process";
1000
+ import { basename } from "path";
999
1001
  import os2 from "os";
1000
1002
  var SESSION_FILE = `${os2.tmpdir()}/valt-session-${process.getuid?.() ?? "default"}.json`;
1001
- function getEnvConfig() {
1002
- const apiKey = process.env["VALT_API_KEY"];
1003
+ function resolveConfig() {
1004
+ const apiKey = getApiKey();
1003
1005
  if (!apiKey) {
1004
- error("VALT_API_KEY environment variable is required");
1006
+ error("No API key found. Run `valt login` or set VALT_API_KEY.");
1005
1007
  process.exit(1);
1006
1008
  }
1007
1009
  const endpoint = process.env["VALT_ENDPOINT"] ?? "https://ingest.usevalt.com";
1008
1010
  const apiEndpoint = process.env["VALT_API_ENDPOINT"] ?? "https://usevalt.com";
1009
- const projectId = process.env["VALT_PROJECT_ID"];
1010
- return { apiKey, endpoint, apiEndpoint, projectId };
1011
+ return { apiKey, endpoint, apiEndpoint };
1012
+ }
1013
+ function detectProjectSlug() {
1014
+ const envProject = process.env["VALT_PROJECT_ID"];
1015
+ if (envProject) return envProject;
1016
+ try {
1017
+ const remote = execSync("git remote get-url origin", {
1018
+ encoding: "utf-8",
1019
+ timeout: 3e3,
1020
+ stdio: ["pipe", "pipe", "pipe"]
1021
+ }).trim();
1022
+ const match = remote.match(/\/([^/]+?)(?:\.git)?$/) ?? remote.match(/:([^/]+?)(?:\.git)?$/);
1023
+ if (match?.[1]) return match[1];
1024
+ } catch {
1025
+ }
1026
+ return basename(process.cwd());
1011
1027
  }
1012
1028
  function readSessionFile() {
1013
1029
  try {
@@ -1036,10 +1052,11 @@ async function sendEvents(endpoint, apiKey, events) {
1036
1052
  }
1037
1053
  var sessionStartCommand = new Command13("session-start").description("Hook: called when a Claude Code session starts").action(async () => {
1038
1054
  try {
1039
- const { apiKey, endpoint, apiEndpoint, projectId } = getEnvConfig();
1055
+ const { apiKey, endpoint, apiEndpoint } = resolveConfig();
1056
+ const projectSlug = detectProjectSlug();
1040
1057
  const sessionId = crypto.randomUUID();
1041
1058
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1042
- const state = { sessionId, startedAt, apiKey, endpoint, apiEndpoint, projectId };
1059
+ const state = { sessionId, startedAt, apiKey, endpoint, apiEndpoint, projectSlug };
1043
1060
  writeFileSync3(SESSION_FILE, JSON.stringify(state, null, 2), { mode: 384 });
1044
1061
  const sent = await sendEvents(endpoint, apiKey, [
1045
1062
  {
@@ -1053,54 +1070,24 @@ var sessionStartCommand = new Command13("session-start").description("Hook: call
1053
1070
  model: process.env["CLAUDE_MODEL"] ?? "unknown",
1054
1071
  repository: process.env["CLAUDE_REPO"],
1055
1072
  branch: process.env["CLAUDE_BRANCH"],
1056
- project_id: projectId
1073
+ project_slug: projectSlug,
1074
+ cwd: process.cwd()
1057
1075
  }
1058
1076
  }
1059
1077
  ]);
1060
1078
  if (sent) {
1061
- success(`Valt session started: ${sessionId}`);
1079
+ success(`Valt session started: ${sessionId.slice(0, 8)} (project: ${projectSlug})`);
1062
1080
  } else {
1063
- warn("Session started locally but failed to send to Valt. Events will be captured.");
1064
- }
1065
- if (projectId) {
1066
- try {
1067
- const convParams = new URLSearchParams({
1068
- project_id: projectId,
1069
- type: "convention",
1070
- limit: "20"
1071
- });
1072
- const convRes = await fetch(`${apiEndpoint}/api/v1/memories?${convParams.toString()}`, {
1073
- headers: {
1074
- "Content-Type": "application/json",
1075
- Authorization: `Bearer ${apiKey}`
1076
- },
1077
- signal: AbortSignal.timeout(5e3)
1078
- });
1079
- if (convRes.ok) {
1080
- const convBody = await convRes.json();
1081
- const conventions = convBody.data ?? [];
1082
- if (conventions.length > 0) {
1083
- console.log("\n## Project Conventions");
1084
- for (const c of conventions) {
1085
- console.log(`- **${c.title}**: ${c.content}`);
1086
- }
1087
- console.log("");
1088
- }
1089
- }
1090
- } catch {
1091
- }
1081
+ warn("Session started locally but failed to send to Valt.");
1092
1082
  }
1093
1083
  } catch (err) {
1094
- error(`Failed to start session: ${err instanceof Error ? err.message : String(err)}`);
1084
+ warn(`Failed to start session: ${err instanceof Error ? err.message : String(err)}`);
1095
1085
  }
1096
1086
  });
1097
1087
  var toolCallCommand = new Command13("tool-call").description("Hook: called on each Claude Code tool call (reads JSON from stdin)").action(async () => {
1098
1088
  try {
1099
1089
  const session = readSessionFile();
1100
- if (!session) {
1101
- warn("No active Valt session. Run `valt hook session-start` first.");
1102
- return;
1103
- }
1090
+ if (!session) return;
1104
1091
  let stdinData = "";
1105
1092
  if (!process.stdin.isTTY) {
1106
1093
  stdinData = await new Promise((resolve4) => {
@@ -1131,17 +1118,13 @@ var toolCallCommand = new Command13("tool-call").description("Hook: called on ea
1131
1118
  metadata: toolData
1132
1119
  }
1133
1120
  ]);
1134
- } catch (err) {
1135
- warn(`Tool call tracking failed: ${err instanceof Error ? err.message : String(err)}`);
1121
+ } catch {
1136
1122
  }
1137
1123
  });
1138
1124
  var sessionEndCommand = new Command13("session-end").description("Hook: called when a Claude Code session ends").action(async () => {
1139
1125
  try {
1140
1126
  const session = readSessionFile();
1141
- if (!session) {
1142
- warn("No active Valt session to end.");
1143
- return;
1144
- }
1127
+ if (!session) return;
1145
1128
  const now = /* @__PURE__ */ new Date();
1146
1129
  const startedAt = new Date(session.startedAt);
1147
1130
  const durationMs = now.getTime() - startedAt.getTime();
@@ -1164,19 +1147,146 @@ var sessionEndCommand = new Command13("session-end").description("Hook: called w
1164
1147
  } catch {
1165
1148
  }
1166
1149
  if (sent) {
1167
- success(`Valt session ended: ${session.sessionId} (${Math.round(durationMs / 1e3)}s)`);
1150
+ success(`Valt session ended: ${session.sessionId.slice(0, 8)} (${Math.round(durationMs / 1e3)}s)`);
1168
1151
  } else {
1169
1152
  warn("Session ended locally but failed to send to Valt.");
1170
1153
  }
1171
- } catch (err) {
1172
- error(`Failed to end session: ${err instanceof Error ? err.message : String(err)}`);
1154
+ } catch {
1173
1155
  }
1174
1156
  });
1175
1157
  var hookCommand = new Command13("hook").description("Claude Code hook handlers for Valt session tracking").addCommand(sessionStartCommand).addCommand(toolCallCommand).addCommand(sessionEndCommand);
1176
1158
 
1177
- // src/commands/start.ts
1159
+ // src/commands/setup.ts
1178
1160
  import { Command as Command14 } from "commander";
1179
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
1161
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
1162
+ import { join } from "path";
1163
+ import os3 from "os";
1164
+ var CLAUDE_DIR = join(os3.homedir(), ".claude");
1165
+ var HOOKS_FILE = join(CLAUDE_DIR, "hooks.json");
1166
+ var HOOK_PREFIX = "npx --yes @usevalt/cli";
1167
+ function getValtHooks() {
1168
+ return {
1169
+ SessionStart: [
1170
+ {
1171
+ hooks: [
1172
+ {
1173
+ type: "command",
1174
+ command: `${HOOK_PREFIX} hook session-start`
1175
+ }
1176
+ ]
1177
+ }
1178
+ ],
1179
+ PreToolUse: [
1180
+ {
1181
+ matcher: "*",
1182
+ hooks: [
1183
+ {
1184
+ type: "command",
1185
+ command: `${HOOK_PREFIX} hook tool-call`
1186
+ }
1187
+ ]
1188
+ }
1189
+ ],
1190
+ SessionEnd: [
1191
+ {
1192
+ matcher: "*",
1193
+ hooks: [
1194
+ {
1195
+ type: "command",
1196
+ command: `${HOOK_PREFIX} hook session-end`
1197
+ }
1198
+ ]
1199
+ }
1200
+ ]
1201
+ };
1202
+ }
1203
+ var setupCommand = new Command14("setup").description("Configure Claude Code hooks for automatic session tracking").option("--remove", "Remove Valt hooks from Claude Code").action(async (opts) => {
1204
+ try {
1205
+ const apiKey = getApiKey();
1206
+ if (!apiKey) {
1207
+ error("No API key found. Run `valt login` first.");
1208
+ process.exit(1);
1209
+ }
1210
+ if (!existsSync4(CLAUDE_DIR)) {
1211
+ mkdirSync2(CLAUDE_DIR, { recursive: true });
1212
+ }
1213
+ if (opts.remove) {
1214
+ removeHooks();
1215
+ return;
1216
+ }
1217
+ let config = { hooks: {} };
1218
+ if (existsSync4(HOOKS_FILE)) {
1219
+ try {
1220
+ const raw = readFileSync4(HOOKS_FILE, "utf-8");
1221
+ config = JSON.parse(raw);
1222
+ if (!config.hooks) config.hooks = {};
1223
+ } catch {
1224
+ warn("Could not parse existing hooks.json. Creating new one.");
1225
+ config = { hooks: {} };
1226
+ }
1227
+ }
1228
+ const valtHooks = getValtHooks();
1229
+ for (const [event, hooks] of Object.entries(valtHooks)) {
1230
+ const existing = config.hooks[event];
1231
+ if (existing && Array.isArray(existing)) {
1232
+ const hasValt = existing.some((h) => {
1233
+ const entry = h;
1234
+ return entry.hooks?.some((hk) => hk.command?.includes("valt") && hk.command?.includes("hook"));
1235
+ });
1236
+ if (!hasValt) {
1237
+ existing.push(...hooks);
1238
+ }
1239
+ } else {
1240
+ config.hooks[event] = hooks;
1241
+ }
1242
+ }
1243
+ writeFileSync4(HOOKS_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
1244
+ success("Claude Code hooks configured.");
1245
+ info("");
1246
+ info("Every Claude Code session will now be tracked by Valt.");
1247
+ info(`Projects are auto-detected from git repo name or folder name.`);
1248
+ info(`Hooks written to: ${dim(HOOKS_FILE)}`);
1249
+ info("");
1250
+ info("To remove: valt setup --remove");
1251
+ } catch (err) {
1252
+ error(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
1253
+ process.exit(1);
1254
+ }
1255
+ });
1256
+ function removeHooks() {
1257
+ if (!existsSync4(HOOKS_FILE)) {
1258
+ info("No hooks.json found. Nothing to remove.");
1259
+ return;
1260
+ }
1261
+ try {
1262
+ const raw = readFileSync4(HOOKS_FILE, "utf-8");
1263
+ const config = JSON.parse(raw);
1264
+ if (!config.hooks) {
1265
+ info("No hooks configured. Nothing to remove.");
1266
+ return;
1267
+ }
1268
+ for (const event of Object.keys(config.hooks)) {
1269
+ const entries = config.hooks[event];
1270
+ if (!Array.isArray(entries)) continue;
1271
+ config.hooks[event] = entries.filter((h) => {
1272
+ const entry = h;
1273
+ return !entry.hooks?.some((hk) => hk.command?.includes("valt") && hk.command?.includes("hook"));
1274
+ });
1275
+ if (config.hooks[event].length === 0) {
1276
+ delete config.hooks[event];
1277
+ }
1278
+ }
1279
+ writeFileSync4(HOOKS_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
1280
+ success("Valt hooks removed from Claude Code.");
1281
+ } catch {
1282
+ error("Could not parse hooks.json.");
1283
+ process.exit(1);
1284
+ }
1285
+ }
1286
+
1287
+ // src/commands/start.ts
1288
+ import { Command as Command15 } from "commander";
1289
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
1180
1290
  import { resolve as resolve3 } from "path";
1181
1291
  var HOOKS_PATH = ".claude/hooks.json";
1182
1292
  var VALT_HOOKS = {
@@ -1191,7 +1301,7 @@ var VALT_HOOKS = {
1191
1301
  hooks: [{ type: "command", command: "npx @usevalt/cli hook session-end" }]
1192
1302
  }]
1193
1303
  };
1194
- var startCommand = new Command14("start").description("Configure Valt for Claude Code in the current project").action(() => {
1304
+ var startCommand = new Command15("start").description("Configure Valt for Claude Code in the current project").action(() => {
1195
1305
  try {
1196
1306
  const apiKey = getApiKey();
1197
1307
  if (!apiKey) {
@@ -1199,17 +1309,17 @@ var startCommand = new Command14("start").description("Configure Valt for Claude
1199
1309
  process.exit(1);
1200
1310
  }
1201
1311
  const claudeDir = resolve3(process.cwd(), ".claude");
1202
- if (!existsSync4(claudeDir)) {
1312
+ if (!existsSync5(claudeDir)) {
1203
1313
  warn("No .claude/ directory found. This command is designed for Claude Code projects.");
1204
1314
  info("Creating .claude/ directory...");
1205
- const { mkdirSync: mkdirSync2 } = __require("fs");
1206
- mkdirSync2(claudeDir, { recursive: true });
1315
+ const { mkdirSync: mkdirSync3 } = __require("fs");
1316
+ mkdirSync3(claudeDir, { recursive: true });
1207
1317
  }
1208
1318
  const hooksFile = resolve3(process.cwd(), HOOKS_PATH);
1209
1319
  let existingHooks = {};
1210
- if (existsSync4(hooksFile)) {
1320
+ if (existsSync5(hooksFile)) {
1211
1321
  try {
1212
- existingHooks = JSON.parse(readFileSync4(hooksFile, "utf-8"));
1322
+ existingHooks = JSON.parse(readFileSync5(hooksFile, "utf-8"));
1213
1323
  info("Found existing hooks.json -- merging Valt hooks.");
1214
1324
  } catch {
1215
1325
  warn("Existing hooks.json is not valid JSON. Creating a new one.");
@@ -1228,7 +1338,7 @@ var startCommand = new Command14("start").description("Configure Valt for Claude
1228
1338
  hooks[event] = [...existing ?? [], ...valtEntries];
1229
1339
  }
1230
1340
  const merged = { ...existingHooks, hooks };
1231
- writeFileSync4(hooksFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1341
+ writeFileSync5(hooksFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1232
1342
  success("Valt is configured.");
1233
1343
  info(`Hooks written to ${bold(HOOKS_PATH)}`);
1234
1344
  info("Start a Claude Code session and your work will be tracked automatically.");
@@ -1239,8 +1349,8 @@ var startCommand = new Command14("start").description("Configure Valt for Claude
1239
1349
  });
1240
1350
 
1241
1351
  // src/index.ts
1242
- var program = new Command15();
1243
- program.name("valt").description("Valt CLI -- trust layer for AI-assisted development").version("0.2.0");
1352
+ var program = new Command16();
1353
+ program.name("valt").description("Valt CLI -- trust layer for AI-assisted development").version("0.3.0");
1244
1354
  program.addCommand(initCommand);
1245
1355
  program.addCommand(loginCommand);
1246
1356
  program.addCommand(statusCommand);
@@ -1254,5 +1364,6 @@ program.addCommand(costsCommand);
1254
1364
  program.addCommand(analyzeCommand);
1255
1365
  program.addCommand(proxyCommand);
1256
1366
  program.addCommand(hookCommand);
1367
+ program.addCommand(setupCommand);
1257
1368
  program.addCommand(startCommand);
1258
1369
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usevalt/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
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",