@usevalt/cli 0.1.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +677 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @usevalt/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for [Valt](https://usevalt.com) — the trust layer for AI-assisted development.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @usevalt/cli
|
|
9
|
+
# or use npx
|
|
10
|
+
npx @usevalt/cli init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
valt init # Interactive setup — creates .valtrc.json
|
|
17
|
+
valt login # Authenticate via browser
|
|
18
|
+
valt doctor # Verify configuration and connectivity
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
|---------|-------------|
|
|
25
|
+
| `valt init` | Interactive project setup |
|
|
26
|
+
| `valt login` | Authenticate via browser OAuth |
|
|
27
|
+
| `valt status` | Show recent session activity |
|
|
28
|
+
| `valt sessions [id]` | List or view sessions |
|
|
29
|
+
| `valt analyze` | Run evaluation on current git diff |
|
|
30
|
+
| `valt verify <commit>` | Check Valt Verified status of a commit |
|
|
31
|
+
| `valt cert generate --session <id>` | Generate certificate for a session |
|
|
32
|
+
| `valt cert show <number>` | Display certificate details |
|
|
33
|
+
| `valt costs [--period 7d\|30d\|90d]` | Show cost summary |
|
|
34
|
+
| `valt proxy start\|status\|stop` | Manage MCP proxy |
|
|
35
|
+
| `valt config set\|get\|list` | Manage configuration |
|
|
36
|
+
| `valt whoami` | Show current user and org |
|
|
37
|
+
| `valt doctor` | Check configuration and connectivity |
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
Create `.valtrc.json` in your project root (or run `valt init`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"apiKey": "valt_sk_live_...",
|
|
46
|
+
"endpoint": "https://api.usevalt.com",
|
|
47
|
+
"projectSlug": "my-project"
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Configuration priority: environment variables > `.valtrc.json` in current or parent directories.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command13 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/lib/config.ts
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
var CONFIG_FILE = ".valtrc.json";
|
|
13
|
+
function findConfigPath(startDir = process.cwd()) {
|
|
14
|
+
let dir = startDir;
|
|
15
|
+
while (true) {
|
|
16
|
+
const candidate = resolve(dir, CONFIG_FILE);
|
|
17
|
+
if (existsSync(candidate)) return candidate;
|
|
18
|
+
const parent = resolve(dir, "..");
|
|
19
|
+
if (parent === dir) return null;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readConfig(dir = process.cwd()) {
|
|
24
|
+
const path = findConfigPath(dir);
|
|
25
|
+
if (!path) return {};
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function writeConfig(config, dir = process.cwd()) {
|
|
33
|
+
const path = resolve(dir, CONFIG_FILE);
|
|
34
|
+
writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
35
|
+
}
|
|
36
|
+
function getApiKey() {
|
|
37
|
+
const envKey = process.env.VALT_API_KEY;
|
|
38
|
+
if (envKey) return envKey;
|
|
39
|
+
const config = readConfig();
|
|
40
|
+
return config.apiKey ?? null;
|
|
41
|
+
}
|
|
42
|
+
function getEndpoint() {
|
|
43
|
+
const config = readConfig();
|
|
44
|
+
return config.endpoint ?? process.env.VALT_ENDPOINT ?? "https://api.usevalt.com";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/lib/output.ts
|
|
48
|
+
import chalk from "chalk";
|
|
49
|
+
function success(msg) {
|
|
50
|
+
console.log(chalk.green("\u2714") + " " + msg);
|
|
51
|
+
}
|
|
52
|
+
function error(msg) {
|
|
53
|
+
console.error(chalk.red("\u2716") + " " + msg);
|
|
54
|
+
}
|
|
55
|
+
function warn(msg) {
|
|
56
|
+
console.warn(chalk.yellow("\u26A0") + " " + msg);
|
|
57
|
+
}
|
|
58
|
+
function info(msg) {
|
|
59
|
+
console.log(chalk.blue("\u2139") + " " + msg);
|
|
60
|
+
}
|
|
61
|
+
function dim(msg) {
|
|
62
|
+
return chalk.dim(msg);
|
|
63
|
+
}
|
|
64
|
+
function bold(msg) {
|
|
65
|
+
return chalk.bold(msg);
|
|
66
|
+
}
|
|
67
|
+
function formatDate(iso) {
|
|
68
|
+
return new Date(iso).toLocaleString();
|
|
69
|
+
}
|
|
70
|
+
function formatDuration(ms) {
|
|
71
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
72
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
73
|
+
return `${Math.round(ms / 6e4)}m`;
|
|
74
|
+
}
|
|
75
|
+
function formatCost(usd) {
|
|
76
|
+
if (usd === 0) return "$0.00";
|
|
77
|
+
if (usd < 0.01) return "<$0.01";
|
|
78
|
+
return `$${usd.toFixed(2)}`;
|
|
79
|
+
}
|
|
80
|
+
function table(rows) {
|
|
81
|
+
if (rows.length === 0) return;
|
|
82
|
+
const colWidths = rows[0].map(
|
|
83
|
+
(_, colIdx) => Math.max(...rows.map((row) => (row[colIdx] ?? "").length))
|
|
84
|
+
);
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
console.log(
|
|
87
|
+
row.map((cell, i) => cell.padEnd(colWidths[i] + 2)).join("")
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/commands/init.ts
|
|
93
|
+
import { API_KEY_PREFIX } from "@usevalt/shared";
|
|
94
|
+
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
|
+
try {
|
|
96
|
+
const existing = readConfig();
|
|
97
|
+
const config = { ...existing };
|
|
98
|
+
if (opts.apiKey) {
|
|
99
|
+
if (!opts.apiKey.startsWith(API_KEY_PREFIX)) {
|
|
100
|
+
error(`API key must start with '${API_KEY_PREFIX}'`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
config.apiKey = opts.apiKey;
|
|
104
|
+
}
|
|
105
|
+
if (opts.endpoint) {
|
|
106
|
+
config.endpoint = opts.endpoint;
|
|
107
|
+
}
|
|
108
|
+
writeConfig(config);
|
|
109
|
+
success("Created .valtrc.json");
|
|
110
|
+
info("Next steps:");
|
|
111
|
+
if (!config.apiKey) {
|
|
112
|
+
info(" 1. Add your API key: valt init --api-key <key>");
|
|
113
|
+
}
|
|
114
|
+
info(" 2. Install the SDK: npm install @usevalt/sdk");
|
|
115
|
+
info(' 3. Start tracking: import { Valt } from "@usevalt/sdk"');
|
|
116
|
+
} catch (err) {
|
|
117
|
+
error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// src/commands/login.ts
|
|
123
|
+
import { Command as Command2 } from "commander";
|
|
124
|
+
var loginCommand = new Command2("login").description("Authenticate with Valt (opens browser)").action(async () => {
|
|
125
|
+
try {
|
|
126
|
+
info("Opening browser for authentication...");
|
|
127
|
+
info("Login URL: https://usevalt.com/login");
|
|
128
|
+
info("");
|
|
129
|
+
info("To authenticate:");
|
|
130
|
+
info(" 1. Go to https://usevalt.com/settings/api-keys");
|
|
131
|
+
info(" 2. Create an API key");
|
|
132
|
+
info(" 3. Run: valt init --api-key <your-key>");
|
|
133
|
+
} catch (err) {
|
|
134
|
+
error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// src/commands/status.ts
|
|
140
|
+
import { Command as Command3 } from "commander";
|
|
141
|
+
|
|
142
|
+
// src/lib/api.ts
|
|
143
|
+
var ApiError = class extends Error {
|
|
144
|
+
constructor(status, message) {
|
|
145
|
+
super(message);
|
|
146
|
+
this.status = status;
|
|
147
|
+
this.name = "ApiError";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
async function apiRequest(path, options = {}) {
|
|
151
|
+
const apiKey = getApiKey();
|
|
152
|
+
if (!apiKey) {
|
|
153
|
+
throw new ApiError(0, "No API key configured. Run `valt init` or set VALT_API_KEY.");
|
|
154
|
+
}
|
|
155
|
+
const endpoint = getEndpoint();
|
|
156
|
+
const url = `${endpoint}${path}`;
|
|
157
|
+
const response = await fetch(url, {
|
|
158
|
+
...options,
|
|
159
|
+
headers: {
|
|
160
|
+
"Content-Type": "application/json",
|
|
161
|
+
Authorization: `Bearer ${apiKey}`,
|
|
162
|
+
...options.headers
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
const body = await response.text().catch(() => "");
|
|
167
|
+
throw new ApiError(response.status, `API error ${response.status}: ${body}`);
|
|
168
|
+
}
|
|
169
|
+
return response.json();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/commands/status.ts
|
|
173
|
+
import ora from "ora";
|
|
174
|
+
var statusCommand = new Command3("status").description("Show recent session activity").option("-n, --limit <count>", "Number of sessions to show", "10").action(async (opts) => {
|
|
175
|
+
const spinner = ora("Fetching sessions...").start();
|
|
176
|
+
try {
|
|
177
|
+
const limit = parseInt(opts.limit, 10) || 10;
|
|
178
|
+
const data = await apiRequest(
|
|
179
|
+
`/v1/sessions?limit=${limit}`
|
|
180
|
+
);
|
|
181
|
+
spinner.stop();
|
|
182
|
+
if (data.sessions.length === 0) {
|
|
183
|
+
info("No sessions found. Start tracking with the SDK.");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log(bold(`Recent Sessions (${data.sessions.length})`));
|
|
187
|
+
console.log("");
|
|
188
|
+
const rows = [
|
|
189
|
+
["TOOL", "STATUS", "SCORE", "COST", "DURATION", "STARTED", "SUMMARY"]
|
|
190
|
+
];
|
|
191
|
+
for (const s of data.sessions) {
|
|
192
|
+
rows.push([
|
|
193
|
+
s.tool,
|
|
194
|
+
s.status,
|
|
195
|
+
s.trust_score !== null ? String(s.trust_score) : "-",
|
|
196
|
+
formatCost(s.total_cost_usd),
|
|
197
|
+
s.duration_ms ? formatDuration(s.duration_ms) : "-",
|
|
198
|
+
formatDate(s.started_at),
|
|
199
|
+
s.prompt_summary?.slice(0, 40) ?? "-"
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
table(rows);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
spinner.stop();
|
|
205
|
+
if (err instanceof ApiError) {
|
|
206
|
+
error(err.message);
|
|
207
|
+
} else {
|
|
208
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
209
|
+
}
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// src/commands/verify.ts
|
|
215
|
+
import { Command as Command4 } from "commander";
|
|
216
|
+
import ora2 from "ora";
|
|
217
|
+
var verifyCommand = new Command4("verify").description("Check Valt Verified status for a commit").argument("<commit>", "Git commit SHA to verify").action(async (commit) => {
|
|
218
|
+
const spinner = ora2("Verifying commit...").start();
|
|
219
|
+
try {
|
|
220
|
+
const data = await apiRequest(
|
|
221
|
+
`/v1/certificates/verify?commit=${encodeURIComponent(commit)}`
|
|
222
|
+
);
|
|
223
|
+
spinner.stop();
|
|
224
|
+
if (data.verified) {
|
|
225
|
+
success(`Valt Verified ${bold(data.certificate_number ?? "")}`);
|
|
226
|
+
info(` Trust Score: ${data.trust_score ?? "-"}`);
|
|
227
|
+
info(` Status: ${data.status ?? "-"}`);
|
|
228
|
+
info(` Signature Valid: ${data.signature_valid ? "Yes" : "No"}`);
|
|
229
|
+
if (data.issued_at) info(` Issued: ${data.issued_at}`);
|
|
230
|
+
} else {
|
|
231
|
+
warn("Not Valt Verified");
|
|
232
|
+
if (data.reason) info(` Reason: ${data.reason}`);
|
|
233
|
+
if (data.certificate_number) info(` Certificate: ${data.certificate_number}`);
|
|
234
|
+
}
|
|
235
|
+
} catch (err) {
|
|
236
|
+
spinner.stop();
|
|
237
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
238
|
+
warn("No certificate found for this commit.");
|
|
239
|
+
} else if (err instanceof ApiError) {
|
|
240
|
+
error(err.message);
|
|
241
|
+
} else {
|
|
242
|
+
error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
243
|
+
}
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// src/commands/doctor.ts
|
|
249
|
+
import { Command as Command5 } from "commander";
|
|
250
|
+
import { API_KEY_PREFIX as API_KEY_PREFIX2 } from "@usevalt/shared";
|
|
251
|
+
var doctorCommand = new Command5("doctor").description("Check Valt configuration and connectivity").action(async () => {
|
|
252
|
+
let issues = 0;
|
|
253
|
+
info("Checking Valt configuration...\n");
|
|
254
|
+
const configPath = findConfigPath();
|
|
255
|
+
if (configPath) {
|
|
256
|
+
success(`Config file: ${dim(configPath)}`);
|
|
257
|
+
} else {
|
|
258
|
+
warn("No .valtrc.json found. Run `valt init`.");
|
|
259
|
+
issues++;
|
|
260
|
+
}
|
|
261
|
+
const apiKey = getApiKey();
|
|
262
|
+
if (apiKey) {
|
|
263
|
+
if (apiKey.startsWith(API_KEY_PREFIX2)) {
|
|
264
|
+
success(`API key: ${dim(apiKey.slice(0, 12) + "...")}`);
|
|
265
|
+
} else {
|
|
266
|
+
error(`API key has invalid prefix (expected ${API_KEY_PREFIX2})`);
|
|
267
|
+
issues++;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
warn("No API key configured. Set VALT_API_KEY or run `valt init --api-key <key>`.");
|
|
271
|
+
issues++;
|
|
272
|
+
}
|
|
273
|
+
const endpoint = getEndpoint();
|
|
274
|
+
success(`Endpoint: ${dim(endpoint)}`);
|
|
275
|
+
if (apiKey) {
|
|
276
|
+
try {
|
|
277
|
+
const response = await fetch(`${endpoint}/health`, {
|
|
278
|
+
signal: AbortSignal.timeout(5e3)
|
|
279
|
+
});
|
|
280
|
+
if (response.ok) {
|
|
281
|
+
success("API connectivity: OK");
|
|
282
|
+
} else {
|
|
283
|
+
warn(`API returned status ${response.status}`);
|
|
284
|
+
issues++;
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
error("Cannot reach API endpoint");
|
|
288
|
+
issues++;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
console.log("");
|
|
292
|
+
if (issues === 0) {
|
|
293
|
+
success("All checks passed!");
|
|
294
|
+
} else {
|
|
295
|
+
warn(`${issues} issue${issues > 1 ? "s" : ""} found.`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// src/commands/whoami.ts
|
|
300
|
+
import { Command as Command6 } from "commander";
|
|
301
|
+
var whoamiCommand = new Command6("whoami").description("Show current authentication status").action(async () => {
|
|
302
|
+
const apiKey = getApiKey();
|
|
303
|
+
const config = readConfig();
|
|
304
|
+
if (!apiKey) {
|
|
305
|
+
info("Not authenticated. Run `valt init --api-key <key>` to configure.");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
info(bold("Current Configuration"));
|
|
309
|
+
info(` API Key: ${dim(apiKey.slice(0, 12) + "...")}`);
|
|
310
|
+
info(` Endpoint: ${dim(getEndpoint())}`);
|
|
311
|
+
if (config.projectSlug) {
|
|
312
|
+
info(` Project: ${dim(config.projectSlug)}`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// src/commands/config.ts
|
|
317
|
+
import { Command as Command7 } from "commander";
|
|
318
|
+
var configCommand = new Command7("config").description("Manage Valt configuration");
|
|
319
|
+
configCommand.command("set <key> <value>").description("Set a config value").action((key, value) => {
|
|
320
|
+
try {
|
|
321
|
+
const config = readConfig();
|
|
322
|
+
config[key] = value;
|
|
323
|
+
writeConfig(config);
|
|
324
|
+
success(`Set ${key} = ${value}`);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
error(`Failed to set config: ${err instanceof Error ? err.message : String(err)}`);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
configCommand.command("get <key>").description("Get a config value").action((key) => {
|
|
330
|
+
try {
|
|
331
|
+
const config = readConfig();
|
|
332
|
+
if (!(key in config)) {
|
|
333
|
+
error(`Key "${key}" not found`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
console.log(config[key]);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
error(`Failed to get config: ${err instanceof Error ? err.message : String(err)}`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
configCommand.command("list").description("List all config values").action(() => {
|
|
342
|
+
try {
|
|
343
|
+
const config = readConfig();
|
|
344
|
+
const configPath = findConfigPath();
|
|
345
|
+
const entries = Object.entries(config);
|
|
346
|
+
if (entries.length === 0) {
|
|
347
|
+
console.log(dim("No config found. Run `valt init` to create one."));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
console.log(dim(`Config file: ${configPath ?? "not found"}`));
|
|
351
|
+
for (const [key, value] of entries) {
|
|
352
|
+
console.log(` ${bold(key)}: ${value}`);
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
error(`Failed to list config: ${err instanceof Error ? err.message : String(err)}`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// src/commands/sessions.ts
|
|
360
|
+
import { Command as Command8 } from "commander";
|
|
361
|
+
import ora3 from "ora";
|
|
362
|
+
var sessionsCommand = new Command8("sessions").description("View session details").argument("<id>", "Session ID").action(async (id) => {
|
|
363
|
+
const spinner = ora3("Fetching session...").start();
|
|
364
|
+
try {
|
|
365
|
+
const session = await apiRequest(
|
|
366
|
+
`/v1/sessions/${encodeURIComponent(id)}`
|
|
367
|
+
);
|
|
368
|
+
spinner.stop();
|
|
369
|
+
const s = session.data;
|
|
370
|
+
console.log(bold(`Session: ${id}`));
|
|
371
|
+
console.log("");
|
|
372
|
+
table([
|
|
373
|
+
["Tool", s.tool ?? "-"],
|
|
374
|
+
["Model", s.model ?? "-"],
|
|
375
|
+
["Status", s.status ?? "-"],
|
|
376
|
+
["Trust Score", s.trust_score !== null ? String(s.trust_score) : "-"],
|
|
377
|
+
["Cost", s.total_cost_usd !== null ? formatCost(s.total_cost_usd) : "-"],
|
|
378
|
+
["Duration", s.duration_ms !== null ? formatDuration(s.duration_ms) : "-"],
|
|
379
|
+
["Branch", s.git_branch ?? "-"],
|
|
380
|
+
["Started", s.started_at ? formatDate(s.started_at) : "-"]
|
|
381
|
+
]);
|
|
382
|
+
if (s.tags && s.tags.length > 0) {
|
|
383
|
+
console.log("");
|
|
384
|
+
console.log(dim(`Tags: ${s.tags.join(", ")}`));
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
387
|
+
spinner.stop();
|
|
388
|
+
if (err instanceof ApiError) {
|
|
389
|
+
error(err.message);
|
|
390
|
+
} else {
|
|
391
|
+
error(`Failed to fetch session: ${err instanceof Error ? err.message : String(err)}`);
|
|
392
|
+
}
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// src/commands/cert.ts
|
|
398
|
+
import { Command as Command9 } from "commander";
|
|
399
|
+
import ora4 from "ora";
|
|
400
|
+
var certCommand = new Command9("cert").description("Manage certificates");
|
|
401
|
+
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
|
+
const spinner = ora4("Generating certificate...").start();
|
|
403
|
+
try {
|
|
404
|
+
const result = await apiRequest("/v1/certificates", {
|
|
405
|
+
method: "POST",
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
session_id: opts.session,
|
|
408
|
+
force: opts.force ?? false
|
|
409
|
+
})
|
|
410
|
+
});
|
|
411
|
+
spinner.stop();
|
|
412
|
+
success(`Certificate generated: ${result.data.certificate_number} (score: ${result.data.trust_score})`);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
spinner.stop();
|
|
415
|
+
if (err instanceof ApiError) {
|
|
416
|
+
error(err.message);
|
|
417
|
+
} else {
|
|
418
|
+
error(`Failed to generate certificate: ${err instanceof Error ? err.message : String(err)}`);
|
|
419
|
+
}
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
certCommand.command("show <number>").description("Show certificate details").action(async (certNumber) => {
|
|
424
|
+
const spinner = ora4("Fetching certificate...").start();
|
|
425
|
+
try {
|
|
426
|
+
const result = await apiRequest(
|
|
427
|
+
`/v1/certificates?certificate_number=${encodeURIComponent(certNumber)}`
|
|
428
|
+
);
|
|
429
|
+
spinner.stop();
|
|
430
|
+
const certs = result.data;
|
|
431
|
+
if (!certs || certs.length === 0) {
|
|
432
|
+
error(`No certificate found with number: ${certNumber}`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const cert = certs[0];
|
|
436
|
+
console.log(bold(`Certificate: ${cert.certificate_number}`));
|
|
437
|
+
console.log("");
|
|
438
|
+
table([
|
|
439
|
+
["Status", cert.status],
|
|
440
|
+
["Trust Score", String(cert.trust_score)],
|
|
441
|
+
["Session", cert.session_id],
|
|
442
|
+
["Git Commit", cert.git_commit ?? "-"],
|
|
443
|
+
["Issued", cert.issued_at]
|
|
444
|
+
]);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
spinner.stop();
|
|
447
|
+
if (err instanceof ApiError) {
|
|
448
|
+
error(err.message);
|
|
449
|
+
} else {
|
|
450
|
+
error(`Failed to fetch certificate: ${err instanceof Error ? err.message : String(err)}`);
|
|
451
|
+
}
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// src/commands/costs.ts
|
|
457
|
+
import { Command as Command10 } from "commander";
|
|
458
|
+
import ora5 from "ora";
|
|
459
|
+
var costsCommand = new Command10("costs").description("View cost breakdown").option("--period <period>", "Time period (7d, 30d, 90d)", "30d").action(async (opts) => {
|
|
460
|
+
const spinner = ora5("Fetching costs...").start();
|
|
461
|
+
try {
|
|
462
|
+
const days = opts.period.replace("d", "");
|
|
463
|
+
const result = await apiRequest(
|
|
464
|
+
`/v1/analytics/costs?days=${encodeURIComponent(days)}`
|
|
465
|
+
);
|
|
466
|
+
spinner.stop();
|
|
467
|
+
const data = result.data;
|
|
468
|
+
console.log(bold(`Total Cost: ${formatCost(data.total_cost_usd)}`));
|
|
469
|
+
console.log("");
|
|
470
|
+
if (data.cost_by_tool && data.cost_by_tool.length > 0) {
|
|
471
|
+
console.log(dim("By Tool:"));
|
|
472
|
+
table(data.cost_by_tool.map((t) => [
|
|
473
|
+
t.tool,
|
|
474
|
+
formatCost(t.totalCost),
|
|
475
|
+
`${t.sessionCount} sessions`
|
|
476
|
+
]));
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
spinner.stop();
|
|
480
|
+
if (err instanceof ApiError) {
|
|
481
|
+
error(err.message);
|
|
482
|
+
} else {
|
|
483
|
+
error(`Failed to fetch costs: ${err instanceof Error ? err.message : String(err)}`);
|
|
484
|
+
}
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// src/commands/analyze.ts
|
|
490
|
+
import { Command as Command11 } from "commander";
|
|
491
|
+
import ora6 from "ora";
|
|
492
|
+
var analyzeCommand = new Command11("analyze").description("Analyze a session or commit").option("--commit <sha>", "Git commit SHA to analyze").option("--session <id>", "Session ID to analyze").action(async (opts) => {
|
|
493
|
+
const spinner = ora6("Analyzing...").start();
|
|
494
|
+
try {
|
|
495
|
+
let sessionId = opts.session;
|
|
496
|
+
if (opts.commit && !sessionId) {
|
|
497
|
+
const sessions = await apiRequest(
|
|
498
|
+
`/v1/sessions?git_commit=${encodeURIComponent(opts.commit)}`
|
|
499
|
+
);
|
|
500
|
+
if (!sessions.sessions || sessions.sessions.length === 0) {
|
|
501
|
+
spinner.stop();
|
|
502
|
+
error(`No session found for commit: ${opts.commit}`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
sessionId = sessions.sessions[0].id;
|
|
506
|
+
}
|
|
507
|
+
if (!sessionId) {
|
|
508
|
+
spinner.stop();
|
|
509
|
+
error("Provide --session <id> or --commit <sha>");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const result = await apiRequest(
|
|
513
|
+
`/v1/sessions/${encodeURIComponent(sessionId)}`
|
|
514
|
+
);
|
|
515
|
+
spinner.stop();
|
|
516
|
+
const s = result.data;
|
|
517
|
+
console.log(bold("Trust Score Analysis"));
|
|
518
|
+
console.log("");
|
|
519
|
+
if (s.trust_score_breakdown) {
|
|
520
|
+
table([
|
|
521
|
+
["Security", String(s.trust_score_breakdown.security ?? "-")],
|
|
522
|
+
["Tests", String(s.trust_score_breakdown.tests ?? "-")],
|
|
523
|
+
["Architecture", String(s.trust_score_breakdown.architecture ?? "-")],
|
|
524
|
+
["Human Review", String(s.trust_score_breakdown.human_review ?? "-")],
|
|
525
|
+
["Governance", String(s.trust_score_breakdown.governance ?? "-")]
|
|
526
|
+
]);
|
|
527
|
+
console.log("");
|
|
528
|
+
console.log(bold(`Composite Score: ${s.trust_score ?? "-"}`));
|
|
529
|
+
} else {
|
|
530
|
+
console.log(dim("Session has not been evaluated yet."));
|
|
531
|
+
}
|
|
532
|
+
} catch (err) {
|
|
533
|
+
spinner.stop();
|
|
534
|
+
if (err instanceof ApiError) {
|
|
535
|
+
error(err.message);
|
|
536
|
+
} else {
|
|
537
|
+
error(`Failed to analyze: ${err instanceof Error ? err.message : String(err)}`);
|
|
538
|
+
}
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// src/commands/proxy.ts
|
|
544
|
+
import { Command as Command12 } from "commander";
|
|
545
|
+
import ora7 from "ora";
|
|
546
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, unlinkSync } from "fs";
|
|
547
|
+
import { resolve as resolve2 } from "path";
|
|
548
|
+
import { spawn } from "child_process";
|
|
549
|
+
var PID_FILE = resolve2(process.env.HOME ?? ".", ".valt", "proxy.pid");
|
|
550
|
+
function readPid() {
|
|
551
|
+
try {
|
|
552
|
+
if (!existsSync2(PID_FILE)) return null;
|
|
553
|
+
const pid = parseInt(readFileSync2(PID_FILE, "utf-8").trim(), 10);
|
|
554
|
+
return isNaN(pid) ? null : pid;
|
|
555
|
+
} catch {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function writePid(pid) {
|
|
560
|
+
const dir = resolve2(process.env.HOME ?? ".", ".valt");
|
|
561
|
+
if (!existsSync2(dir)) {
|
|
562
|
+
mkdirSync(dir, { recursive: true });
|
|
563
|
+
}
|
|
564
|
+
writeFileSync2(PID_FILE, String(pid));
|
|
565
|
+
}
|
|
566
|
+
function isProcessRunning(pid) {
|
|
567
|
+
try {
|
|
568
|
+
process.kill(pid, 0);
|
|
569
|
+
return true;
|
|
570
|
+
} catch {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
var proxyCommand = new Command12("proxy").description("Manage MCP proxy for transparent tool call logging");
|
|
575
|
+
proxyCommand.command("start").description("Start the MCP proxy").option("-p, --port <port>", "Proxy port", "3100").option("--upstream <server>", "Upstream MCP server command").action(async (opts) => {
|
|
576
|
+
const existingPid = readPid();
|
|
577
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
578
|
+
warn(`Proxy is already running (PID ${existingPid}). Use 'valt proxy stop' first.`);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const apiKey = getApiKey();
|
|
582
|
+
if (!apiKey) {
|
|
583
|
+
error("No API key configured. Run `valt init` first.");
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
const config = readConfig();
|
|
587
|
+
const spinner = ora7("Starting MCP proxy...").start();
|
|
588
|
+
try {
|
|
589
|
+
const args = ["@usevalt/mcp-proxy", "--port", opts.port];
|
|
590
|
+
if (opts.upstream) {
|
|
591
|
+
args.push("--upstream", opts.upstream);
|
|
592
|
+
}
|
|
593
|
+
const child = spawn("npx", args, {
|
|
594
|
+
detached: true,
|
|
595
|
+
stdio: "ignore",
|
|
596
|
+
env: {
|
|
597
|
+
...process.env,
|
|
598
|
+
VALT_API_KEY: apiKey,
|
|
599
|
+
VALT_PROJECT: config.projectSlug ?? ""
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
child.unref();
|
|
603
|
+
if (child.pid) {
|
|
604
|
+
writePid(child.pid);
|
|
605
|
+
spinner.stop();
|
|
606
|
+
success(`MCP proxy started on port ${opts.port} (PID ${child.pid})`);
|
|
607
|
+
info("Configure your AI tool to use the proxy. See: https://usevalt.com/docs/proxy");
|
|
608
|
+
} else {
|
|
609
|
+
spinner.stop();
|
|
610
|
+
error("Failed to start proxy process.");
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
} catch (err) {
|
|
614
|
+
spinner.stop();
|
|
615
|
+
error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
proxyCommand.command("status").description("Check proxy status").action(() => {
|
|
620
|
+
const pid = readPid();
|
|
621
|
+
if (!pid) {
|
|
622
|
+
info("Proxy is not running.");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (isProcessRunning(pid)) {
|
|
626
|
+
success(`Proxy is running (PID ${pid})`);
|
|
627
|
+
} else {
|
|
628
|
+
info("Proxy is not running (stale PID file).");
|
|
629
|
+
try {
|
|
630
|
+
unlinkSync(PID_FILE);
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
proxyCommand.command("stop").description("Stop the MCP proxy").action(() => {
|
|
636
|
+
const pid = readPid();
|
|
637
|
+
if (!pid) {
|
|
638
|
+
info("Proxy is not running.");
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (!isProcessRunning(pid)) {
|
|
642
|
+
info("Proxy is not running (stale PID file).");
|
|
643
|
+
try {
|
|
644
|
+
unlinkSync(PID_FILE);
|
|
645
|
+
} catch {
|
|
646
|
+
}
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
process.kill(pid, "SIGTERM");
|
|
651
|
+
try {
|
|
652
|
+
unlinkSync(PID_FILE);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
success(`Proxy stopped (PID ${pid})`);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
error(`Failed to stop proxy: ${err instanceof Error ? err.message : String(err)}`);
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// src/index.ts
|
|
663
|
+
var program = new Command13();
|
|
664
|
+
program.name("valt").description("Valt CLI \u2014 trust layer for AI-assisted development").version("0.1.0");
|
|
665
|
+
program.addCommand(initCommand);
|
|
666
|
+
program.addCommand(loginCommand);
|
|
667
|
+
program.addCommand(statusCommand);
|
|
668
|
+
program.addCommand(verifyCommand);
|
|
669
|
+
program.addCommand(doctorCommand);
|
|
670
|
+
program.addCommand(whoamiCommand);
|
|
671
|
+
program.addCommand(configCommand);
|
|
672
|
+
program.addCommand(sessionsCommand);
|
|
673
|
+
program.addCommand(certCommand);
|
|
674
|
+
program.addCommand(costsCommand);
|
|
675
|
+
program.addCommand(analyzeCommand);
|
|
676
|
+
program.addCommand(proxyCommand);
|
|
677
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usevalt/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Valt CLI — trust layer for AI-assisted development",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/valtdev/valt",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"valt",
|
|
13
|
+
"ai",
|
|
14
|
+
"cli",
|
|
15
|
+
"coding-agents",
|
|
16
|
+
"trust",
|
|
17
|
+
"certificates"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://usevalt.com",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"valt": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
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
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"lint": "eslint src/",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"clean": "rm -rf dist"
|
|
50
|
+
}
|
|
51
|
+
}
|