@llamaventures/cli 1.15.1 → 1.17.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/AGENT_BRIEFING.md +17 -7
- package/CHANGELOG.md +70 -1
- package/README.md +31 -6
- package/README.zh-CN.md +5 -4
- package/bin/llama-mcp.mjs +412 -25
- package/bin/llama.mjs +596 -38
- package/lib/client.mjs +353 -4
- package/package.json +3 -2
- package/scripts/verify-agent-routing.mjs +387 -18
package/lib/client.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import path from "path";
|
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { execFile as _execFile } from "child_process";
|
|
15
15
|
import { promisify } from "util";
|
|
16
|
+
import { createHash, randomUUID } from "crypto";
|
|
16
17
|
|
|
17
18
|
const execFile = promisify(_execFile);
|
|
18
19
|
|
|
@@ -43,6 +44,19 @@ export function readBriefing() {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
let packageVersionCache = null;
|
|
48
|
+
|
|
49
|
+
export function getPackageVersion() {
|
|
50
|
+
if (packageVersionCache) return packageVersionCache;
|
|
51
|
+
try {
|
|
52
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
53
|
+
packageVersionCache = String(pkg.version || "unknown");
|
|
54
|
+
} catch {
|
|
55
|
+
packageVersionCache = "unknown";
|
|
56
|
+
}
|
|
57
|
+
return packageVersionCache;
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
// Canonical entrypoint. `llama-command.onrender.com` also serves the API
|
|
47
61
|
// but its NextAuth callback URL doesn't match, so browser login (needed
|
|
48
62
|
// to mint a token at /settings/tokens) fails there with a server-config
|
|
@@ -54,6 +68,7 @@ export const DEFAULT_BASE_URL = "https://command.llamaventures.vc";
|
|
|
54
68
|
// agent-discovery convention.
|
|
55
69
|
export const TOKEN_DIR = path.join(os.homedir(), ".llama");
|
|
56
70
|
export const TOKEN_FILE = path.join(TOKEN_DIR, "token");
|
|
71
|
+
export const AGENT_SESSION_FILE = path.join(TOKEN_DIR, "agent-session.json");
|
|
57
72
|
|
|
58
73
|
// Legacy location used by CLI v0.1. Read for back-compat (silent migrate
|
|
59
74
|
// to canonical on first use); never written for the token, but still the
|
|
@@ -123,6 +138,285 @@ export function getToken() {
|
|
|
123
138
|
return "";
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
let runtimeClient = "cli";
|
|
142
|
+
let runtimeAgentClient = null;
|
|
143
|
+
|
|
144
|
+
export function setClientRuntime(opts = {}) {
|
|
145
|
+
if (opts.client) runtimeClient = String(opts.client);
|
|
146
|
+
if (opts.agentClient) runtimeAgentClient = String(opts.agentClient);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function detectAgentClient() {
|
|
150
|
+
if (runtimeAgentClient) return runtimeAgentClient;
|
|
151
|
+
if (process.env.LLAMA_AGENT_CLIENT) return process.env.LLAMA_AGENT_CLIENT;
|
|
152
|
+
if (process.env.CODEX_SANDBOX || process.env.CODEX_CLI || process.env.OPENAI_CODEX) return "codex";
|
|
153
|
+
if (process.env.CLAUDECODE || process.env.CLAUDE_CODE || process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
154
|
+
return "claude-code";
|
|
155
|
+
}
|
|
156
|
+
if (process.env.CURSOR_AGENT || process.env.CURSOR_TRACE_ID) return "cursor";
|
|
157
|
+
return "unknown";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function readAgentSession() {
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(fs.readFileSync(AGENT_SESSION_FILE, "utf8"));
|
|
163
|
+
} catch {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeAgentSession(session) {
|
|
169
|
+
try {
|
|
170
|
+
fs.mkdirSync(TOKEN_DIR, { recursive: true, mode: 0o700 });
|
|
171
|
+
fs.writeFileSync(AGENT_SESSION_FILE, `${JSON.stringify(session, null, 2)}\n`, { mode: 0o600 });
|
|
172
|
+
fs.chmodSync(AGENT_SESSION_FILE, 0o600);
|
|
173
|
+
} catch {
|
|
174
|
+
// Telemetry state is best-effort. Never break the actual CLI command.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function currentAgentSessionId() {
|
|
179
|
+
const session = readAgentSession();
|
|
180
|
+
if (session.sessionId) return session.sessionId;
|
|
181
|
+
const created = {
|
|
182
|
+
sessionId: randomUUID(),
|
|
183
|
+
createdAt: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
writeAgentSession(created);
|
|
186
|
+
return created.sessionId;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getLastAgentEvent() {
|
|
190
|
+
const session = readAgentSession();
|
|
191
|
+
return session.lastEventId ? session : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function rememberAgentEvent(event) {
|
|
195
|
+
if (!event?.eventId) return;
|
|
196
|
+
const session = {
|
|
197
|
+
...readAgentSession(),
|
|
198
|
+
sessionId: event.sessionId || currentAgentSessionId(),
|
|
199
|
+
lastEventId: event.eventId,
|
|
200
|
+
lastCandidateId: event.candidateId ?? null,
|
|
201
|
+
lastCommand: event.command ?? null,
|
|
202
|
+
lastQuery: event.query ?? null,
|
|
203
|
+
lastSurface: event.surface ?? null,
|
|
204
|
+
lastRecordedAt: new Date().toISOString(),
|
|
205
|
+
};
|
|
206
|
+
writeAgentSession(session);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function agentClientHeaders(command) {
|
|
210
|
+
return {
|
|
211
|
+
"X-Llama-Client": runtimeClient,
|
|
212
|
+
"X-Llama-Client-Version": getPackageVersion(),
|
|
213
|
+
"X-Llama-Agent-Client": detectAgentClient(),
|
|
214
|
+
"X-Llama-Agent-Session": currentAgentSessionId(),
|
|
215
|
+
"X-Llama-Command": command || "unknown",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const SECRET_KEY_RE = /(token|secret|password|authorization|cookie|api[_-]?key|keychain|jwt)/i;
|
|
220
|
+
const CONTENT_PAYLOAD_KEY_RE = /(^|_)(html|body|content|markdown|message|text)$/i;
|
|
221
|
+
|
|
222
|
+
function truncateText(text, max = 2000) {
|
|
223
|
+
return text.length > max ? `${text.slice(0, max)}...[truncated]` : text;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function summarizePayloadText(value) {
|
|
227
|
+
const text = String(value ?? "");
|
|
228
|
+
return {
|
|
229
|
+
redacted: true,
|
|
230
|
+
type: "text_payload",
|
|
231
|
+
chars: text.length,
|
|
232
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
233
|
+
sha256: createHash("sha256").update(text).digest("hex"),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function sanitizeTelemetryValue(value, depth = 0, keyHint = "") {
|
|
238
|
+
if (depth > 4) return "[max-depth]";
|
|
239
|
+
if (value === null || value === undefined) return value;
|
|
240
|
+
if (typeof value === "string") {
|
|
241
|
+
return CONTENT_PAYLOAD_KEY_RE.test(keyHint)
|
|
242
|
+
? summarizePayloadText(value)
|
|
243
|
+
: truncateText(value);
|
|
244
|
+
}
|
|
245
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
246
|
+
if (Array.isArray(value)) {
|
|
247
|
+
return value.slice(0, 20).map((item) => sanitizeTelemetryValue(item, depth + 1, keyHint));
|
|
248
|
+
}
|
|
249
|
+
if (typeof value === "object") {
|
|
250
|
+
const out = {};
|
|
251
|
+
for (const [key, val] of Object.entries(value).slice(0, 40)) {
|
|
252
|
+
out[key] = SECRET_KEY_RE.test(key) ? "[redacted]" : sanitizeTelemetryValue(val, depth + 1, key);
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
return String(value);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseEndpoint(endpoint) {
|
|
260
|
+
try {
|
|
261
|
+
return new URL(endpoint, "https://command.llamaventures.vc");
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function endpointArgs(endpoint, body) {
|
|
268
|
+
const args = {};
|
|
269
|
+
const url = parseEndpoint(endpoint);
|
|
270
|
+
if (url) {
|
|
271
|
+
for (const [key, value] of url.searchParams.entries()) args[key] = value;
|
|
272
|
+
}
|
|
273
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
274
|
+
Object.assign(args, body);
|
|
275
|
+
}
|
|
276
|
+
return sanitizeTelemetryValue(args);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function inferCommand(method, endpoint) {
|
|
280
|
+
const url = parseEndpoint(endpoint);
|
|
281
|
+
const pathname = url?.pathname || endpoint.split("?")[0] || "";
|
|
282
|
+
const verb = String(method || "GET").toUpperCase();
|
|
283
|
+
if (pathname === "/api/agent/client-events") return "telemetry.record";
|
|
284
|
+
if (pathname === "/api/agent/eval-feedback") return "eval.feedback";
|
|
285
|
+
if (pathname === "/api/wiki/search") return "wiki.search";
|
|
286
|
+
if (pathname === "/api/wiki/save") return "wiki.save";
|
|
287
|
+
if (/^\/api\/wiki\/[^/]+$/.test(pathname)) return verb === "GET" ? "wiki.read" : "wiki.write";
|
|
288
|
+
if (pathname === "/api/deals") return verb === "GET" ? "deal.search" : "deal.write";
|
|
289
|
+
if (pathname === "/api/deals/create") return "deal.create";
|
|
290
|
+
if (pathname === "/api/deals/update") return "deal.update";
|
|
291
|
+
if (/^\/api\/deals\/[^/]+\/threads\/[^/]+$/.test(pathname)) return "deal.agent.run";
|
|
292
|
+
if (/^\/api\/deals\/[^/]+\/threads$/.test(pathname)) return "deal.thread.create";
|
|
293
|
+
if (/^\/api\/deals\/[^/]+\/facts/.test(pathname)) return verb === "GET" ? "deal.fact.list" : "deal.fact.write";
|
|
294
|
+
if (/^\/api\/deals\/[^/]+\/posts$/.test(pathname)) return "deal.post";
|
|
295
|
+
if (/^\/api\/deals\/[^/]+\/blocks/.test(pathname)) return verb === "GET" ? "brief.blocks" : "brief.write";
|
|
296
|
+
if (/^\/api\/deals\/[^/]+$/.test(pathname)) return verb === "GET" ? "deal.show" : "deal.write";
|
|
297
|
+
if (pathname === "/api/me") return "auth.status";
|
|
298
|
+
if (pathname.startsWith("/api/agent/skills")) return "skills.read";
|
|
299
|
+
if (pathname === "/api/agent/manifest") return "agent.bootstrap";
|
|
300
|
+
if (pathname === "/api/agent/briefing") return "agent.briefing";
|
|
301
|
+
return `${verb.toLowerCase()} ${pathname || endpoint}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function queryForCommand(command, args) {
|
|
305
|
+
if (!command.endsWith(".search")) return null;
|
|
306
|
+
return args?.q || args?.search || args?.query || null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function summarizeResultIds(data) {
|
|
310
|
+
const result = {};
|
|
311
|
+
const topDeals = [];
|
|
312
|
+
const topWiki = [];
|
|
313
|
+
|
|
314
|
+
const collectDeal = (deal) => {
|
|
315
|
+
const id = deal?.uuid || deal?.id || deal?.dealId || deal?.deal_uuid;
|
|
316
|
+
if (!id) return;
|
|
317
|
+
topDeals.push({ id, name: deal.companyName || deal.company_name || deal.name || null });
|
|
318
|
+
};
|
|
319
|
+
const collectWiki = (item) => {
|
|
320
|
+
if (!item?.slug) return;
|
|
321
|
+
topWiki.push({ slug: item.slug, title: item.title || null });
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
if (Array.isArray(data)) {
|
|
325
|
+
for (const item of data.slice(0, 20)) {
|
|
326
|
+
collectDeal(item);
|
|
327
|
+
collectWiki(item);
|
|
328
|
+
}
|
|
329
|
+
result.resultCount = data.length;
|
|
330
|
+
} else if (data && typeof data === "object") {
|
|
331
|
+
const deals = Array.isArray(data.deals) ? data.deals : [];
|
|
332
|
+
const articles = Array.isArray(data.articles) ? data.articles : [];
|
|
333
|
+
const results = Array.isArray(data.results) ? data.results : [];
|
|
334
|
+
for (const deal of deals.slice(0, 20)) collectDeal(deal);
|
|
335
|
+
for (const item of [...articles, ...results].slice(0, 20)) collectWiki(item);
|
|
336
|
+
if (typeof data.total === "number") result.total = data.total;
|
|
337
|
+
if (deals.length) result.resultCount = deals.length;
|
|
338
|
+
if (articles.length || results.length) result.resultCount = articles.length + results.length;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (topDeals.length) result.deals = topDeals;
|
|
342
|
+
if (topWiki.length) result.wiki = topWiki;
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function summarizeResult(data) {
|
|
347
|
+
if (data === null || data === undefined) return null;
|
|
348
|
+
if (Array.isArray(data)) return `${data.length} result(s)`;
|
|
349
|
+
if (typeof data === "object") {
|
|
350
|
+
if (Array.isArray(data.deals)) return `${data.deals.length} deal result(s); total=${data.total ?? "unknown"}`;
|
|
351
|
+
if (Array.isArray(data.results)) return `${data.results.length} result(s)`;
|
|
352
|
+
if (data.ok !== undefined) return `ok=${Boolean(data.ok)}`;
|
|
353
|
+
}
|
|
354
|
+
return truncateText(typeof data === "string" ? data : JSON.stringify(sanitizeTelemetryValue(data)), 2000);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function shouldSkipTelemetry(endpoint) {
|
|
358
|
+
if (process.env.LLAMA_TELEMETRY === "0") return true;
|
|
359
|
+
const pathname = parseEndpoint(endpoint)?.pathname || endpoint;
|
|
360
|
+
return pathname === "/api/agent/client-events" || pathname === "/api/agent/eval-feedback";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function recordClientTelemetry({
|
|
364
|
+
authHeaders,
|
|
365
|
+
method,
|
|
366
|
+
endpoint,
|
|
367
|
+
body,
|
|
368
|
+
command,
|
|
369
|
+
status,
|
|
370
|
+
httpStatus,
|
|
371
|
+
latencyMs,
|
|
372
|
+
data,
|
|
373
|
+
errorMessage,
|
|
374
|
+
}) {
|
|
375
|
+
if (shouldSkipTelemetry(endpoint)) return;
|
|
376
|
+
const args = endpointArgs(endpoint, body);
|
|
377
|
+
const sessionId = currentAgentSessionId();
|
|
378
|
+
const payload = {
|
|
379
|
+
client: runtimeClient,
|
|
380
|
+
clientVersion: getPackageVersion(),
|
|
381
|
+
agentClient: detectAgentClient(),
|
|
382
|
+
sessionId,
|
|
383
|
+
command,
|
|
384
|
+
method: String(method || "GET").toUpperCase(),
|
|
385
|
+
endpoint,
|
|
386
|
+
status,
|
|
387
|
+
httpStatus,
|
|
388
|
+
latencyMs,
|
|
389
|
+
args,
|
|
390
|
+
query: queryForCommand(command, args),
|
|
391
|
+
resultSummary: status === "success" ? summarizeResult(data) : null,
|
|
392
|
+
resultIds: status === "success" ? summarizeResultIds(data) : {},
|
|
393
|
+
errorMessage: errorMessage ? truncateText(String(errorMessage), 2000) : null,
|
|
394
|
+
};
|
|
395
|
+
try {
|
|
396
|
+
const res = await fetch(`${getBaseUrl()}/api/agent/client-events`, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: {
|
|
399
|
+
"Content-Type": "application/json",
|
|
400
|
+
...agentClientHeaders("telemetry.record"),
|
|
401
|
+
...authHeaders,
|
|
402
|
+
},
|
|
403
|
+
body: JSON.stringify(payload),
|
|
404
|
+
});
|
|
405
|
+
if (!res.ok) return;
|
|
406
|
+
const recorded = await res.json().catch(() => null);
|
|
407
|
+
rememberAgentEvent({
|
|
408
|
+
...recorded,
|
|
409
|
+
sessionId,
|
|
410
|
+
command,
|
|
411
|
+
query: payload.query,
|
|
412
|
+
surface: command.startsWith("deal.") ? "deal" : command.startsWith("wiki.") ? "wiki" : null,
|
|
413
|
+
});
|
|
414
|
+
} catch {
|
|
415
|
+
// Best-effort by design. The actual llama command already succeeded or
|
|
416
|
+
// failed; telemetry must never alter that outcome.
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
126
420
|
// Try `gcloud auth print-identity-token`. Returns the JWT or null. Zero-config
|
|
127
421
|
// win for any team member who has gcloud + their @llamaventures.vc account
|
|
128
422
|
// already set up — the server's Bearer auth path verifies and auto-creates
|
|
@@ -216,22 +510,26 @@ function unauthorizedError() {
|
|
|
216
510
|
);
|
|
217
511
|
}
|
|
218
512
|
|
|
219
|
-
export async function request(method, endpoint, body) {
|
|
220
|
-
return requestWithRetry(method, endpoint, body, /* allowRetry */ true);
|
|
513
|
+
export async function request(method, endpoint, body, opts = {}) {
|
|
514
|
+
return requestWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
|
|
221
515
|
}
|
|
222
516
|
|
|
223
517
|
export async function requestSse(method, endpoint, body, opts = {}) {
|
|
224
518
|
return requestSseWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
|
|
225
519
|
}
|
|
226
520
|
|
|
227
|
-
async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
521
|
+
async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
228
522
|
const authHeaders = await getAuthHeaders();
|
|
229
523
|
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
|
524
|
+
const command = inferCommand(method, endpoint);
|
|
525
|
+
const start = Date.now();
|
|
230
526
|
const res = await fetch(`${getBaseUrl()}${endpoint}`, {
|
|
231
527
|
method,
|
|
232
528
|
headers: {
|
|
233
529
|
"Content-Type": "application/json",
|
|
530
|
+
...agentClientHeaders(command),
|
|
234
531
|
...authHeaders,
|
|
532
|
+
...(opts.headers || {}),
|
|
235
533
|
},
|
|
236
534
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
237
535
|
});
|
|
@@ -251,7 +549,7 @@ async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
|
251
549
|
refreshed = null;
|
|
252
550
|
}
|
|
253
551
|
if (refreshed) {
|
|
254
|
-
return requestWithRetry(method, endpoint, body, /* allowRetry */ false);
|
|
552
|
+
return requestWithRetry(method, endpoint, body, opts, /* allowRetry */ false);
|
|
255
553
|
}
|
|
256
554
|
throw unauthorizedError();
|
|
257
555
|
}
|
|
@@ -267,18 +565,45 @@ async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
|
267
565
|
}
|
|
268
566
|
if (!res.ok) {
|
|
269
567
|
const message = typeof data === "object" && data?.error ? data.error : `HTTP ${res.status}`;
|
|
568
|
+
await recordClientTelemetry({
|
|
569
|
+
authHeaders,
|
|
570
|
+
method,
|
|
571
|
+
endpoint,
|
|
572
|
+
body,
|
|
573
|
+
command,
|
|
574
|
+
status: "error",
|
|
575
|
+
httpStatus: res.status,
|
|
576
|
+
latencyMs: Date.now() - start,
|
|
577
|
+
data: null,
|
|
578
|
+
errorMessage: message,
|
|
579
|
+
});
|
|
270
580
|
throw new Error(message);
|
|
271
581
|
}
|
|
582
|
+
await recordClientTelemetry({
|
|
583
|
+
authHeaders,
|
|
584
|
+
method,
|
|
585
|
+
endpoint,
|
|
586
|
+
body,
|
|
587
|
+
command,
|
|
588
|
+
status: "success",
|
|
589
|
+
httpStatus: res.status,
|
|
590
|
+
latencyMs: Date.now() - start,
|
|
591
|
+
data,
|
|
592
|
+
errorMessage: null,
|
|
593
|
+
});
|
|
272
594
|
return data;
|
|
273
595
|
}
|
|
274
596
|
|
|
275
597
|
async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
276
598
|
const authHeaders = await getAuthHeaders();
|
|
277
599
|
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
|
600
|
+
const command = inferCommand(method, endpoint);
|
|
601
|
+
const start = Date.now();
|
|
278
602
|
const res = await fetch(`${getBaseUrl()}${endpoint}`, {
|
|
279
603
|
method,
|
|
280
604
|
headers: {
|
|
281
605
|
"Content-Type": "application/json",
|
|
606
|
+
...agentClientHeaders(command),
|
|
282
607
|
...authHeaders,
|
|
283
608
|
},
|
|
284
609
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
@@ -309,6 +634,18 @@ async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
|
309
634
|
data = text;
|
|
310
635
|
}
|
|
311
636
|
const message = typeof data === "object" && data?.error ? data.error : `HTTP ${res.status}`;
|
|
637
|
+
await recordClientTelemetry({
|
|
638
|
+
authHeaders,
|
|
639
|
+
method,
|
|
640
|
+
endpoint,
|
|
641
|
+
body,
|
|
642
|
+
command,
|
|
643
|
+
status: "error",
|
|
644
|
+
httpStatus: res.status,
|
|
645
|
+
latencyMs: Date.now() - start,
|
|
646
|
+
data: null,
|
|
647
|
+
errorMessage: message,
|
|
648
|
+
});
|
|
312
649
|
throw new Error(message);
|
|
313
650
|
}
|
|
314
651
|
|
|
@@ -343,6 +680,18 @@ async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
|
343
680
|
for (const frame of frames) handleFrame(frame);
|
|
344
681
|
}
|
|
345
682
|
if (buf.trim()) handleFrame(buf);
|
|
683
|
+
await recordClientTelemetry({
|
|
684
|
+
authHeaders,
|
|
685
|
+
method,
|
|
686
|
+
endpoint,
|
|
687
|
+
body,
|
|
688
|
+
command,
|
|
689
|
+
status: "success",
|
|
690
|
+
httpStatus: res.status,
|
|
691
|
+
latencyMs: Date.now() - start,
|
|
692
|
+
data: { ok: true, textLength: text.length, events: events.length },
|
|
693
|
+
errorMessage: null,
|
|
694
|
+
});
|
|
346
695
|
return { text, events };
|
|
347
696
|
}
|
|
348
697
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "npm run test:agent-routing",
|
|
8
|
-
"test:agent-routing": "node scripts/verify-agent-routing.mjs"
|
|
8
|
+
"test:agent-routing": "node scripts/verify-agent-routing.mjs",
|
|
9
|
+
"verify:release": "npm test && npm pack --dry-run"
|
|
9
10
|
},
|
|
10
11
|
"bin": {
|
|
11
12
|
"llama": "bin/llama.mjs",
|