@usevalt/cli 0.1.0 → 0.2.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 +602 -21
- package/package.json +20 -18
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
10
|
+
import { Command as Command15 } from "commander";
|
|
5
11
|
|
|
6
12
|
// src/commands/init.ts
|
|
7
13
|
import { Command } from "commander";
|
|
@@ -9,6 +15,13 @@ import { Command } from "commander";
|
|
|
9
15
|
// src/lib/config.ts
|
|
10
16
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
11
17
|
import { resolve } from "path";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var configSchema = z.object({
|
|
20
|
+
apiKey: z.string().optional(),
|
|
21
|
+
endpoint: z.string().url().optional(),
|
|
22
|
+
projectId: z.string().optional(),
|
|
23
|
+
projectSlug: z.string().optional()
|
|
24
|
+
}).passthrough();
|
|
12
25
|
var CONFIG_FILE = ".valtrc.json";
|
|
13
26
|
function findConfigPath(startDir = process.cwd()) {
|
|
14
27
|
let dir = startDir;
|
|
@@ -24,7 +37,13 @@ function readConfig(dir = process.cwd()) {
|
|
|
24
37
|
const path = findConfigPath(dir);
|
|
25
38
|
if (!path) return {};
|
|
26
39
|
try {
|
|
27
|
-
|
|
40
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
41
|
+
const parsed = configSchema.safeParse(raw);
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
console.warn(`[valt] Invalid config in ${path}: ${parsed.error.message}`);
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
return parsed.data;
|
|
28
47
|
} catch {
|
|
29
48
|
return {};
|
|
30
49
|
}
|
|
@@ -91,8 +110,115 @@ function table(rows) {
|
|
|
91
110
|
|
|
92
111
|
// src/commands/init.ts
|
|
93
112
|
import { API_KEY_PREFIX } from "@usevalt/shared";
|
|
113
|
+
|
|
114
|
+
// src/schemas.ts
|
|
115
|
+
import { z as z2 } from "zod";
|
|
116
|
+
var ApiErrorResponseSchema = z2.object({
|
|
117
|
+
error: z2.string().optional(),
|
|
118
|
+
message: z2.string().optional()
|
|
119
|
+
});
|
|
120
|
+
var CertificateSchema = z2.object({
|
|
121
|
+
certificate_number: z2.string(),
|
|
122
|
+
status: z2.string(),
|
|
123
|
+
trust_score: z2.number(),
|
|
124
|
+
session_id: z2.string(),
|
|
125
|
+
git_commit: z2.string().nullable(),
|
|
126
|
+
issued_at: z2.string()
|
|
127
|
+
});
|
|
128
|
+
var CertificateGenerateResponseSchema = z2.object({
|
|
129
|
+
data: z2.object({
|
|
130
|
+
certificate_number: z2.string(),
|
|
131
|
+
trust_score: z2.number()
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
var CertificateListResponseSchema = z2.object({
|
|
135
|
+
data: z2.array(CertificateSchema)
|
|
136
|
+
});
|
|
137
|
+
var VerifyResponseSchema = z2.object({
|
|
138
|
+
verified: z2.boolean(),
|
|
139
|
+
certificate_number: z2.string().optional(),
|
|
140
|
+
trust_score: z2.number().optional(),
|
|
141
|
+
status: z2.string().optional(),
|
|
142
|
+
signature_valid: z2.boolean().optional(),
|
|
143
|
+
issued_at: z2.string().optional(),
|
|
144
|
+
reason: z2.string().optional()
|
|
145
|
+
});
|
|
146
|
+
var SessionDetailSchema = z2.object({
|
|
147
|
+
id: z2.string(),
|
|
148
|
+
tool: z2.string().nullable(),
|
|
149
|
+
model: z2.string().nullable(),
|
|
150
|
+
status: z2.string().nullable(),
|
|
151
|
+
trust_score: z2.number().nullable(),
|
|
152
|
+
total_cost_usd: z2.number().nullable(),
|
|
153
|
+
duration_ms: z2.number().nullable(),
|
|
154
|
+
git_branch: z2.string().nullable(),
|
|
155
|
+
started_at: z2.string().nullable(),
|
|
156
|
+
tags: z2.array(z2.string()).nullable()
|
|
157
|
+
});
|
|
158
|
+
var SessionDetailResponseSchema = z2.object({
|
|
159
|
+
data: SessionDetailSchema
|
|
160
|
+
});
|
|
161
|
+
var SessionSummarySchema = z2.object({
|
|
162
|
+
id: z2.string(),
|
|
163
|
+
tool: z2.string(),
|
|
164
|
+
status: z2.string(),
|
|
165
|
+
started_at: z2.string(),
|
|
166
|
+
duration_ms: z2.number().nullable(),
|
|
167
|
+
total_cost_usd: z2.number(),
|
|
168
|
+
trust_score: z2.number().nullable(),
|
|
169
|
+
prompt_summary: z2.string().nullable()
|
|
170
|
+
});
|
|
171
|
+
var SessionListResponseSchema = z2.object({
|
|
172
|
+
sessions: z2.array(SessionSummarySchema)
|
|
173
|
+
});
|
|
174
|
+
var SessionWithBreakdownSchema = z2.object({
|
|
175
|
+
trust_score: z2.number().nullable(),
|
|
176
|
+
trust_score_breakdown: z2.record(z2.string(), z2.number()).nullable()
|
|
177
|
+
});
|
|
178
|
+
var SessionWithBreakdownResponseSchema = z2.object({
|
|
179
|
+
data: SessionWithBreakdownSchema
|
|
180
|
+
});
|
|
181
|
+
var SessionSearchResponseSchema = z2.object({
|
|
182
|
+
sessions: z2.array(z2.object({ id: z2.string() }))
|
|
183
|
+
});
|
|
184
|
+
var CostByToolSchema = z2.object({
|
|
185
|
+
tool: z2.string(),
|
|
186
|
+
totalCost: z2.number(),
|
|
187
|
+
sessionCount: z2.number()
|
|
188
|
+
});
|
|
189
|
+
var CostsResponseSchema = z2.object({
|
|
190
|
+
data: z2.object({
|
|
191
|
+
total_cost_usd: z2.number(),
|
|
192
|
+
cost_by_tool: z2.array(CostByToolSchema),
|
|
193
|
+
daily_costs: z2.array(z2.object({
|
|
194
|
+
date: z2.string(),
|
|
195
|
+
cost: z2.number()
|
|
196
|
+
}))
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
var ConfigKeySchema = z2.enum(["apiKey", "endpoint", "projectId", "projectSlug"]);
|
|
200
|
+
var InitArgsSchema = z2.object({
|
|
201
|
+
apiKey: z2.string().optional(),
|
|
202
|
+
endpoint: z2.string().url("Endpoint must be a valid URL").optional()
|
|
203
|
+
});
|
|
204
|
+
function validateResponse(schema, data, label) {
|
|
205
|
+
const result = schema.safeParse(data);
|
|
206
|
+
if (!result.success) {
|
|
207
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
208
|
+
throw new Error(`Invalid ${label} response: ${issues}`);
|
|
209
|
+
}
|
|
210
|
+
return result.data;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/commands/init.ts
|
|
94
214
|
var initCommand = new Command("init").description("Initialize Valt in the current project").option("-k, --api-key <key>", "API key").option("-e, --endpoint <url>", "API endpoint").action(async (opts) => {
|
|
95
215
|
try {
|
|
216
|
+
const argsResult = InitArgsSchema.safeParse(opts);
|
|
217
|
+
if (!argsResult.success) {
|
|
218
|
+
const issues = argsResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
219
|
+
error(`Invalid arguments: ${issues}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
96
222
|
const existing = readConfig();
|
|
97
223
|
const config = { ...existing };
|
|
98
224
|
if (opts.apiKey) {
|
|
@@ -121,19 +247,75 @@ var initCommand = new Command("init").description("Initialize Valt in the curren
|
|
|
121
247
|
|
|
122
248
|
// src/commands/login.ts
|
|
123
249
|
import { Command as Command2 } from "commander";
|
|
250
|
+
import os from "os";
|
|
251
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
252
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
253
|
+
function getEndpoint2() {
|
|
254
|
+
return process.env.VALT_ENDPOINT ?? "https://usevalt.com";
|
|
255
|
+
}
|
|
256
|
+
function sleep(ms) {
|
|
257
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
258
|
+
}
|
|
259
|
+
async function openBrowser(url) {
|
|
260
|
+
try {
|
|
261
|
+
const open = (await import("open")).default;
|
|
262
|
+
await open(url);
|
|
263
|
+
} catch {
|
|
264
|
+
warn(`Could not open browser automatically. Please visit the URL above.`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
124
267
|
var loginCommand = new Command2("login").description("Authenticate with Valt (opens browser)").action(async () => {
|
|
268
|
+
const endpoint = getEndpoint2();
|
|
269
|
+
info("Starting device authorization flow...");
|
|
270
|
+
let deviceData;
|
|
125
271
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
272
|
+
const res = await fetch(`${endpoint}/api/auth/device`, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { "Content-Type": "application/json" }
|
|
275
|
+
});
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
throw new Error(`Server returned ${res.status}`);
|
|
278
|
+
}
|
|
279
|
+
deviceData = await res.json();
|
|
133
280
|
} catch (err) {
|
|
134
|
-
error(`
|
|
281
|
+
error(`Failed to initiate login: ${err instanceof Error ? err.message : String(err)}`);
|
|
135
282
|
process.exit(1);
|
|
136
283
|
}
|
|
284
|
+
const verificationUrl = `${endpoint}/auth/device?code=${deviceData.user_code}`;
|
|
285
|
+
info("");
|
|
286
|
+
info(`Your verification code: ${bold(deviceData.user_code)}`);
|
|
287
|
+
info("");
|
|
288
|
+
info(`Opening browser to: ${dim(verificationUrl)}`);
|
|
289
|
+
info("If the browser does not open, visit the URL above manually.");
|
|
290
|
+
info("");
|
|
291
|
+
await openBrowser(verificationUrl);
|
|
292
|
+
info("Waiting for approval...");
|
|
293
|
+
const startTime = Date.now();
|
|
294
|
+
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
295
|
+
await sleep(POLL_INTERVAL_MS);
|
|
296
|
+
try {
|
|
297
|
+
const res = await fetch(
|
|
298
|
+
`${endpoint}/api/auth/device?code=${deviceData.device_code}`
|
|
299
|
+
);
|
|
300
|
+
if (!res.ok) continue;
|
|
301
|
+
const poll = await res.json();
|
|
302
|
+
if (poll.status === "approved" && poll.api_key) {
|
|
303
|
+
const keyPrefix = poll.api_key.slice(0, 8);
|
|
304
|
+
writeConfig({ apiKey: poll.api_key, endpoint }, os.homedir());
|
|
305
|
+
info("");
|
|
306
|
+
success(`Authenticated successfully. API key: ${dim(keyPrefix + "...")}`);
|
|
307
|
+
success(`Credentials saved to ~/.valtrc.json`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (poll.status === "expired") {
|
|
311
|
+
error("Device code expired. Please try again.");
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
error("Login timed out after 5 minutes. Please try again.");
|
|
318
|
+
process.exit(1);
|
|
137
319
|
});
|
|
138
320
|
|
|
139
321
|
// src/commands/status.ts
|
|
@@ -175,9 +357,10 @@ var statusCommand = new Command3("status").description("Show recent session acti
|
|
|
175
357
|
const spinner = ora("Fetching sessions...").start();
|
|
176
358
|
try {
|
|
177
359
|
const limit = parseInt(opts.limit, 10) || 10;
|
|
178
|
-
const
|
|
360
|
+
const raw = await apiRequest(
|
|
179
361
|
`/v1/sessions?limit=${limit}`
|
|
180
362
|
);
|
|
363
|
+
const data = validateResponse(SessionListResponseSchema, raw, "session list");
|
|
181
364
|
spinner.stop();
|
|
182
365
|
if (data.sessions.length === 0) {
|
|
183
366
|
info("No sessions found. Start tracking with the SDK.");
|
|
@@ -217,9 +400,10 @@ import ora2 from "ora";
|
|
|
217
400
|
var verifyCommand = new Command4("verify").description("Check Valt Verified status for a commit").argument("<commit>", "Git commit SHA to verify").action(async (commit) => {
|
|
218
401
|
const spinner = ora2("Verifying commit...").start();
|
|
219
402
|
try {
|
|
220
|
-
const
|
|
403
|
+
const raw = await apiRequest(
|
|
221
404
|
`/v1/certificates/verify?commit=${encodeURIComponent(commit)}`
|
|
222
405
|
);
|
|
406
|
+
const data = validateResponse(VerifyResponseSchema, raw, "certificate verify");
|
|
223
407
|
spinner.stop();
|
|
224
408
|
if (data.verified) {
|
|
225
409
|
success(`Valt Verified ${bold(data.certificate_number ?? "")}`);
|
|
@@ -288,6 +472,25 @@ var doctorCommand = new Command5("doctor").description("Check Valt configuration
|
|
|
288
472
|
issues++;
|
|
289
473
|
}
|
|
290
474
|
}
|
|
475
|
+
const evalUrl = process.env["VALT_EVAL_URL"];
|
|
476
|
+
if (evalUrl) {
|
|
477
|
+
try {
|
|
478
|
+
const evalRes = await fetch(`${evalUrl}/eval/health`, {
|
|
479
|
+
signal: AbortSignal.timeout(5e3)
|
|
480
|
+
});
|
|
481
|
+
if (evalRes.ok) {
|
|
482
|
+
success(`Eval service: ${dim(evalUrl)} (Semgrep)`);
|
|
483
|
+
} else {
|
|
484
|
+
warn(`Eval service returned status ${evalRes.status}`);
|
|
485
|
+
issues++;
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
488
|
+
warn("Eval service unreachable (security scores will use heuristic fallback)");
|
|
489
|
+
issues++;
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
info("Eval service: not configured (set VALT_EVAL_URL for Semgrep scans)");
|
|
493
|
+
}
|
|
291
494
|
console.log("");
|
|
292
495
|
if (issues === 0) {
|
|
293
496
|
success("All checks passed!");
|
|
@@ -318,6 +521,10 @@ import { Command as Command7 } from "commander";
|
|
|
318
521
|
var configCommand = new Command7("config").description("Manage Valt configuration");
|
|
319
522
|
configCommand.command("set <key> <value>").description("Set a config value").action((key, value) => {
|
|
320
523
|
try {
|
|
524
|
+
const keyResult = ConfigKeySchema.safeParse(key);
|
|
525
|
+
if (!keyResult.success) {
|
|
526
|
+
warn(`Unknown config key "${key}". Valid keys: ${ConfigKeySchema.options.join(", ")}`);
|
|
527
|
+
}
|
|
321
528
|
const config = readConfig();
|
|
322
529
|
config[key] = value;
|
|
323
530
|
writeConfig(config);
|
|
@@ -359,12 +566,14 @@ configCommand.command("list").description("List all config values").action(() =>
|
|
|
359
566
|
// src/commands/sessions.ts
|
|
360
567
|
import { Command as Command8 } from "commander";
|
|
361
568
|
import ora3 from "ora";
|
|
362
|
-
var sessionsCommand = new Command8("sessions").description("View
|
|
569
|
+
var sessionsCommand = new Command8("sessions").description("View and list sessions");
|
|
570
|
+
sessionsCommand.command("show <id>").description("Show session details").action(async (id) => {
|
|
363
571
|
const spinner = ora3("Fetching session...").start();
|
|
364
572
|
try {
|
|
365
|
-
const
|
|
573
|
+
const raw = await apiRequest(
|
|
366
574
|
`/v1/sessions/${encodeURIComponent(id)}`
|
|
367
575
|
);
|
|
576
|
+
const session = validateResponse(SessionDetailResponseSchema, raw, "session detail");
|
|
368
577
|
spinner.stop();
|
|
369
578
|
const s = session.data;
|
|
370
579
|
console.log(bold(`Session: ${id}`));
|
|
@@ -393,6 +602,55 @@ var sessionsCommand = new Command8("sessions").description("View session details
|
|
|
393
602
|
process.exit(1);
|
|
394
603
|
}
|
|
395
604
|
});
|
|
605
|
+
sessionsCommand.command("list").description("List recent sessions").option("-n, --limit <count>", "Number of sessions to show", "10").option("-s, --status <status>", "Filter by status").action(async (opts) => {
|
|
606
|
+
const spinner = ora3("Fetching sessions...").start();
|
|
607
|
+
try {
|
|
608
|
+
const limit = parseInt(opts.limit, 10) || 10;
|
|
609
|
+
let url = `/v1/sessions?limit=${limit}`;
|
|
610
|
+
if (opts.status) {
|
|
611
|
+
url += `&status=${encodeURIComponent(opts.status)}`;
|
|
612
|
+
}
|
|
613
|
+
const raw = await apiRequest(url);
|
|
614
|
+
const data = validateResponse(SessionListResponseSchema, raw, "session list");
|
|
615
|
+
spinner.stop();
|
|
616
|
+
if (data.sessions.length === 0) {
|
|
617
|
+
info("No sessions found.");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
console.log(bold(`Sessions (${data.sessions.length})`));
|
|
621
|
+
console.log("");
|
|
622
|
+
const rows = [
|
|
623
|
+
["ID", "TOOL", "STATUS", "SCORE", "COST", "DURATION", "STARTED"]
|
|
624
|
+
];
|
|
625
|
+
for (const s of data.sessions) {
|
|
626
|
+
rows.push([
|
|
627
|
+
s.id.slice(0, 8),
|
|
628
|
+
s.tool,
|
|
629
|
+
s.status,
|
|
630
|
+
s.trust_score !== null ? String(s.trust_score) : "-",
|
|
631
|
+
formatCost(s.total_cost_usd),
|
|
632
|
+
s.duration_ms ? formatDuration(s.duration_ms) : "-",
|
|
633
|
+
formatDate(s.started_at)
|
|
634
|
+
]);
|
|
635
|
+
}
|
|
636
|
+
table(rows);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
spinner.stop();
|
|
639
|
+
if (err instanceof ApiError) {
|
|
640
|
+
error(err.message);
|
|
641
|
+
} else {
|
|
642
|
+
error(`Failed to list sessions: ${err instanceof Error ? err.message : String(err)}`);
|
|
643
|
+
}
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
sessionsCommand.argument("[id]", "Session ID to show").action(async (id) => {
|
|
648
|
+
if (id) {
|
|
649
|
+
await sessionsCommand.commands.find((c) => c.name() === "show").parseAsync(["node", "show", id]);
|
|
650
|
+
} else {
|
|
651
|
+
await sessionsCommand.commands.find((c) => c.name() === "list").parseAsync(["node", "list"]);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
396
654
|
|
|
397
655
|
// src/commands/cert.ts
|
|
398
656
|
import { Command as Command9 } from "commander";
|
|
@@ -401,13 +659,14 @@ var certCommand = new Command9("cert").description("Manage certificates");
|
|
|
401
659
|
certCommand.command("generate").description("Generate a certificate for a session").requiredOption("--session <id>", "Session ID").option("--force", "Force regeneration if certificate exists").action(async (opts) => {
|
|
402
660
|
const spinner = ora4("Generating certificate...").start();
|
|
403
661
|
try {
|
|
404
|
-
const
|
|
662
|
+
const raw = await apiRequest("/v1/certificates", {
|
|
405
663
|
method: "POST",
|
|
406
664
|
body: JSON.stringify({
|
|
407
665
|
session_id: opts.session,
|
|
408
666
|
force: opts.force ?? false
|
|
409
667
|
})
|
|
410
668
|
});
|
|
669
|
+
const result = validateResponse(CertificateGenerateResponseSchema, raw, "certificate generate");
|
|
411
670
|
spinner.stop();
|
|
412
671
|
success(`Certificate generated: ${result.data.certificate_number} (score: ${result.data.trust_score})`);
|
|
413
672
|
} catch (err) {
|
|
@@ -423,9 +682,10 @@ certCommand.command("generate").description("Generate a certificate for a sessio
|
|
|
423
682
|
certCommand.command("show <number>").description("Show certificate details").action(async (certNumber) => {
|
|
424
683
|
const spinner = ora4("Fetching certificate...").start();
|
|
425
684
|
try {
|
|
426
|
-
const
|
|
685
|
+
const raw = await apiRequest(
|
|
427
686
|
`/v1/certificates?certificate_number=${encodeURIComponent(certNumber)}`
|
|
428
687
|
);
|
|
688
|
+
const result = validateResponse(CertificateListResponseSchema, raw, "certificate list");
|
|
429
689
|
spinner.stop();
|
|
430
690
|
const certs = result.data;
|
|
431
691
|
if (!certs || certs.length === 0) {
|
|
@@ -452,6 +712,77 @@ certCommand.command("show <number>").description("Show certificate details").act
|
|
|
452
712
|
process.exit(1);
|
|
453
713
|
}
|
|
454
714
|
});
|
|
715
|
+
certCommand.command("list").description("List certificates").option("-n, --limit <count>", "Number of certificates to show", "10").option("-s, --status <status>", "Filter by status (active, revoked)").action(async (opts) => {
|
|
716
|
+
const spinner = ora4("Fetching certificates...").start();
|
|
717
|
+
try {
|
|
718
|
+
const limit = parseInt(opts.limit, 10) || 10;
|
|
719
|
+
let url = `/v1/certificates?limit=${limit}`;
|
|
720
|
+
if (opts.status) {
|
|
721
|
+
url += `&status=${encodeURIComponent(opts.status)}`;
|
|
722
|
+
}
|
|
723
|
+
const raw = await apiRequest(url);
|
|
724
|
+
const result = validateResponse(CertificateListResponseSchema, raw, "certificate list");
|
|
725
|
+
spinner.stop();
|
|
726
|
+
const certs = result.data;
|
|
727
|
+
if (!certs || certs.length === 0) {
|
|
728
|
+
info("No certificates found.");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
console.log(bold(`Certificates (${certs.length})`));
|
|
732
|
+
console.log("");
|
|
733
|
+
const rows = [
|
|
734
|
+
["NUMBER", "STATUS", "SCORE", "COMMIT", "ISSUED"]
|
|
735
|
+
];
|
|
736
|
+
for (const cert of certs) {
|
|
737
|
+
rows.push([
|
|
738
|
+
cert.certificate_number,
|
|
739
|
+
cert.status,
|
|
740
|
+
String(cert.trust_score),
|
|
741
|
+
cert.git_commit?.slice(0, 8) ?? "-",
|
|
742
|
+
formatDate(cert.issued_at)
|
|
743
|
+
]);
|
|
744
|
+
}
|
|
745
|
+
table(rows);
|
|
746
|
+
} catch (err) {
|
|
747
|
+
spinner.stop();
|
|
748
|
+
if (err instanceof ApiError) {
|
|
749
|
+
error(err.message);
|
|
750
|
+
} else {
|
|
751
|
+
error(`Failed to list certificates: ${err instanceof Error ? err.message : String(err)}`);
|
|
752
|
+
}
|
|
753
|
+
process.exit(1);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
certCommand.command("verify <commit>").description("Verify a certificate for a commit").action(async (commit) => {
|
|
757
|
+
const spinner = ora4("Verifying...").start();
|
|
758
|
+
try {
|
|
759
|
+
const raw = await apiRequest(
|
|
760
|
+
`/v1/certificates/verify?commit=${encodeURIComponent(commit)}`
|
|
761
|
+
);
|
|
762
|
+
const data = validateResponse(VerifyResponseSchema, raw, "certificate verify");
|
|
763
|
+
spinner.stop();
|
|
764
|
+
if (data.verified) {
|
|
765
|
+
success(`Valt Verified ${bold(data.certificate_number ?? "")}`);
|
|
766
|
+
info(` Trust Score: ${data.trust_score ?? "-"}`);
|
|
767
|
+
info(` Status: ${data.status ?? "-"}`);
|
|
768
|
+
info(` Signature Valid: ${data.signature_valid ? "Yes" : "No"}`);
|
|
769
|
+
if (data.issued_at) info(` Issued: ${data.issued_at}`);
|
|
770
|
+
} else {
|
|
771
|
+
warn("Not Valt Verified");
|
|
772
|
+
if (data.reason) info(` Reason: ${data.reason}`);
|
|
773
|
+
}
|
|
774
|
+
} catch (err) {
|
|
775
|
+
spinner.stop();
|
|
776
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
777
|
+
warn("No certificate found for this commit.");
|
|
778
|
+
} else if (err instanceof ApiError) {
|
|
779
|
+
error(err.message);
|
|
780
|
+
} else {
|
|
781
|
+
error(`Failed to verify: ${err instanceof Error ? err.message : String(err)}`);
|
|
782
|
+
}
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
455
786
|
|
|
456
787
|
// src/commands/costs.ts
|
|
457
788
|
import { Command as Command10 } from "commander";
|
|
@@ -460,9 +791,10 @@ var costsCommand = new Command10("costs").description("View cost breakdown").opt
|
|
|
460
791
|
const spinner = ora5("Fetching costs...").start();
|
|
461
792
|
try {
|
|
462
793
|
const days = opts.period.replace("d", "");
|
|
463
|
-
const
|
|
794
|
+
const raw = await apiRequest(
|
|
464
795
|
`/v1/analytics/costs?days=${encodeURIComponent(days)}`
|
|
465
796
|
);
|
|
797
|
+
const result = validateResponse(CostsResponseSchema, raw, "costs");
|
|
466
798
|
spinner.stop();
|
|
467
799
|
const data = result.data;
|
|
468
800
|
console.log(bold(`Total Cost: ${formatCost(data.total_cost_usd)}`));
|
|
@@ -494,9 +826,10 @@ var analyzeCommand = new Command11("analyze").description("Analyze a session or
|
|
|
494
826
|
try {
|
|
495
827
|
let sessionId = opts.session;
|
|
496
828
|
if (opts.commit && !sessionId) {
|
|
497
|
-
const
|
|
829
|
+
const rawSessions = await apiRequest(
|
|
498
830
|
`/v1/sessions?git_commit=${encodeURIComponent(opts.commit)}`
|
|
499
831
|
);
|
|
832
|
+
const sessions = validateResponse(SessionSearchResponseSchema, rawSessions, "session search");
|
|
500
833
|
if (!sessions.sessions || sessions.sessions.length === 0) {
|
|
501
834
|
spinner.stop();
|
|
502
835
|
error(`No session found for commit: ${opts.commit}`);
|
|
@@ -509,9 +842,10 @@ var analyzeCommand = new Command11("analyze").description("Analyze a session or
|
|
|
509
842
|
error("Provide --session <id> or --commit <sha>");
|
|
510
843
|
return;
|
|
511
844
|
}
|
|
512
|
-
const
|
|
845
|
+
const raw = await apiRequest(
|
|
513
846
|
`/v1/sessions/${encodeURIComponent(sessionId)}`
|
|
514
847
|
);
|
|
848
|
+
const result = validateResponse(SessionWithBreakdownResponseSchema, raw, "session breakdown");
|
|
515
849
|
spinner.stop();
|
|
516
850
|
const s = result.data;
|
|
517
851
|
console.log(bold("Trust Score Analysis"));
|
|
@@ -659,9 +993,254 @@ proxyCommand.command("stop").description("Stop the MCP proxy").action(() => {
|
|
|
659
993
|
}
|
|
660
994
|
});
|
|
661
995
|
|
|
996
|
+
// src/commands/hook.ts
|
|
997
|
+
import { Command as Command13 } from "commander";
|
|
998
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, existsSync as existsSync3 } from "fs";
|
|
999
|
+
import os2 from "os";
|
|
1000
|
+
var SESSION_FILE = `${os2.tmpdir()}/valt-session-${process.getuid?.() ?? "default"}.json`;
|
|
1001
|
+
function getEnvConfig() {
|
|
1002
|
+
const apiKey = process.env["VALT_API_KEY"];
|
|
1003
|
+
if (!apiKey) {
|
|
1004
|
+
error("VALT_API_KEY environment variable is required");
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
const endpoint = process.env["VALT_ENDPOINT"] ?? "https://ingest.usevalt.com";
|
|
1008
|
+
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
|
+
}
|
|
1012
|
+
function readSessionFile() {
|
|
1013
|
+
try {
|
|
1014
|
+
if (!existsSync3(SESSION_FILE)) return null;
|
|
1015
|
+
const data = readFileSync3(SESSION_FILE, "utf-8");
|
|
1016
|
+
return JSON.parse(data);
|
|
1017
|
+
} catch {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async function sendEvents(endpoint, apiKey, events) {
|
|
1022
|
+
try {
|
|
1023
|
+
const res = await fetch(`${endpoint}/v1/events`, {
|
|
1024
|
+
method: "POST",
|
|
1025
|
+
headers: {
|
|
1026
|
+
"Content-Type": "application/json",
|
|
1027
|
+
Authorization: `Bearer ${apiKey}`
|
|
1028
|
+
},
|
|
1029
|
+
body: JSON.stringify({ events }),
|
|
1030
|
+
signal: AbortSignal.timeout(1e4)
|
|
1031
|
+
});
|
|
1032
|
+
return res.ok;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
var sessionStartCommand = new Command13("session-start").description("Hook: called when a Claude Code session starts").action(async () => {
|
|
1038
|
+
try {
|
|
1039
|
+
const { apiKey, endpoint, apiEndpoint, projectId } = getEnvConfig();
|
|
1040
|
+
const sessionId = crypto.randomUUID();
|
|
1041
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1042
|
+
const state = { sessionId, startedAt, apiKey, endpoint, apiEndpoint, projectId };
|
|
1043
|
+
writeFileSync3(SESSION_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1044
|
+
const sent = await sendEvents(endpoint, apiKey, [
|
|
1045
|
+
{
|
|
1046
|
+
event_id: crypto.randomUUID(),
|
|
1047
|
+
session_id: sessionId,
|
|
1048
|
+
event_type: "session.start",
|
|
1049
|
+
timestamp: startedAt,
|
|
1050
|
+
tool: "claude-code",
|
|
1051
|
+
metadata: {
|
|
1052
|
+
tool: "claude-code",
|
|
1053
|
+
model: process.env["CLAUDE_MODEL"] ?? "unknown",
|
|
1054
|
+
repository: process.env["CLAUDE_REPO"],
|
|
1055
|
+
branch: process.env["CLAUDE_BRANCH"],
|
|
1056
|
+
project_id: projectId
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
]);
|
|
1060
|
+
if (sent) {
|
|
1061
|
+
success(`Valt session started: ${sessionId}`);
|
|
1062
|
+
} 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
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
error(`Failed to start session: ${err instanceof Error ? err.message : String(err)}`);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
var toolCallCommand = new Command13("tool-call").description("Hook: called on each Claude Code tool call (reads JSON from stdin)").action(async () => {
|
|
1098
|
+
try {
|
|
1099
|
+
const session = readSessionFile();
|
|
1100
|
+
if (!session) {
|
|
1101
|
+
warn("No active Valt session. Run `valt hook session-start` first.");
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
let stdinData = "";
|
|
1105
|
+
if (!process.stdin.isTTY) {
|
|
1106
|
+
stdinData = await new Promise((resolve4) => {
|
|
1107
|
+
const chunks = [];
|
|
1108
|
+
process.stdin.setEncoding("utf-8");
|
|
1109
|
+
process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
|
|
1110
|
+
process.stdin.on("end", () => resolve4(chunks.join("")));
|
|
1111
|
+
setTimeout(() => resolve4(chunks.join("")), 1e3);
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
let toolData = {};
|
|
1115
|
+
if (stdinData.trim()) {
|
|
1116
|
+
try {
|
|
1117
|
+
toolData = JSON.parse(stdinData);
|
|
1118
|
+
} catch {
|
|
1119
|
+
toolData = { raw_input: stdinData.trim() };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const toolName = toolData["tool_name"] ?? toolData["name"] ?? "unknown";
|
|
1123
|
+
await sendEvents(session.endpoint, session.apiKey, [
|
|
1124
|
+
{
|
|
1125
|
+
event_id: crypto.randomUUID(),
|
|
1126
|
+
session_id: session.sessionId,
|
|
1127
|
+
event_type: "tool.call",
|
|
1128
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1129
|
+
tool: "claude-code",
|
|
1130
|
+
tool_name: toolName,
|
|
1131
|
+
metadata: toolData
|
|
1132
|
+
}
|
|
1133
|
+
]);
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
warn(`Tool call tracking failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
var sessionEndCommand = new Command13("session-end").description("Hook: called when a Claude Code session ends").action(async () => {
|
|
1139
|
+
try {
|
|
1140
|
+
const session = readSessionFile();
|
|
1141
|
+
if (!session) {
|
|
1142
|
+
warn("No active Valt session to end.");
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const now = /* @__PURE__ */ new Date();
|
|
1146
|
+
const startedAt = new Date(session.startedAt);
|
|
1147
|
+
const durationMs = now.getTime() - startedAt.getTime();
|
|
1148
|
+
const sent = await sendEvents(session.endpoint, session.apiKey, [
|
|
1149
|
+
{
|
|
1150
|
+
event_id: crypto.randomUUID(),
|
|
1151
|
+
session_id: session.sessionId,
|
|
1152
|
+
event_type: "session.end",
|
|
1153
|
+
timestamp: now.toISOString(),
|
|
1154
|
+
duration_ms: durationMs,
|
|
1155
|
+
tool: "claude-code",
|
|
1156
|
+
metadata: {
|
|
1157
|
+
started_at: session.startedAt,
|
|
1158
|
+
ended_at: now.toISOString()
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
]);
|
|
1162
|
+
try {
|
|
1163
|
+
unlinkSync2(SESSION_FILE);
|
|
1164
|
+
} catch {
|
|
1165
|
+
}
|
|
1166
|
+
if (sent) {
|
|
1167
|
+
success(`Valt session ended: ${session.sessionId} (${Math.round(durationMs / 1e3)}s)`);
|
|
1168
|
+
} else {
|
|
1169
|
+
warn("Session ended locally but failed to send to Valt.");
|
|
1170
|
+
}
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
error(`Failed to end session: ${err instanceof Error ? err.message : String(err)}`);
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
var hookCommand = new Command13("hook").description("Claude Code hook handlers for Valt session tracking").addCommand(sessionStartCommand).addCommand(toolCallCommand).addCommand(sessionEndCommand);
|
|
1176
|
+
|
|
1177
|
+
// src/commands/start.ts
|
|
1178
|
+
import { Command as Command14 } from "commander";
|
|
1179
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "fs";
|
|
1180
|
+
import { resolve as resolve3 } from "path";
|
|
1181
|
+
var HOOKS_PATH = ".claude/hooks.json";
|
|
1182
|
+
var VALT_HOOKS = {
|
|
1183
|
+
SessionStart: [{
|
|
1184
|
+
hooks: [{ type: "command", command: "npx @usevalt/cli hook session-start" }]
|
|
1185
|
+
}],
|
|
1186
|
+
PreToolCall: [{
|
|
1187
|
+
matcher: ".*",
|
|
1188
|
+
hooks: [{ type: "command", command: "echo '{}' | npx @usevalt/cli hook tool-call" }]
|
|
1189
|
+
}],
|
|
1190
|
+
Stop: [{
|
|
1191
|
+
hooks: [{ type: "command", command: "npx @usevalt/cli hook session-end" }]
|
|
1192
|
+
}]
|
|
1193
|
+
};
|
|
1194
|
+
var startCommand = new Command14("start").description("Configure Valt for Claude Code in the current project").action(() => {
|
|
1195
|
+
try {
|
|
1196
|
+
const apiKey = getApiKey();
|
|
1197
|
+
if (!apiKey) {
|
|
1198
|
+
error("No API key found. Set VALT_API_KEY or run `valt login` first.");
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
const claudeDir = resolve3(process.cwd(), ".claude");
|
|
1202
|
+
if (!existsSync4(claudeDir)) {
|
|
1203
|
+
warn("No .claude/ directory found. This command is designed for Claude Code projects.");
|
|
1204
|
+
info("Creating .claude/ directory...");
|
|
1205
|
+
const { mkdirSync: mkdirSync2 } = __require("fs");
|
|
1206
|
+
mkdirSync2(claudeDir, { recursive: true });
|
|
1207
|
+
}
|
|
1208
|
+
const hooksFile = resolve3(process.cwd(), HOOKS_PATH);
|
|
1209
|
+
let existingHooks = {};
|
|
1210
|
+
if (existsSync4(hooksFile)) {
|
|
1211
|
+
try {
|
|
1212
|
+
existingHooks = JSON.parse(readFileSync4(hooksFile, "utf-8"));
|
|
1213
|
+
info("Found existing hooks.json -- merging Valt hooks.");
|
|
1214
|
+
} catch {
|
|
1215
|
+
warn("Existing hooks.json is not valid JSON. Creating a new one.");
|
|
1216
|
+
existingHooks = {};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const hooks = existingHooks.hooks ?? {};
|
|
1220
|
+
for (const [event, valtEntries] of Object.entries(VALT_HOOKS)) {
|
|
1221
|
+
const existing = hooks[event];
|
|
1222
|
+
const hasValt = existing?.some(
|
|
1223
|
+
(entry) => entry.hooks?.some((h) => h.command?.includes("@usevalt/cli"))
|
|
1224
|
+
);
|
|
1225
|
+
if (hasValt) {
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
hooks[event] = [...existing ?? [], ...valtEntries];
|
|
1229
|
+
}
|
|
1230
|
+
const merged = { ...existingHooks, hooks };
|
|
1231
|
+
writeFileSync4(hooksFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
1232
|
+
success("Valt is configured.");
|
|
1233
|
+
info(`Hooks written to ${bold(HOOKS_PATH)}`);
|
|
1234
|
+
info("Start a Claude Code session and your work will be tracked automatically.");
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
error(`Failed to configure: ${err instanceof Error ? err.message : String(err)}`);
|
|
1237
|
+
process.exit(1);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
|
|
662
1241
|
// src/index.ts
|
|
663
|
-
var program = new
|
|
664
|
-
program.name("valt").description("Valt CLI
|
|
1242
|
+
var program = new Command15();
|
|
1243
|
+
program.name("valt").description("Valt CLI -- trust layer for AI-assisted development").version("0.2.0");
|
|
665
1244
|
program.addCommand(initCommand);
|
|
666
1245
|
program.addCommand(loginCommand);
|
|
667
1246
|
program.addCommand(statusCommand);
|
|
@@ -674,4 +1253,6 @@ program.addCommand(certCommand);
|
|
|
674
1253
|
program.addCommand(costsCommand);
|
|
675
1254
|
program.addCommand(analyzeCommand);
|
|
676
1255
|
program.addCommand(proxyCommand);
|
|
1256
|
+
program.addCommand(hookCommand);
|
|
1257
|
+
program.addCommand(startCommand);
|
|
677
1258
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usevalt/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Valt CLI — trust layer for AI-assisted development",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/
|
|
8
|
+
"url": "https://github.com/usevalt/valt",
|
|
9
9
|
"directory": "packages/cli"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
@@ -25,21 +25,6 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"commander": "^13.0.0",
|
|
30
|
-
"chalk": "^5.4.0",
|
|
31
|
-
"ora": "^8.1.0",
|
|
32
|
-
"@usevalt/shared": "0.1.0"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"tsup": "^8.4.0",
|
|
36
|
-
"typescript": "^5.7.0",
|
|
37
|
-
"vitest": "^3.2.0",
|
|
38
|
-
"@types/node": "^22.0.0",
|
|
39
|
-
"eslint": "^9.0.0",
|
|
40
|
-
"@usevalt/typescript-config": "0.0.0",
|
|
41
|
-
"@usevalt/eslint-config": "0.0.0"
|
|
42
|
-
},
|
|
43
28
|
"scripts": {
|
|
44
29
|
"build": "tsup",
|
|
45
30
|
"dev": "tsup --watch",
|
|
@@ -47,5 +32,22 @@
|
|
|
47
32
|
"lint": "eslint src/",
|
|
48
33
|
"typecheck": "tsc --noEmit",
|
|
49
34
|
"clean": "rm -rf dist"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@usevalt/shared": "workspace:*",
|
|
38
|
+
"chalk": "^5.4.0",
|
|
39
|
+
"commander": "^13.0.0",
|
|
40
|
+
"open": "^11.0.0",
|
|
41
|
+
"ora": "^8.1.0",
|
|
42
|
+
"zod": "^3.25.76"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"@usevalt/eslint-config": "workspace:*",
|
|
47
|
+
"@usevalt/typescript-config": "workspace:*",
|
|
48
|
+
"eslint": "^9.0.0",
|
|
49
|
+
"tsup": "^8.4.0",
|
|
50
|
+
"typescript": "^5.7.0",
|
|
51
|
+
"vitest": "^3.2.0"
|
|
50
52
|
}
|
|
51
|
-
}
|
|
53
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Valt
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|