@llamaventures/cli 1.6.1 → 1.7.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
@@ -157,6 +157,9 @@ llama deal list [--owner ...] [--status ...]
157
157
  # Pipeline — write
158
158
  llama deal create "Company" --description "..."
159
159
  llama deal update <dealId> <field> <value>
160
+ # writable: status theirStage stage notes dealOwner source description website
161
+ # location founders proposedAmount roundSize valuation sector subsector
162
+ # foundedYear leadInvestor investors (each write logs a deal_events row)
160
163
 
161
164
  # Brief blocks
162
165
  llama brief blocks <dealId>
@@ -244,6 +247,7 @@ You can also fetch this exact briefing as an MCP prompt named `agent_briefing`.
244
247
  ## Boundaries (what NOT to do)
245
248
 
246
249
  - **Don't impersonate a human's opinion.** Tag AI-generated content as `[AI · …]`.
250
+ - **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
251
  - **Don't use absolute language** ("only", "all", "best", "no one", "极") unless verifiable.
248
252
  - **Don't bypass `llama` CLI / MCP for pipeline writes.** CSRF defence, rate limits, audit logs all flow through it.
249
253
  - **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
  // ============================================================
@@ -272,6 +349,108 @@ server.registerTool(
272
349
  addBriefBlock(dealId, { type: "callout", tone, heading: heading ?? "", body })
273
350
  );
274
351
 
