@llamaventures/cli 1.6.1 → 1.8.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 CHANGED
@@ -29,6 +29,19 @@ Don't:
29
29
 
30
30
  Conversation produces value → that value flows somewhere. This is not optional.
31
31
 
32
+ ### When someone gives you info about a deal (the most common case)
33
+
34
+ A teammate says "I just met them and heard…" or pastes a chunk of notes. Your job: get it into the right deal, in the right layer, and confirm it's right. Three steps:
35
+
36
+ 1. **Find the deal** — `llama deal search "<name>"` (Pipeline First). New name → offer to create it.
37
+ 2. **Split what they gave you into two kinds** — this is the whole data model:
38
+ - **Verifiable claims → facts.** `llama deal fact add <dealId> --category <cat> --claim "…" --source "<where it came from>"`. A claim someone *relayed* ("their ARR is $3M", "raised from a16z") is a fact at **unverified** trust — it's hearsay until checked. Pass `--attested` ONLY if you actually verified it against a source yourself.
39
+ - **Their judgment / impression → a note.** `llama post <dealId> "…"`. "Founder seemed evasive", "I'd lean pass", "worth a second meeting" — opinion, not fact. Attributed, never "verified".
40
+ - A pasted blob → pull the verifiable claims out as facts, capture their take as a note.
41
+ 3. **Confirm accuracy.** After filing, tell them in plain language what you recorded and where, and ask if it's right. Facts you add stay **unverified** until a human confirms them (then they rise to human-vouched) — the confirmation IS the trust step. Never silently mark something verified.
42
+
43
+ Why split it: facts and opinions live in different layers so the deal keeps one clean **source of truth** (facts, sourced + trust-rated) separate from people's **takes** (notes). The four layers — facts / notes / brief (AI's synthesis) / timeline — are documented in Llama Command's `docs/SCHEMA.md`.
44
+
32
45
  ### Where does this HTML / thesis / artifact go? (decision tree)
33
46
 
34
47
  When the user hands you an HTML page, thesis write-up, market map, dashboard, IC memo, sector landscape — anything that isn't a one-off note — pick the destination in this order. **Llama Command native (the workbench) outranks Netlify for everything internal.** Only escape to Netlify when the page is truly going to a public / founder-facing URL.
@@ -157,6 +170,9 @@ llama deal list [--owner ...] [--status ...]
157
170
  # Pipeline — write
158
171
  llama deal create "Company" --description "..."
159
172
  llama deal update <dealId> <field> <value>
173
+ # writable: status theirStage stage notes dealOwner source description website
174
+ # location founders proposedAmount roundSize valuation sector subsector
175
+ # foundedYear leadInvestor investors (each write logs a deal_events row)
160
176
 
161
177
  # Brief blocks
162
178
  llama brief blocks <dealId>
@@ -244,6 +260,7 @@ You can also fetch this exact briefing as an MCP prompt named `agent_briefing`.
244
260
  ## Boundaries (what NOT to do)
245
261
 
246
262
  - **Don't impersonate a human's opinion.** Tag AI-generated content as `[AI · …]`.
263
+ - **Don't vouch for facts you haven't checked.** When you `add fact`, pass `--attested` only if you actually verified the claim against its source. Without it the fact is stored as *unverified* — that's the honest default, not a failure. You cannot mark a fact as human-confirmed; only a person can raise it there.
247
264
  - **Don't use absolute language** ("only", "all", "best", "no one", "极") unless verifiable.
248
265
  - **Don't bypass `llama` CLI / MCP for pipeline writes.** CSRF defence, rate limits, audit logs all flow through it.
249
266
  - **Don't write to retired surfaces.** Google Sheet is read-only archive. Legacy `~/.llama-command/config.json` auto-migrates.
