@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.
- package/dist/index.js +175 -64
- 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
|
|
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
|
|
1002
|
-
const apiKey =
|
|
1003
|
+
function resolveConfig() {
|
|
1004
|
+
const apiKey = getApiKey();
|
|
1003
1005
|
if (!apiKey) {
|
|
1004
|
-
error("
|
|
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
|
-
|
|
1010
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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 (!
|
|
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:
|
|
1206
|
-
|
|
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 (
|
|
1320
|
+
if (existsSync5(hooksFile)) {
|
|
1211
1321
|
try {
|
|
1212
|
-
existingHooks = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1243
|
-
program.name("valt").description("Valt CLI -- trust layer for AI-assisted development").version("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.
|
|
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/
|
|
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",
|