352
+ server.registerTool(
353
+ "brief_edit",
354
+ {
355
+ description:
356
+ "Edit an existing brief block in place. Pass only the fields you want to change " +
357
+ "(heading/body/url/label/description/tone). Meta toggles: locked (protect from bulk " +
358
+ "overwrite), hidden (fold), sourceSection (route watcher writes). Snapshots the prior " +
359
+ "version to history (reversible via brief_restore_version).",
360
+ inputSchema: {
361
+ dealId: z.string(),
362
+ blockId: z.string(),
363
+ heading: z.string().optional(),
364
+ body: z.string().optional(),
365
+ url: z.string().optional(),
366
+ label: z.string().optional(),
367
+ description: z.string().optional(),
368
+ tone: z.string().optional(),
369
+ locked: z.boolean().optional(),
370
+ hidden: z.boolean().optional(),
371
+ sourceSection: z.string().optional(),
372
+ },
373
+ },
374
+ async ({ dealId, blockId, heading, body, url, label, description, tone, locked, hidden, sourceSection }) => {
375
+ const patch = {};
376
+ for (const [k, v] of Object.entries({ heading, body, url, label, description, tone })) {
377
+ if (v !== undefined) patch[k] = v;
378
+ }
379
+ const meta = {};
380
+ if (locked !== undefined) meta.locked = locked;
381
+ if (hidden !== undefined) meta.hidden = hidden;
382
+ if (sourceSection !== undefined) meta.sourceSection = sourceSection;
383
+ if (Object.keys(meta).length > 0) patch.meta = meta;
384
+ return callApi(
385
+ "PATCH",
386
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`,
387
+ patch
388
+ );
389
+ }
390
+ );
391
+
392
+ server.registerTool(
393
+ "brief_delete",
394
+ {
395
+ description:
396
+ "Soft-delete a brief block (reversible via brief_restore). Locked blocks are refused.",
397
+ inputSchema: { dealId: z.string(), blockId: z.string() },
398
+ },
399
+ async ({ dealId, blockId }) =>
400
+ callApi("DELETE", `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`)
401
+ );
402
+
403
+ server.registerTool(
404
+ "brief_restore",
405
+ {
406
+ description: "Restore a soft-deleted brief block.",
407
+ inputSchema: { dealId: z.string(), blockId: z.string() },
408
+ },
409
+ async ({ dealId, blockId }) =>
410
+ callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/restore`)
411
+ );
412
+
413
+ server.registerTool(
414
+ "brief_history",
415
+ {
416
+ description:
417
+ "List the content-version history of a brief block (every overwrite is snapshotted). " +
418
+ "Use the returned history id with brief_restore_version.",
419
+ inputSchema: {
420
+ dealId: z.string(),
421
+ blockId: z.string(),
422
+ limit: z.number().optional(),
423
+ },
424
+ },
425
+ async ({ dealId, blockId, limit }) => {
426
+ const qs = limit ? `?limit=${encodeURIComponent(String(limit))}` : "";
427
+ return callApi(
428
+ "GET",
429
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/history${qs}`
430
+ );
431
+ }
432
+ );
433
+
434
+ server.registerTool(
435
+ "brief_restore_version",
436
+ {
437
+ description:
438
+ "Restore a brief block to a specific historical version (find historyId via brief_history). " +
439
+ "Itself reversible — the outgoing version is snapshotted before replacement.",
440
+ inputSchema: {
441
+ dealId: z.string(),
442
+ blockId: z.string(),
443
+ historyId: z.number(),
444
+ },
445
+ },
446
+ async ({ dealId, blockId, historyId }) =>
447
+ callApi(
448
+ "POST",
449
+ `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}/history`,
450
+ { history_id: historyId }
451
+ )
452
+ );
453
+
275
454
  // ============================================================
276
455
  // Wiki (knowledge base)
277
456
  // ============================================================
@@ -482,6 +661,109 @@ server.registerTool(
482
661
  }
483
662
  );
484
663
 
664
+ server.registerTool(
665
+ "mentions_resolve",
666
+ {
667
+ description: "Mark an @-mention as resolved (clears it from the recipient's open cues).",
668
+ inputSchema: { mentionId: z.union([z.string(), z.number()]) },
669
+ },
670
+ async ({ mentionId }) =>
671
+ callApi("POST", `/api/mentions/${encodeURIComponent(String(mentionId))}/resolve`)
672
+ );
673
+
674
+ // ============================================================
675
+ // Skill corrections (persona-owner pushback workflow)
676
+ // ============================================================
677
+
678
+ server.registerTool(
679
+ "skill_correction_list",
680
+ {
681
+ description:
682
+ "List the recorded corrections (long-term rules) for a persona/skill. These shape how " +
683
+ "that persona's analysis is generated.",
684
+ inputSchema: {
685
+ skillSlug: z.string(),
686
+ includeDeleted: z.boolean().optional(),
687
+ },
688
+ },
689
+ async ({ skillSlug, includeDeleted }) => {
690
+ const params = new URLSearchParams({ skill: skillSlug });
691
+ if (includeDeleted) params.set("include_deleted", "1");
692
+ return callApi("GET", `/api/skill-corrections?${params}`);
693
+ }
694
+ );
695
+
696
+ server.registerTool(
697
+ "skill_correction_add",
698
+ {
699
+ description:
700
+ "Record a long-term correction rule for a persona/skill (e.g. 'always check burn multiple " +
701
+ "before commenting on efficiency'). ALWAYS reconfirm the distilled rule with the user before " +
702
+ "calling — this changes how the persona behaves going forward. Optionally tie it to the deal/" +
703
+ "block where it came up.",
704
+ inputSchema: {
705
+ skillSlug: z.string(),
706
+ correctionText: z.string(),
707
+ dealUuid: z.string().optional(),
708
+ blockId: z.string().optional(),
709
+ },
710
+ },
711
+ async ({ skillSlug, correctionText, dealUuid, blockId }) =>
712
+ callApi("POST", "/api/skill-corrections", {
713
+ skill_slug: skillSlug,
714
+ correction_text: correctionText,
715
+ triggered_in_deal_uuid: dealUuid ?? null,
716
+ triggered_in_block_id: blockId ?? null,
717
+ })
718
+ );
719
+
720
+ server.registerTool(
721
+ "skill_correction_delete",
722
+ {
723
+ description: "Soft-delete a recorded skill correction by id.",
724
+ inputSchema: { id: z.union([z.string(), z.number()]) },
725
+ },
726
+ async ({ id }) =>
727
+ callApi("DELETE", `/api/skill-corrections/${encodeURIComponent(String(id))}`)
728
+ );
729
+
730
+ // ============================================================
731
+ // Brief / persona refresh (signal-driven re-evaluation)
732
+ // ============================================================
733
+
734
+ server.registerTool(
735
+ "deal_refresh_brief",
736
+ {
737
+ description:
738
+ "Trigger a stale-section re-evaluation of a deal's brief. Pass force=true to bypass the " +
739
+ "debounce. Returns a runId (or null if debounced / deal inactive).",
740
+ inputSchema: {
741
+ dealId: z.string(),
742
+ force: z.boolean().optional(),
743
+ },
744
+ },
745
+ async ({ dealId, force }) =>
746
+ callApi(
747
+ "POST",
748
+ `/api/deals/${encodeURIComponent(dealId)}/refresh-brief${force ? "?force=1" : ""}`
749
+ )
750
+ );
751
+
752
+ server.registerTool(
753
+ "deal_refresh_persona",
754
+ {
755
+ description:
756
+ "Regenerate one persona's analysis section for a deal. persona ∈ gavin | kyle | jack | " +
757
+ "david | bryan | herman | hongjiang | liuyi | kevin. Returns a runId (or null if debounced).",
758
+ inputSchema: {
759
+ dealId: z.string(),
760
+ persona: z.string(),
761
+ },
762
+ },
763
+ async ({ dealId, persona }) =>
764
+ callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/refresh-persona`, { persona })
765
+ );
766
+
485
767
  // ============================================================
486
768
  // External pitch (founder intake) — no Llama Command token required
487
769
  // ============================================================
package/bin/llama.mjs CHANGED
@@ -226,6 +226,13 @@ Deals:
226
226
  llama deal create "Company" --source <name> --description "..." --website https://...
227
227
  llama deal show <dealId>
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):
@@ -1147,14 +1154,18 @@ https://command.llamaventures.vc/settings/tokens, run
1147
1154
  if (!flags.category || !flags.claim) {
1148
1155
  throw new Error(
1149
1156
  `Usage: llama deal fact add <dealId> --category <cat> --claim "<text>" ` +
1150
- `[--source <url>] [--confidence high|medium|low]`
1157
+ `[--source <url>] [--confidence high|medium|low] [--attested]`
1151
1158
  );
1152
1159
  }
1160
+ // --attested: the caller takes responsibility that this is accurate
1161
+ // (verified against the source). With it, the fact is recorded as
1162
+ // vouched; without it, it stays unverified. Declare honestly.
1153
1163
  print(await request("POST", `/api/deals/${encodeURIComponent(dealId)}/facts`, {
1154
1164
  category: String(flags.category),
1155
1165
  claim: String(flags.claim),
1156
1166
  source: flags.source ? String(flags.source) : "",
1157
1167
  confidence: flags.confidence ? String(flags.confidence) : "medium",
1168
+ attested: flags.attested === true,
1158
1169
  }));
1159
1170
  return;
1160
1171
  }
@@ -2160,8 +2171,8 @@ Routing — is this the right command?
2160
2171
  const { flags } = parseFlags(rest.slice(1), knownFlags);
2161
2172
 
2162
2173
  // --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.
2174
+ // Accept it as an alias for --doc so the earlier failure mode
2175
+ // (silent fall-through to 'main') can't happen again.
2165
2176
  if (flags.slug && !flags.doc) {
2166
2177
  process.stderr.write("note: --slug accepted as alias for --doc.\n");
2167
2178
  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.7.0",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "bin": {