package/bin/llama-mcp.mjs CHANGED
@@ -197,7 +197,8 @@ server.registerTool(
197
197
  description:
198
198
  "Update a single whitelisted field on a deal. Writable fields: status, theirStage, " +
199
199
  "notes, stage, dealOwner, source, description, website, location, founders, " +
200
- "proposedAmount, roundSize, valuation. Logs a field_change event in deal_events.",
200
+ "proposedAmount, roundSize, valuation, sector, subsector, foundedYear, leadInvestor, " +
201
+ "investors. Logs a field_change event in deal_events.",
201
202
  inputSchema: {
202
203
  dealId: z.string(),
203
204
  field: z.string().describe("camelCase field name (see description for whitelist)"),
@@ -208,6 +209,82 @@ server.registerTool(
208
209
  callApi("POST", "/api/deals/update", { dealId, field, value })
209
210
  );
210
211
 
212
+ // ============================================================
213
+ // Deal facts (research substrate + trust ladder)
214
+ // ============================================================
215
+
216
+ server.registerTool(
217
+ "deal_fact_list",
218
+ {
219
+ description:
220
+ "List a deal's recorded facts (the research substrate). Each fact carries a " +
221
+ "category, a claim, a source, a confidence, and a trust rung (unverified → " +
222
+ "agent-verified → human-vouched → endorsed) plus who/what recorded it.",
223
+ inputSchema: {
224
+ dealId: z.string(),
225
+ },
226
+ },
227
+ async ({ dealId }) =>
228
+ callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/facts`)
229
+ );
230
+
231
+ server.registerTool(
232
+ "deal_fact_add",
233
+ {
234
+ description:
235
+ "Record a factual claim about a deal. RESPONSIBILITY: set `attested` honestly — " +
236
+ "true ONLY if you actually verified the claim against its cited source (the fact " +
237
+ "is then stored at trust level 'agent-verified'); false/omitted if you are relaying " +
238
+ "something unconfirmed (stored 'unverified', which is the honest default). You CANNOT " +
239
+ "mark a fact as human-confirmed — only a person can raise it to 'human-vouched'. " +
240
+ "`confidence` is how certain the claim is; `attested` is whether YOU take responsibility " +
241
+ "for having checked it. category ∈ founders | financials | product | market | team | " +
242
+ "company_basics | risk | fundraise | milestone | meta.",
243
+ inputSchema: {
244
+ dealId: z.string(),
245
+ category: z.string(),
246
+ claim: z.string(),
247
+ source: z.string().optional().describe("where you found this (URL, 'deck p3', 'LinkedIn')"),
248
+ confidence: z.enum(["high", "medium", "low"]).optional(),
249
+ attested: z
250
+ .boolean()
251
+ .optional()
252
+ .describe("true → stored 'agent-verified'; false/omitted → 'unverified'. Answer honestly."),
253
+ },
254
+ },
255
+ async ({ dealId, category, claim, source, confidence, attested }) =>
256
+ callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/facts`, {
257
+ category,
258
+ claim,
259
+ source: source ?? "",
260
+ confidence: confidence ?? "medium",
261
+ attested: attested === true,
262
+ })
263
+ );
264
+
265
+ server.registerTool(
266
+ "deal_fact_verify",
267
+ {
268
+ description:
269
+ "Verify a recorded fact. status='confirmed' vouches for it (raises to 'human-vouched'); " +
270
+ "status='disputed' marks it contradicted. Trust-ladder guardrails apply server-side " +
271
+ "(external-org callers are capped at 'unverified'; only Partners reach 'endorsed'). " +
272
+ "Optionally pass correctedValue when disputing.",
273
+ inputSchema: {
274
+ dealId: z.string(),
275
+ factId: z.union([z.string(), z.number()]),
276
+ status: z.enum(["confirmed", "disputed"]),
277
+ correctedValue: z.string().optional(),
278
+ },
279
+ },
280
+ async ({ dealId, factId, status, correctedValue }) =>
281
+ callApi(
282
+ "PATCH",
283
+ `/api/deals/${encodeURIComponent(dealId)}/facts/${encodeURIComponent(String(factId))}`,
284
+ { status, ...(correctedValue !== undefined ? { correctedValue } : {}) }
285
+ )
286
+ );
287
+
211
288
  // ============================================================
212
289
  // Brief blocks
213
290
  // ============================================================
@@ -226,6 +303,22 @@ server.registerTool(
226
303
  callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/blocks`)
227
304
  );
228
305
 
306
+ server.registerTool(
307
+ "deal_feed",
308
+ {
309
+ description:
310
+ "The unified, time-sorted stream of everything a HUMAN has added to a deal — " +
311
+ "facts + notes/discussion + legacy posts, merged at query time, newest first. " +
312
+ "Excludes AI-generated content. Each item: kind (fact|note), ts, who, text, " +
313
+ "and for facts: source + trust rung + category.",
314
+ inputSchema: {
315
+ dealId: z.string(),
316
+ },
317
+ },
318
+ async ({ dealId }) =>
319
+ callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/feed`)
320
+ );
321
+
229
322
  server.registerTool(
230
323
  "brief_add_text",
231
324
  {
@@ -272,6 +365,108 @@ server.registerTool(
272
365
  addBriefBlock(dealId, { type: "callout", tone, heading: heading ?? "", body })
273
366
  );
274
367
 
368
+ server.registerTool(
369
+ "brief_edit",
370
+ {
371
+ description:
372
+ "Edit an existing brief block in place. Pass only the fields you want to change " +
373
+ "(heading/body/url/label/description/tone). Meta toggles: locked (protect from bulk " +
374
+ "overwrite), hidden (fold), sourceSection (route watcher writes). Snapshots the prior " +
375
+ "version to history (reversible via brief_restore_version).",
376
+ inputSchema: {
377
+ dealId: z.string(),
378
+ blockId: z.string(),
379
+ heading: z.string().optional(),
380
+ body: z.string().optional(),
381
+ url: z.string().optional(),
382
+ label: z.string().optional(),
383
+ description: z.string().optional(),
384
+ tone: z.string().optional(),
385
+ locked: z.boolean().optional(),
386
+ hidden: z.boolean().optional(),
387
+ sourceSection: z.string().optional(),
388
+ },
389
+ },
390
+ async ({ dealId, blockId, heading, body, url, label, description, tone, locked, hidden, sourceSection }) => {
391
+ const patch = {};
392
+ for (const [k, v] of Object.entries({ heading, body, url, label, description, tone })) {
393
+ if (v !== undefined) patch[k] = v;
394
+ }
395
+ const meta = {};
396
+ if (locked !== undefined) meta.locked = locked;
397
+ if (hidden !== undefined) meta.hidden = hidden;
398
+ if (sourceSection !== undefined) meta.sourceSection = sourceSection;
399
+ if (Object.keys(meta).length > 0) patch.meta = meta;
400
+ return callApi(
401
+ "PATCH",
402
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`,
403
+ patch
404
+ );
405
+ }
406
+ );
407
+
408
+ server.registerTool(
409
+ "brief_delete",
410
+ {
411
+ description:
412
+ "Soft-delete a brief block (reversible via brief_restore). Locked blocks are refused.",
413
+ inputSchema: { dealId: z.string(), blockId: z.string() },
414
+ },
415
+ async ({ dealId, blockId }) =>
416
+ callApi("DELETE", `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`)
417
+ );
418
+
419
+ server.registerTool(
420
+ "brief_restore",
421
+ {
422
+ description: "Restore a soft-deleted brief block.",
423
+ inputSchema: { dealId: z.string(), blockId: z.string() },
424
+ },
425
+ async ({ dealId, blockId }) =>
426
+ callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/restore`)
427
+ );
428
+
429
+ server.registerTool(
430
+ "brief_history",
431
+ {
432
+ description:
433
+ "List the content-version history of a brief block (every overwrite is snapshotted). " +
434
+ "Use the returned history id with brief_restore_version.",
435
+ inputSchema: {
436
+ dealId: z.string(),
437
+ blockId: z.string(),
438
+ limit: z.number().optional(),
439
+ },
440
+ },
441
+ async ({ dealId, blockId, limit }) => {
442
+ const qs = limit ? `?limit=${encodeURIComponent(String(limit))}` : "";
443
+ return callApi(
444
+ "GET",
445
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/history${qs}`
446
+ );
447
+ }
448
+ );
449
+
450
+ server.registerTool(
451
+ "brief_restore_version",
452
+ {
453
+ description:
454
+ "Restore a brief block to a specific historical version (find historyId via brief_history). " +
455
+ "Itself reversible — the outgoing version is snapshotted before replacement.",
456
+ inputSchema: {
457
+ dealId: z.string(),
458
+ blockId: z.string(),
459
+ historyId: z.number(),
460
+ },
461
+ },
462
+ async ({ dealId, blockId, historyId }) =>
463
+ callApi(
464
+ "POST",
465
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/history`,
466
+ { history_id: historyId }
467
+ )
468
+ );
469
+
275
470
  // ============================================================
276
471
  // Wiki (knowledge base)
277
472
  // ============================================================
@@ -482,6 +677,109 @@ server.registerTool(
482
677
  }
483
678
  );
484
679
 
680
+ server.registerTool(
681
+ "mentions_resolve",
682
+ {
683
+ description: "Mark an @-mention as resolved (clears it from the recipient's open cues).",
684
+ inputSchema: { mentionId: z.union([z.string(), z.number()]) },
685
+ },
686
+ async ({ mentionId }) =>
687
+ callApi("POST", `/api/mentions/${encodeURIComponent(String(mentionId))}/resolve`)
688
+ );
689
+
690
+ // ============================================================
691
+ // Skill corrections (persona-owner pushback workflow)
692
+ // ============================================================
693
+
694
+ server.registerTool(
695
+ "skill_correction_list",
696
+ {
697
+ description:
698
+ "List the recorded corrections (long-term rules) for a persona/skill. These shape how " +
699
+ "that persona's analysis is generated.",
700
+ inputSchema: {
701
+ skillSlug: z.string(),
702
+ includeDeleted: z.boolean().optional(),
703
+ },
704
+ },
705
+ async ({ skillSlug, includeDeleted }) => {
706
+ const params = new URLSearchParams({ skill: skillSlug });
707
+ if (includeDeleted) params.set("include_deleted", "1");
708
+ return callApi("GET", `/api/skill-corrections?${params}`);
709
+ }
710
+ );
711
+
712
+ server.registerTool(
713
+ "skill_correction_add",
714
+ {
715
+ description:
716
+ "Record a long-term correction rule for a persona/skill (e.g. 'always check burn multiple " +
717
+ "before commenting on efficiency'). ALWAYS reconfirm the distilled rule with the user before " +
718
+ "calling — this changes how the persona behaves going forward. Optionally tie it to the deal/" +
719
+ "block where it came up.",
720
+ inputSchema: {
721
+ skillSlug: z.string(),
722
+ correctionText: z.string(),
723
+ dealUuid: z.string().optional(),
724
+ blockId: z.string().optional(),
725
+ },
726
+ },
727
+ async ({ skillSlug, correctionText, dealUuid, blockId }) =>
728
+ callApi("POST", "/api/skill-corrections", {
729
+ skill_slug: skillSlug,
730
+ correction_text: correctionText,
731
+ triggered_in_deal_uuid: dealUuid ?? null,
732
+ triggered_in_block_id: blockId ?? null,
733
+ })
734
+ );
735
+
736
+ server.registerTool(
737
+ "skill_correction_delete",
738
+ {
739
+ description: "Soft-delete a recorded skill correction by id.",
740
+ inputSchema: { id: z.union([z.string(), z.number()]) },
741
+ },
742
+ async ({ id }) =>
743
+ callApi("DELETE", `/api/skill-corrections/${encodeURIComponent(String(id))}`)
744
+ );
745
+
746
+ // ============================================================
747
+ // Brief / persona refresh (signal-driven re-evaluation)
748
+ // ============================================================
749
+
750
+ server.registerTool(
751
+ "deal_refresh_brief",
752
+ {
753
+ description:
754
+ "Trigger a stale-section re-evaluation of a deal's brief. Pass force=true to bypass the " +
755
+ "debounce. Returns a runId (or null if debounced / deal inactive).",
756
+ inputSchema: {
757
+ dealId: z.string(),
758
+ force: z.boolean().optional(),
759
+ },
760
+ },
761
+ async ({ dealId, force }) =>
762
+ callApi(
763
+ "POST",
764
+ `/api/deals/${encodeURIComponent(dealId)}/refresh-brief${force ? "?force=1" : ""}`
765
+ )
766
+ );
767
+
768
+ server.registerTool(
769
+ "deal_refresh_persona",
770
+ {
771
+ description:
772
+ "Regenerate one persona's analysis section for a deal. persona ∈ gavin | kyle | jack | " +
773
+ "david | bryan | herman | hongjiang | liuyi | kevin. Returns a runId (or null if debounced).",
774
+ inputSchema: {
775
+ dealId: z.string(),
776
+ persona: z.string(),
777
+ },
778
+ },
779
+ async ({ dealId, persona }) =>
780
+ callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/refresh-persona`, { persona })
781
+ );
782
+
485
783
  // ============================================================
486
784
  // External pitch (founder intake) — no Llama Command token required
487
785
  // ============================================================
package/bin/llama.mjs CHANGED
@@ -198,8 +198,7 @@ async function searchDeals(q, flags) {
198
198
  return result;
199
199
  }
200
200
 
201
- function usage() {
202
- console.log(`Llama Command CLI
201
+ const HELP_FULL = `Llama Command CLI
203
202
 
204
203
  Agent onboarding (run once on first install):
205
204
  llama agent-onboard # print AGENT_BRIEFING.md — the workflow contract for AI agents
@@ -225,7 +224,15 @@ Manually-set \`llc_\` tokens are used as a fallback.
225
224
  Deals:
226
225
  llama deal create "Company" --source <name> --description "..." --website https://...
227
226
  llama deal show <dealId>
227
+ llama deal feed <dealId> # everything humans added (facts + notes), newest first
228
228
  llama deal update <dealId> <field> <value>
229
+ Writable fields: status, theirStage, stage, notes, dealOwner, source,
230
+ description, website, location, founders, proposedAmount, roundSize,
231
+ valuation, sector, subsector, foundedYear, leadInvestor, investors.
232
+ e.g. llama deal update <dealId> website https://acme.ai
233
+ llama deal update <dealId> sector "Developer Tools"
234
+ llama deal update <dealId> foundedYear 2024
235
+ llama deal update <dealId> leadInvestor "Acme Capital"
229
236
  llama deal search <query> [--founder name] [--owner <user-key>] [--status Diligence]
230
237
  [--theirStage Raising] [--stage Seed]
231
238
  [--limit 200] [--offset 0]
@@ -307,7 +314,7 @@ Deal soft-delete / restore / trash list:
307
314
 
308
315
  Deal facts (AI-extracted or human-asserted, with verification):
309
316
  llama deal fact list <dealId> # ⚠ session-only on server today
310
- llama deal fact add <dealId> --category <cat> --claim "<text>" [--source <url>] [--confidence high|medium|low]
317
+ llama deal fact add <dealId> --category <cat> --claim "<text>" [--source <url>] [--confidence high|medium|low] [--attested]
311
318
  llama deal fact verify <dealId> <factId> --status confirmed|disputed [--corrected-value "..."]
312
319
 
313
320
  Skill corrections (persona-owner pushback — read by persona-watcher):
@@ -403,7 +410,86 @@ Token discovery (in order):
403
410
  Env:
404
411
  LLAMA_TOKEN token override
405
412
  LLAMA_API_URL API base URL override
406
- `);
413
+ `;
414
+
415
+ // ── Progressive help (Constitution §1) ──
416
+ // Default `llama` / `llama --help` prints a SHORT root: the command groups +
417
+ // a few starters. Drill into one group with `llama help <area>` (or
418
+ // `llama <area> --help`); `llama help all` prints the full reference above.
419
+ const HELP_ROOT = `Llama Command CLI — the \`llama\` command for the Llama Ventures workbench.
420
+
421
+ Common:
422
+ llama deal search "<name>" find a deal in the pipeline
423
+ llama deal show <dealId> full deal record
424
+ llama deal feed <dealId> everything humans added, newest first
425
+ llama post <dealId> "..." add a note to a deal
426
+ llama agent-onboard print the AI-agent workflow contract
427
+
428
+ Command groups — run \`llama help <group>\` for that group's commands:
429
+ deal create · show · feed · update · search · collaborators · links · delete
430
+ brief brief blocks: list · add · edit · history · refresh
431
+ facts deal facts + skill corrections (the sourced, trust-rated layer)
432
+ timeline timeline · posts · mentions
433
+ wiki cross-deal knowledge entries (markdown or HTML)
434
+ memo long-form HTML investment memo
435
+ html deal-specific HTML artifacts (/deals/<id>/browse/<slug>)
436
+ pitch external founder intake (no token needed)
437
+ ownership claim · nominate · approvals
438
+ admin audit events (system admin only)
439
+ auth setup · tokens · auth status
440
+
441
+ llama help all the full command reference (everything at once)
442
+
443
+ Auth: if you've run \`gcloud auth login\` with your @llamaventures.vc account,
444
+ the CLI auto-detects it — no token needed (\`llc_\` tokens are a fallback).`;
445
+
446
+ // Area → which top-level sections of HELP_FULL belong to it.
447
+ const HELP_AREA_MATCH = {
448
+ deal: [/^Deals/, /^Collaborators/, /^Soft-delete/, /^Deal links/, /^Deal soft-delete/],
449
+ brief: [/^Brief blocks/, /^Brief \/ persona/],
450
+ facts: [/^Deal facts/, /^Skill corrections/],
451
+ timeline: [/^Timeline/, /^Mentions/],
452
+ wiki: [/^Wiki/, /^Where does this HTML/],
453
+ memo: [/^Memo/],
454
+ html: [/^Deal page HTML/],
455
+ pitch: [/^External pitch/],
456
+ ownership: [/^Ownership/, /^Approvals/],
457
+ admin: [/^Admin/],
458
+ auth: [/^Setup/, /^Zero-config/, /^Token discovery/, /^Env/],
459
+ };
460
+
461
+ // Slice HELP_FULL into sections: a top-level (non-indented) header line plus
462
+ // the indented/blank lines that follow it, until the next header.
463
+ function helpSections() {
464
+ const out = [];
465
+ let cur = null;
466
+ for (const line of HELP_FULL.split("\n")) {
467
+ if (/^[A-Za-z]/.test(line)) {
468
+ cur = { head: line, lines: [line] };
469
+ out.push(cur);
470
+ } else if (cur) {
471
+ cur.lines.push(line);
472
+ }
473
+ }
474
+ return out;
475
+ }
476
+
477
+ function usage(area) {
478
+ if (area === "all") {
479
+ console.log(HELP_FULL);
480
+ return;
481
+ }
482
+ const matchers = area && HELP_AREA_MATCH[area];
483
+ if (matchers) {
484
+ const blocks = helpSections()
485
+ .filter((s) => matchers.some((re) => re.test(s.head)))
486
+ .map((s) => s.lines.join("\n").replace(/\s+$/, ""));
487
+ if (blocks.length) {
488
+ console.log(blocks.join("\n\n"));
489
+ return;
490
+ }
491
+ }
492
+ console.log(HELP_ROOT);
407
493
  }
408
494
 
409
495
  // ============================================================
@@ -681,7 +767,12 @@ async function main() {
681
767
  return;
682
768
  }
683
769
  if (!area || area === "help" || area === "--help" || area === "-h") {
684
- usage();
770
+ usage(area === "help" ? action : undefined);
771
+ return;
772
+ }
773
+ // `llama <area> --help` / `-h` → just that group's commands
774
+ if (action === "--help" || action === "-h") {
775
+ usage(area);
685
776
  return;
686
777
  }
687
778
 
@@ -971,6 +1062,13 @@ https://command.llamaventures.vc/settings/tokens, run
971
1062
  return;
972
1063
  }
973
1064
 
1065
+ if (area === "deal" && action === "feed") {
1066
+ const dealId = rest[0];
1067
+ if (!dealId) throw new Error("Usage: llama deal feed <dealId>");
1068
+ print(await request("GET", `/api/deals/${encodeURIComponent(dealId)}/feed`));
1069
+ return;
1070
+ }
1071
+
974
1072
  if (area === "deal" && action === "update") {
975
1073
  const [dealId, field, ...valueParts] = rest;
976
1074
  const value = valueParts.join(" ");
@@ -1147,14 +1245,18 @@ https://command.llamaventures.vc/settings/tokens, run
1147
1245
  if (!flags.category || !flags.claim) {
1148
1246
  throw new Error(
1149
1247
  `Usage: llama deal fact add <dealId> --category <cat> --claim "<text>" ` +
1150
- `[--source <url>] [--confidence high|medium|low]`
1248
+ `[--source <url>] [--confidence high|medium|low] [--attested]`
1151
1249
  );
1152
1250
  }
1251
+ // --attested: the caller takes responsibility that this is accurate
1252
+ // (verified against the source). With it, the fact is recorded as
1253
+ // vouched; without it, it stays unverified. Declare honestly.
1153
1254
  print(await request("POST", `/api/deals/${encodeURIComponent(dealId)}/facts`, {
1154
1255
  category: String(flags.category),
1155
1256
  claim: String(flags.claim),
1156
1257
  source: flags.source ? String(flags.source) : "",
1157
1258
  confidence: flags.confidence ? String(flags.confidence) : "medium",
1259
+ attested: flags.attested === true,
1158
1260
  }));
1159
1261
  return;
1160
1262
  }
@@ -2160,8 +2262,8 @@ Routing — is this the right command?
2160
2262
  const { flags } = parseFlags(rest.slice(1), knownFlags);
2161
2263
 
2162
2264
  // --slug is the natural agent guess (DB column is `document_slug`).
2163
- // Accept it as an alias for --doc so the failure mode that bit
2164
- // Gavin (silent fall-through to 'main') can't happen again.
2265
+ // Accept it as an alias for --doc so the earlier failure mode
2266
+ // (silent fall-through to 'main') can't happen again.
2165
2267
  if (flags.slug && !flags.doc) {
2166
2268
  process.stderr.write("note: --slug accepted as alias for --doc.\n");
2167
2269
  flags.doc = flags.slug;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "bin": {