@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.
@@ -2,14 +2,21 @@
2
2
 
3
3
  import assert from "node:assert/strict";
4
4
  import { spawn } from "node:child_process";
5
+ import { createHash } from "node:crypto";
5
6
  import { createServer } from "node:http";
6
- import { existsSync } from "node:fs";
7
- import { mkdtemp, rm } from "node:fs/promises";
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
8
9
  import os from "node:os";
9
10
  import path from "node:path";
10
11
  import { fileURLToPath } from "node:url";
11
12
 
12
13
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
+ const packageJson = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8"));
15
+ assert.equal(
16
+ packageJson.scripts?.["verify:release"],
17
+ "npm test && npm pack --dry-run",
18
+ "CLI release gate must run agent routing tests and npm pack dry-run",
19
+ );
13
20
  assert.equal(
14
21
  existsSync(path.join(repoRoot, "docs/agent-skills.bundle.json")),
15
22
  false,
@@ -20,8 +27,30 @@ assert.equal(
20
27
  false,
21
28
  "public llama-cli must not copy the Command-side skill mirror",
22
29
  );
30
+ const cliSource = readFileSync(path.join(repoRoot, "bin/llama.mjs"), "utf8");
31
+ assert.match(
32
+ cliSource,
33
+ /About ONE specific deal\? \.{8} llama html publish <deal-id-or-name> --file <path> --title "\.\.\."/,
34
+ "top-level help must route deal-specific HTML to the agent-safe publish path",
35
+ );
36
+ assert.doesNotMatch(
37
+ cliSource,
38
+ /For deal-specific HTML use "llama html upload <dealId>"/,
39
+ "wiki help must not route deal-specific HTML to the low-level upload path",
40
+ );
23
41
  const calls = [];
24
42
  let threadSeq = 0;
43
+ let eventSeq = 0;
44
+ const htmlDocs = new Map();
45
+
46
+ function sha256Hex(value) {
47
+ return createHash("sha256").update(value).digest("hex");
48
+ }
49
+
50
+ function docsForDeal(dealId) {
51
+ if (!htmlDocs.has(dealId)) htmlDocs.set(dealId, new Map());
52
+ return htmlDocs.get(dealId);
53
+ }
25
54
 
26
55
  async function readJson(req) {
27
56
  let raw = "";
@@ -65,11 +94,48 @@ const server = createServer(async (req, res) => {
65
94
  path: url.pathname,
66
95
  query: Object.fromEntries(url.searchParams.entries()),
67
96
  body,
97
+ headers: {
98
+ client: req.headers["x-llama-client"] ?? null,
99
+ clientVersion: req.headers["x-llama-client-version"] ?? null,
100
+ agentClient: req.headers["x-llama-agent-client"] ?? null,
101
+ session: req.headers["x-llama-agent-session"] ?? null,
102
+ command: req.headers["x-llama-command"] ?? null,
103
+ uploadId: req.headers["x-llama-upload-id"] ?? null,
104
+ },
68
105
  });
69
106
 
107
+ if (req.method === "POST" && url.pathname === "/api/agent/client-events") {
108
+ eventSeq += 1;
109
+ writeJson(res, {
110
+ ok: true,
111
+ eventId: eventSeq,
112
+ candidateId: body?.command?.endsWith(".search") ? eventSeq + 1000 : null,
113
+ });
114
+ return;
115
+ }
116
+
117
+ if (req.method === "POST" && url.pathname === "/api/agent/eval-feedback") {
118
+ writeJson(res, {
119
+ ok: true,
120
+ candidate: {
121
+ id: 42,
122
+ source_event_id: body?.eventId ?? null,
123
+ feedback: body?.action ?? null,
124
+ },
125
+ });
126
+ return;
127
+ }
128
+
70
129
  if (req.method === "GET" && url.pathname === "/api/agent/manifest") {
71
130
  writeJson(res, {
72
131
  ok: true,
132
+ contract: {
133
+ contract_version: "agent-contract.v1",
134
+ cli: {
135
+ client_version: url.searchParams.get("clientVersion"),
136
+ status: "ok",
137
+ },
138
+ },
73
139
  briefing: "runtime briefing: use skills_search, skills_read, and object_inspect",
74
140
  llama_os: {
75
141
  visible_skill_count: 49,
@@ -85,6 +151,21 @@ const server = createServer(async (req, res) => {
85
151
  return;
86
152
  }
87
153
 
154
+ if (req.method === "GET" && url.pathname === "/api/agent/briefing") {
155
+ writeJson(res, {
156
+ ok: true,
157
+ contract: {
158
+ contract_version: "agent-contract.v1",
159
+ cli: {
160
+ client_version: url.searchParams.get("clientVersion"),
161
+ status: "ok",
162
+ },
163
+ },
164
+ briefing: "server-owned briefing: check CLI, use Pipeline First, prefer CLI/MCP",
165
+ });
166
+ return;
167
+ }
168
+
88
169
  if (req.method === "GET" && url.pathname === "/api/agent/skills") {
89
170
  writeJson(res, {
90
171
  ok: true,
@@ -136,6 +217,114 @@ const server = createServer(async (req, res) => {
136
217
  return;
137
218
  }
138
219
 
220
+ if (req.method === "GET" && url.pathname === "/api/wiki/search") {
221
+ writeJson(res, [
222
+ {
223
+ slug: "llama-weekly-2026-06-16",
224
+ title: "Llama Weekly 2026-06-16",
225
+ },
226
+ ]);
227
+ return;
228
+ }
229
+
230
+ if (req.method === "GET" && url.pathname === "/api/deals") {
231
+ writeJson(res, {
232
+ deals: [
233
+ {
234
+ uuid: "deal-html",
235
+ companyName: "Acme AI",
236
+ founders: "Ada Founder",
237
+ description: "mock deal for HTML upload tests",
238
+ },
239
+ ],
240
+ total: 1,
241
+ limit: Number(url.searchParams.get("limit") || 200),
242
+ offset: 0,
243
+ });
244
+ return;
245
+ }
246
+
247
+ const docsMatch = url.pathname.match(/^\/api\/deals\/([^/]+)\/documents$/);
248
+ if (docsMatch) {
249
+ const dealId = decodeURIComponent(docsMatch[1]);
250
+ if (dealId !== "deal-html") {
251
+ res.writeHead(404, { "Content-Type": "application/json" });
252
+ res.end(JSON.stringify({ error: "deal not found" }));
253
+ return;
254
+ }
255
+ if (req.method === "GET") {
256
+ const docs = Array.from(docsForDeal(dealId).entries()).map(([slug, doc]) => ({
257
+ slug,
258
+ title: doc.title,
259
+ latest_version: doc.version ?? null,
260
+ latest_updated_at: doc.version ? "2026-06-23T04:00:00Z" : null,
261
+ }));
262
+ writeJson(res, { documents: docs });
263
+ return;
264
+ }
265
+ if (req.method === "POST") {
266
+ const docs = docsForDeal(dealId);
267
+ const slug = body?.slug;
268
+ docs.set(slug, {
269
+ ...(docs.get(slug) || {}),
270
+ title: body?.title || slug,
271
+ });
272
+ writeJson(res, { ok: true, slug, title: body?.title || slug });
273
+ return;
274
+ }
275
+ }
276
+
277
+ const htmlMatch = url.pathname.match(/^\/api\/deals\/([^/]+)\/documents\/([^/]+)\/html$/);
278
+ if (htmlMatch) {
279
+ const dealId = decodeURIComponent(htmlMatch[1]);
280
+ const slug = decodeURIComponent(htmlMatch[2]);
281
+ const docs = docsForDeal(dealId);
282
+ if (req.method === "PUT") {
283
+ const html = typeof body?.html === "string" ? body.html : "";
284
+ const previous = docs.get(slug) || { title: slug, version: 0 };
285
+ const version = Number(previous.version || 0) + 1;
286
+ const bytes = Buffer.byteLength(html, "utf8");
287
+ const sha256 = sha256Hex(html);
288
+ docs.set(slug, {
289
+ ...previous,
290
+ html,
291
+ version,
292
+ bytes,
293
+ sha256,
294
+ source: body?.source || "cli",
295
+ client_upload_id: body?.client_upload_id || null,
296
+ });
297
+ writeJson(res, {
298
+ ok: true,
299
+ document_slug: slug,
300
+ version,
301
+ bytes,
302
+ sha256,
303
+ client_upload_id: body?.client_upload_id || null,
304
+ idempotent_replay: false,
305
+ });
306
+ return;
307
+ }
308
+ if (req.method === "GET") {
309
+ const doc = docs.get(slug);
310
+ if (!doc?.html) {
311
+ writeJson(res, { empty: true });
312
+ return;
313
+ }
314
+ writeJson(res, {
315
+ empty: false,
316
+ document_slug: slug,
317
+ version: doc.version,
318
+ bytes: doc.bytes,
319
+ sha256: doc.sha256,
320
+ source: doc.source,
321
+ created_at: "2026-06-23T04:00:00Z",
322
+ html: doc.html,
323
+ });
324
+ return;
325
+ }
326
+ }
327
+
139
328
  if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads$/.test(url.pathname)) {
140
329
  threadSeq += 1;
141
330
  writeJson(res, { id: `thread-${threadSeq}` });
@@ -191,25 +380,34 @@ function resetCalls() {
191
380
  threadSeq = 0;
192
381
  }
193
382
 
383
+ function businessCalls() {
384
+ return calls.filter((call) => call.path !== "/api/agent/client-events");
385
+ }
386
+
387
+ function telemetryCalls() {
388
+ return calls.filter((call) => call.path === "/api/agent/client-events");
389
+ }
390
+
194
391
  function paths() {
195
- return calls.map((call) => `${call.method} ${call.path}`);
392
+ return businessCalls().map((call) => `${call.method} ${call.path}`);
196
393
  }
197
394
 
198
395
  function assertNoEnrichCall() {
199
396
  assert.equal(
200
- calls.some((call) => call.path.endsWith("/enrich")),
397
+ businessCalls().some((call) => call.path.endsWith("/enrich")),
201
398
  false,
202
399
  `expected no /enrich call, got ${paths().join(", ")}`,
203
400
  );
204
401
  }
205
402
 
206
403
  function assertThreadRun({ title, messageIncludes }) {
207
- assert.equal(calls.length, 2, `expected thread create + SSE run, got ${paths().join(", ")}`);
208
- assert.match(calls[0].path, /^\/api\/deals\/[^/]+\/threads$/);
209
- assert.equal(calls[0].body?.title, title);
210
- assert.match(calls[1].path, /^\/api\/deals\/[^/]+\/threads\/thread-1$/);
404
+ const relevant = businessCalls();
405
+ assert.equal(relevant.length, 2, `expected thread create + SSE run, got ${paths().join(", ")}`);
406
+ assert.match(relevant[0].path, /^\/api\/deals\/[^/]+\/threads$/);
407
+ assert.equal(relevant[0].body?.title, title);
408
+ assert.match(relevant[1].path, /^\/api\/deals\/[^/]+\/threads\/thread-1$/);
211
409
  for (const needle of messageIncludes) {
212
- assert.match(calls[1].body?.message ?? "", new RegExp(escapeRegExp(needle)));
410
+ assert.match(relevant[1].body?.message ?? "", new RegExp(escapeRegExp(needle)));
213
411
  }
214
412
  }
215
413
 
@@ -313,18 +511,29 @@ const baseUrl = `http://${address.address}:${address.port}`;
313
511
  const homeDir = await mkdtemp(path.join(os.tmpdir(), "llama-cli-routing-"));
314
512
 
315
513
  try {
514
+ resetCalls();
515
+ const onboardRun = await runCli(["agent-onboard"], baseUrl, homeDir);
516
+ assert.match(onboardRun.stdout, /server-owned briefing/);
517
+ assert.deepEqual(paths(), ["GET /api/agent/briefing"]);
518
+ assert.ok(businessCalls()[0].query.clientVersion, "agent-onboard passes clientVersion");
519
+ assert.equal(telemetryCalls()[0].body?.command, "agent.briefing");
520
+ assert.equal(telemetryCalls()[0].body?.client, "cli");
521
+ assert.ok(telemetryCalls()[0].body?.sessionId, "telemetry includes an agent session id");
522
+ assert.equal(businessCalls()[0].headers.command, "agent.briefing");
523
+
316
524
  resetCalls();
317
525
  const bootstrapRun = await runCli(["agent", "bootstrap", "--limit", "3"], baseUrl, homeDir);
318
526
  assert.match(bootstrapRun.stdout, /runtime briefing/);
319
527
  assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
320
- assert.equal(calls[0].query.limit, "3");
528
+ assert.equal(businessCalls()[0].query.limit, "3");
529
+ assert.ok(businessCalls()[0].query.clientVersion, "agent bootstrap passes clientVersion");
321
530
 
322
531
  resetCalls();
323
532
  const skillSearchRun = await runCli(["skills", "search", "pipeline", "--limit", "5"], baseUrl, homeDir);
324
533
  assert.match(skillSearchRun.stdout, /llama-command/);
325
534
  assert.deepEqual(paths(), ["GET /api/agent/skills"]);
326
- assert.equal(calls[0].query.q, "pipeline");
327
- assert.equal(calls[0].query.limit, "5");
535
+ assert.equal(businessCalls()[0].query.q, "pipeline");
536
+ assert.equal(businessCalls()[0].query.limit, "5");
328
537
 
329
538
  resetCalls();
330
539
  const skillShowRun = await runCli(["skills", "show", "llama-command"], baseUrl, homeDir);
@@ -336,7 +545,34 @@ try {
336
545
  assert.match(explainRun.stdout, /Status: deleted/);
337
546
  assert.match(explainRun.stdout, /Deleted by Kevin Yu/);
338
547
  assert.deepEqual(paths(), ["GET /api/agent/explain"]);
339
- assert.equal(calls[0].query.q, "https://command.llamaventures.vc/wiki/missing-page");
548
+ assert.equal(businessCalls()[0].query.q, "https://command.llamaventures.vc/wiki/missing-page");
549
+
550
+ resetCalls();
551
+ const wikiRun = await runCli(["wiki", "search", "llama weekly"], baseUrl, homeDir);
552
+ assert.match(wikiRun.stdout, /llama-weekly-2026-06-16/);
553
+ assert.deepEqual(paths(), ["GET /api/wiki/search"]);
554
+ assert.equal(telemetryCalls()[0].body?.command, "wiki.search");
555
+ assert.equal(telemetryCalls()[0].body?.query, "llama weekly");
556
+
557
+ resetCalls();
558
+ const evalRun = await runCli(
559
+ [
560
+ "eval",
561
+ "bad",
562
+ "--last",
563
+ "--reason",
564
+ "missed dev weekly",
565
+ "--expect",
566
+ "wiki:llamaos-weekly-2026-06-17",
567
+ ],
568
+ baseUrl,
569
+ homeDir,
570
+ );
571
+ assert.match(evalRun.stdout, /"feedback": "bad"/);
572
+ assert.deepEqual(paths(), ["POST /api/agent/eval-feedback"]);
573
+ assert.equal(businessCalls()[0].body?.action, "bad");
574
+ assert.equal(businessCalls()[0].body?.eventId, 6);
575
+ assert.equal(businessCalls()[0].body?.expected?.wikiSlugs?.[0], "llamaos-weekly-2026-06-17");
340
576
 
341
577
  resetCalls();
342
578
  const enrichRun = await runCli(
@@ -369,9 +605,9 @@ try {
369
605
  homeDir,
370
606
  );
371
607
  assert.deepEqual(paths(), ["POST /api/deals/deal-cli/enrich"]);
372
- assert.equal(calls[0].body?.apply, true);
373
- assert.equal(calls[0].body?.dryRun, false);
374
- assert.equal(calls[0].body?.executor, "server_agent");
608
+ assert.equal(businessCalls()[0].body?.apply, true);
609
+ assert.equal(businessCalls()[0].body?.dryRun, false);
610
+ assert.equal(businessCalls()[0].body?.executor, "server_agent");
375
611
 
376
612
  resetCalls();
377
613
  const agentRun = await runCli(
@@ -386,6 +622,68 @@ try {
386
622
  messageIncludes: ["custom server task"],
387
623
  });
388
624
 
625
+ const largeHtmlPath = path.join(homeDir, "full-memo.html");
626
+ const largeHtml =
627
+ "<!doctype html><html><head><title>Full Memo</title></head><body>" +
628
+ `<p>${"agent-safe upload ".repeat(18000)}</p>` +
629
+ "</body></html>";
630
+ assert.ok(
631
+ Buffer.byteLength(largeHtml, "utf8") > 252 * 1024,
632
+ "routing test HTML must exceed the incident-sized 252KB memo",
633
+ );
634
+ await writeFile(largeHtmlPath, largeHtml);
635
+
636
+ resetCalls();
637
+ const publishRun = await runCli(
638
+ [
639
+ "html",
640
+ "publish",
641
+ "Acme AI",
642
+ "--file",
643
+ largeHtmlPath,
644
+ "--title",
645
+ "Full Memo",
646
+ "--doc",
647
+ "full-memo",
648
+ ],
649
+ baseUrl,
650
+ homeDir,
651
+ );
652
+ const publishPayload = JSON.parse(publishRun.stdout);
653
+ assert.equal(publishPayload.ok, true);
654
+ assert.equal(publishPayload.deal_uuid, "deal-html");
655
+ assert.equal(publishPayload.document_slug, "full-memo");
656
+ assert.equal(publishPayload.verified?.ok, true);
657
+ assert.match(publishPayload.client_upload_id, /^cli-[0-9a-f-]{36}$/);
658
+ assert.match(publishPayload.sha256, /^[a-f0-9]{64}$/);
659
+ assert.equal(publishPayload.verified?.sha256, publishPayload.sha256);
660
+ assert.deepEqual(paths(), [
661
+ "GET /api/deals/Acme%20AI/documents",
662
+ "GET /api/deals",
663
+ "GET /api/deals/deal-html/documents",
664
+ "POST /api/deals/deal-html/documents",
665
+ "PUT /api/deals/deal-html/documents/full-memo/html",
666
+ "GET /api/deals/deal-html/documents/full-memo/html",
667
+ ]);
668
+ assert.equal(businessCalls()[4].body?.html, largeHtml);
669
+ assert.equal(businessCalls()[4].body?.source, "cli");
670
+ assert.equal(businessCalls()[4].body?.client_upload_id, publishPayload.client_upload_id);
671
+ assert.equal(businessCalls()[4].headers.uploadId, publishPayload.client_upload_id);
672
+ const publishTelemetry = telemetryCalls().find(
673
+ (call) =>
674
+ call.body?.method === "PUT" &&
675
+ call.body?.endpoint === "/api/deals/deal-html/documents/full-memo/html",
676
+ );
677
+ assert.ok(publishTelemetry, "publish upload request must record telemetry");
678
+ assert.equal(publishTelemetry.body?.args?.html?.redacted, true);
679
+ assert.equal(publishTelemetry.body?.args?.html?.bytes, Buffer.byteLength(largeHtml, "utf8"));
680
+ assert.equal(publishTelemetry.body?.args?.html?.sha256, sha256Hex(largeHtml));
681
+ assert.doesNotMatch(
682
+ JSON.stringify(telemetryCalls()),
683
+ /agent-safe upload agent-safe upload agent-safe upload/,
684
+ "telemetry must not contain raw memo HTML text",
685
+ );
686
+
389
687
  resetCalls();
390
688
  const mcpResult = await callMcpTool(
391
689
  "deal_enrich",
@@ -409,19 +707,90 @@ try {
409
707
  assert.equal(payload.threadId, "thread-1");
410
708
  assert.equal(payload.text, "agent done");
411
709
 
710
+ resetCalls();
711
+ const inlineGuard = await callMcpTool(
712
+ "html_upload",
713
+ {
714
+ dealId: "deal-html",
715
+ documentSlug: "inline-too-large",
716
+ html: largeHtml,
717
+ },
718
+ baseUrl,
719
+ homeDir,
720
+ );
721
+ assert.equal(inlineGuard.isError, true);
722
+ assert.match(inlineGuard.content?.[0]?.text ?? "", /Use html_upload_file/);
723
+ assert.deepEqual(paths(), []);
724
+
725
+ resetCalls();
726
+ const inlineBundleGuard = await callMcpTool(
727
+ "html_upload_bundle",
728
+ {
729
+ dealId: "deal-html",
730
+ documentSlug: "bundle-too-large",
731
+ html: largeHtml,
732
+ assets: [
733
+ {
734
+ path: "full-memo_files/cover.txt",
735
+ contentType: "text/plain",
736
+ base64: Buffer.from("asset").toString("base64"),
737
+ },
738
+ ],
739
+ },
740
+ baseUrl,
741
+ homeDir,
742
+ );
743
+ assert.equal(inlineBundleGuard.isError, true);
744
+ assert.match(inlineBundleGuard.content?.[0]?.text ?? "", /Use html_upload_file/);
745
+ assert.deepEqual(paths(), []);
746
+
747
+ resetCalls();
748
+ const mcpFile = await callMcpTool(
749
+ "html_upload_file",
750
+ {
751
+ dealId: "deal-html",
752
+ documentSlug: "mcp-file",
753
+ filePath: largeHtmlPath,
754
+ },
755
+ baseUrl,
756
+ homeDir,
757
+ );
758
+ const mcpFilePayload = JSON.parse(mcpFile.content?.[0]?.text ?? "{}");
759
+ assert.equal(mcpFilePayload.ok, true);
760
+ assert.equal(mcpFilePayload.verified?.ok, true);
761
+ assert.match(mcpFilePayload.client_upload_id, /^mcp-[0-9a-f-]{36}$/);
762
+ assert.match(mcpFilePayload.sha256, /^[a-f0-9]{64}$/);
763
+ assert.equal(mcpFilePayload.verified?.sha256, mcpFilePayload.sha256);
764
+ assert.deepEqual(paths(), [
765
+ "PUT /api/deals/deal-html/documents/mcp-file/html",
766
+ "GET /api/deals/deal-html/documents/mcp-file/html",
767
+ ]);
768
+ assert.equal(businessCalls()[0].body?.client_upload_id, mcpFilePayload.client_upload_id);
769
+ assert.equal(businessCalls()[0].headers.uploadId, mcpFilePayload.client_upload_id);
770
+ const mcpFileTelemetry = telemetryCalls().find(
771
+ (call) =>
772
+ call.body?.method === "PUT" &&
773
+ call.body?.endpoint === "/api/deals/deal-html/documents/mcp-file/html",
774
+ );
775
+ assert.ok(mcpFileTelemetry, "MCP file upload request must record telemetry");
776
+ assert.equal(mcpFileTelemetry.body?.args?.html?.redacted, true);
777
+ assert.equal(mcpFileTelemetry.body?.args?.html?.sha256, sha256Hex(largeHtml));
778
+
412
779
  resetCalls();
413
780
  const mcpBootstrap = await callMcpTool("agent_bootstrap", { limit: 2 }, baseUrl, homeDir);
414
781
  const bootstrapPayload = JSON.parse(mcpBootstrap.content?.[0]?.text ?? "{}");
415
782
  assert.equal(bootstrapPayload.ok, true);
416
783
  assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
417
- assert.equal(calls[0].query.limit, "2");
784
+ assert.equal(businessCalls()[0].query.limit, "2");
785
+ assert.ok(businessCalls()[0].query.clientVersion, "mcp agent_bootstrap passes clientVersion");
786
+ assert.equal(telemetryCalls()[0].body?.client, "mcp");
418
787
 
419
788
  resetCalls();
420
789
  const mcpSkills = await callMcpTool("skills_search", { q: "command", limit: 4 }, baseUrl, homeDir);
421
790
  const skillsPayload = JSON.parse(mcpSkills.content?.[0]?.text ?? "{}");
422
791
  assert.equal(skillsPayload.skills?.[0]?.slug, "llama-command");
423
792
  assert.deepEqual(paths(), ["GET /api/agent/skills"]);
424
- assert.equal(calls[0].query.q, "command");
793
+ assert.equal(businessCalls()[0].query.q, "command");
425
794
 
426
795
  resetCalls();
427
796
  const mcpSkillRead = await callMcpTool("skills_read", { slug: "llama-command" }, baseUrl, homeDir);