@llamaventures/cli 1.18.1 → 1.20.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/bin/llama.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { createRequire } from "module";
4
4
  import { randomUUID } from "crypto";
5
+ import { readFile } from "fs/promises";
5
6
  import readline from "readline";
6
7
  import {
7
8
  DEFAULT_BASE_URL,
@@ -11,7 +12,6 @@ import {
11
12
  TOKEN_FILE,
12
13
  getAuthHeaders,
13
14
  getBaseUrl,
14
- getLastAgentEvent,
15
15
  getToken,
16
16
  print,
17
17
  readBriefing,
@@ -35,6 +35,7 @@ import {
35
35
  import { LLAMA_CLI_CLIENT_ID, pkceLoopbackFlow, revokeToken as revokeOAuthToken } from "../lib/oauth-flow.mjs";
36
36
  import { deleteBundle, detectBackend, readBundle, writeBundle } from "../lib/oauth-storage.mjs";
37
37
  import { maybeNudgeUpdate, getUpdateNudge } from "../lib/version-check.mjs";
38
+ import { getBuildInfo } from "../lib/build-info.mjs";
38
39
 
39
40
  const requireFromHere = createRequire(import.meta.url);
40
41
  const { version: PKG_VERSION } = requireFromHere("../package.json");
@@ -174,45 +175,6 @@ function slugifyTitle(title) {
174
175
  return slug;
175
176
  }
176
177
 
177
- function parseExpectedIds(raw) {
178
- const text = typeof raw === "string" ? raw : "";
179
- const expected = { dealIds: [], wikiSlugs: [], raw: [] };
180
- for (const item of text.split(",").map((s) => s.trim()).filter(Boolean)) {
181
- const [kind, ...rest] = item.split(":");
182
- const value = rest.join(":").trim();
183
- if (kind === "deal" && value) expected.dealIds.push(value);
184
- else if ((kind === "wiki" || kind === "slug") && value) expected.wikiSlugs.push(value);
185
- else expected.raw.push(item);
186
- }
187
- return expected;
188
- }
189
-
190
- async function submitEvalFeedback(action, flags, queryText = "") {
191
- const useLast = flags.last !== false;
192
- const last = useLast ? getLastAgentEvent() : null;
193
- const eventId =
194
- flags.event && flags.event !== true
195
- ? Number(flags.event)
196
- : last?.lastEventId ?? null;
197
- if ((action === "good" || action === "bad") && !eventId && !queryText) {
198
- throw new Error(`Usage: llama eval ${action} [--last] [--reason "..."]`);
199
- }
200
- const body = {
201
- action,
202
- eventId: Number.isFinite(eventId) ? eventId : undefined,
203
- query: queryText || undefined,
204
- surface: flags.surface && flags.surface !== true ? String(flags.surface) : last?.lastSurface ?? undefined,
205
- expected:
206
- flags.expect && flags.expect !== true
207
- ? parseExpectedIds(String(flags.expect))
208
- : {},
209
- reason: flags.reason && flags.reason !== true ? String(flags.reason) : undefined,
210
- privacyLevel:
211
- flags.privacy && flags.privacy !== true ? String(flags.privacy) : "internal",
212
- };
213
- return request("POST", "/api/agent/eval-feedback", body);
214
- }
215
-
216
178
  // Client-side fuzzy match — used as a fallback when the server hasn't yet
217
179
  // shipped the search/filter API (Fix B, 2026-04-25). Once the server
218
180
  // returns the `{deals,total,limit,offset}` envelope, this path is never
@@ -402,8 +364,8 @@ Agent onboarding (run once on first install):
402
364
  llama activity new-deals --since 24h # recent deal creations for agents
403
365
  llama activity updated-deals --since 7d # meaningful deal updates, grouped
404
366
  llama explain <url-or-object> # explain Command URL/object status + lifecycle
405
- llama eval good|bad --last # mark the latest CLI/MCP result for eval
406
- llama eval add "<query>" --expect wiki:<slug>|deal:<uuid>
367
+ llama pref list [--status proposed] # standing agent preferences (injected every turn)
368
+ llama pref add reply-style "Lead with the conclusion." [--team]
407
369
 
408
370
  External pitch — talk to Llama Ventures' intake agent (no token required):
409
371
  llama pitch start --name "Jane Doe" --email "jane@acme.ai"
@@ -425,6 +387,7 @@ Manually-set \`llc_\` tokens are used as a fallback.
425
387
 
426
388
  Deals:
427
389
  llama deal create "Company" --source <name> --deal-owner <name|email|userId> --source-direction Inbound|Outbound --description "..." --status Interested|Outreached|Sourced --website https://...
390
+ llama deal founders set <dealId> --json '[{"name":"Ada","email":"ada@example.com","linkedin_url":"https://linkedin.com/in/ada"}]'
428
391
  llama deal show <dealId>
429
392
  llama deal feed <dealId> # every contribution (facts + notes), human-typed or assistant-drafted, newest first
430
393
  llama deal update <dealId> <field> <value>
@@ -512,7 +475,7 @@ Brief blocks:
512
475
 
513
476
  Common flags on every add-*:
514
477
  --source-section <key> Target a structured section (team, highlights, recommendation,
515
- <persona>_analysis, ...). Without this, blocks land in "_other"
478
+ landscape_map, competitors). Without this, blocks land in "_other"
516
479
  at the bottom of the TOC. AI writers want this.
517
480
  --reply-to <blockId> Make the block a reply to <blockId>. Snapshots parent's heading
518
481
  + 200-char excerpt into meta so the back-link survives parent
@@ -521,10 +484,9 @@ Brief blocks:
521
484
  2026-05-03). Use bottom for batched writes that need to
522
485
  preserve insertion order.
523
486
 
524
- Brief / persona refresh + agent-run revert:
487
+ Brief refresh + agent-run revert:
525
488
  llama deal refresh-brief <dealId> [--force] # re-eval stale sections
526
489
  # --force = every unlocked watcher-managed section
527
- llama deal refresh-persona <dealId> <persona-key> # server validates persona key
528
490
  llama deal revert-run <dealId> <runId> --section <key> # legacy 4-section model only
529
491
  # section: company|team|highlights|recommendation
530
492
 
@@ -534,17 +496,13 @@ Deal soft-delete / restore / trash list:
534
496
  llama deal trash # list deleted deals
535
497
 
536
498
  Deal facts (AI-extracted or human-asserted, with verification):
499
+ llama deal ingest <dealId> --file <packet.json> [--idempotency-key <key>] # atomic facts + optional Feed note
500
+ packet: {"source":{"kind":"meeting_note","title":"Office visit"},"facts":[{"category":"team","claim":"..."}],"note":"..."}
501
+ categories: company_basics | team | product | market | financials | fundraise | risk | milestone | meta
537
502
  llama deal fact list <dealId>
538
503
  llama deal fact add <dealId> --category <cat> --claim "<text>" [--source "..."] [--source-url <url>] [--confidence high|medium|low] [--attested]
539
504
  llama deal fact verify <dealId> <factId> --status confirmed|disputed [--corrected-value "..."]
540
505
 
541
- Skill corrections (persona-owner pushback — read by persona-watcher):
542
- llama skill-correction list <skill-slug> [--include-deleted]
543
- llama skill-correction add <skill-slug> "<correction text>" [--deal <uuid>] [--block <blockId>]
544
- llama skill-correction delete <id>
545
- Server enforces persona owner OR system admin on POST/DELETE; GET is open.
546
- External personas (owner_email=null) are admin-only for write.
547
-
548
506
  Mentions / Inbox:
549
507
  llama mentions # default: my unresolved cues
550
508
  llama mentions list [--everyone] [--all] # --everyone = team-wide; --all = include resolved
@@ -661,7 +619,6 @@ Common:
661
619
  llama agent bootstrap live Llama OS skill manifest from Command
662
620
  llama skills search "<query>" discover which skill to read
663
621
  llama explain <url-or-object> explain Command URLs, 404s, deleted objects
664
- llama eval bad --last mark latest CLI/MCP result as an eval candidate
665
622
 
666
623
  Command groups — run \`llama help <group>\` for that group's commands:
667
624
  deal create · show · feed · update · enrich · search · collaborators · links · delete
@@ -670,7 +627,7 @@ Command groups — run \`llama help <group>\` for that group's commands:
670
627
  facts deal facts + skill corrections (the sourced, trust-rated layer)
671
628
  timeline timeline · posts · mentions
672
629
  wiki cross-deal knowledge entries (markdown or HTML)
673
- eval mark real CLI/MCP searches good/bad or add a golden-query candidate
630
+ pref standing agent preferences: list · add · retire · approve
674
631
  memo long-form HTML investment memo
675
632
  html deal-specific HTML artifacts (/deals/<id>/browse/<slug>)
676
633
  pitch external founder intake (no token needed)
@@ -689,7 +646,7 @@ the CLI auto-detects it — no token needed (\`llc_\` tokens are a fallback).`;
689
646
  const HELP_AREA_MATCH = {
690
647
  deal: [/^Deals/, /^Collaborators/, /^Soft-delete/, /^Deal links/, /^Deal soft-delete/],
691
648
  activity: [/^Agent activity/],
692
- brief: [/^Brief blocks/, /^Brief \/ persona/],
649
+ brief: [/^Brief blocks/],
693
650
  facts: [/^Deal facts/, /^Skill corrections/],
694
651
  timeline: [/^Timeline/, /^Mentions/],
695
652
  wiki: [/^Wiki/, /^Where does this HTML/],
@@ -1005,6 +962,10 @@ async function runPitchRepl() {
1005
962
  async function main() {
1006
963
  const [area, action, ...rest] = process.argv.slice(2);
1007
964
  if (area === "--version" || area === "-v" || area === "version") {
965
+ if (action === "--json" || action === "json") {
966
+ print(getBuildInfo());
967
+ return;
968
+ }
1008
969
  // `llama version --check` — explicitly check npm for a newer release and
1009
970
  // print the upgrade line (or "up to date"). Lets an agent surface the
1010
971
  // nudge on demand, separate from the throttled, TTY-gated auto-nudge.
@@ -1158,6 +1119,49 @@ async function main() {
1158
1119
  throw new Error(`Unknown skills subcommand "${sub}". Use: list / search / show.`);
1159
1120
  }
1160
1121
 
1122
+ // `llama pref ...` — standing agent preferences (learning-domain v1).
1123
+ // Own user scope activates immediately; team scope needs a system admin.
1124
+ if (area === "pref" || area === "prefs" || area === "preferences") {
1125
+ const sub = action;
1126
+ if (!sub || sub === "list") {
1127
+ const { flags } = parseFlags(rest, ["json", "status"]);
1128
+ const params = new URLSearchParams();
1129
+ if (flags.status && flags.status !== true) params.set("status", String(flags.status));
1130
+ const result = await request("GET", `/api/agent/preferences${params.toString() ? `?${params}` : ""}`);
1131
+ print(result);
1132
+ return;
1133
+ }
1134
+ if (sub === "add") {
1135
+ const { flags, positional } = parseFlags(rest, ["team", "evidence", "json"]);
1136
+ const key = positional[0];
1137
+ const content = positional.slice(1).join(" ").trim();
1138
+ if (!key || !content) {
1139
+ throw new Error('Usage: llama pref add <key> "<content, max 280 chars>" [--team] [--evidence "..."]');
1140
+ }
1141
+ const result = await request("POST", "/api/agent/preferences", {
1142
+ scope: flags.team ? "team" : "user",
1143
+ key,
1144
+ content,
1145
+ evidence: flags.evidence && flags.evidence !== true ? String(flags.evidence) : undefined,
1146
+ });
1147
+ print(result);
1148
+ return;
1149
+ }
1150
+ if (sub === "retire" || sub === "approve") {
1151
+ const { positional } = parseFlags(rest, ["json"]);
1152
+ const id = Number(positional[0]);
1153
+ if (!Number.isInteger(id) || id <= 0) {
1154
+ throw new Error(`Usage: llama pref ${sub} <id>`);
1155
+ }
1156
+ const result = await request("PATCH", `/api/agent/preferences/${id}`, {
1157
+ status: sub === "approve" ? "active" : "retired",
1158
+ });
1159
+ print(result);
1160
+ return;
1161
+ }
1162
+ throw new Error(`Unknown pref subcommand "${sub}". Use: list / add / retire / approve.`);
1163
+ }
1164
+
1161
1165
  if (area === "explain" || (area === "agent" && action === "explain")) {
1162
1166
  const args = area === "explain" ? [action, ...rest].filter(Boolean) : rest;
1163
1167
  const { flags, positional } = parseFlags(args, ["json", "type", "id", "lang"]);
@@ -1195,43 +1199,6 @@ async function main() {
1195
1199
  return;
1196
1200
  }
1197
1201
 
1198
- if (area === "eval") {
1199
- const sub = action;
1200
- if (sub === "good" || sub === "bad") {
1201
- const { flags, positional } = parseFlags(rest, [
1202
- "last",
1203
- "event",
1204
- "reason",
1205
- "expect",
1206
- "surface",
1207
- "privacy",
1208
- ]);
1209
- const q = positional.join(" ").trim();
1210
- print(await submitEvalFeedback(sub, flags, q));
1211
- return;
1212
- }
1213
- if (sub === "add") {
1214
- const { flags, positional } = parseFlags(rest, [
1215
- "event",
1216
- "expect",
1217
- "reason",
1218
- "surface",
1219
- "privacy",
1220
- ]);
1221
- const q = positional.join(" ").trim();
1222
- if (!q && !flags.event) {
1223
- throw new Error(
1224
- `Usage: llama eval add "<query>" --surface deal|wiki --expect wiki:<slug>|deal:<uuid>`,
1225
- );
1226
- }
1227
- print(await submitEvalFeedback("add", flags, q));
1228
- return;
1229
- }
1230
- throw new Error(
1231
- "Usage: llama eval good|bad [--last] [--reason ...] OR llama eval add \"<query>\" --expect wiki:<slug>|deal:<uuid>",
1232
- );
1233
- }
1234
-
1235
1202
  // `llama pitch ...` — external founder-pitch family. No Llama token
1236
1203
  // required; bootstraps a session against /api/external/* via PoW + cookie.
1237
1204
  // See lib/external.mjs and AGENT_BRIEFING.md for the full surface.
@@ -1484,6 +1451,26 @@ async function main() {
1484
1451
  return;
1485
1452
  }
1486
1453
 
1454
+ if (area === "deal" && action === "founders") {
1455
+ const sub = rest[0];
1456
+ const dealId = rest[1];
1457
+ const { flags } = parseFlags(rest.slice(2), ["json"]);
1458
+ if (sub !== "set" || !dealId || typeof flags.json !== "string") {
1459
+ throw new Error(
1460
+ "Usage: llama deal founders set <dealId> --json '[{\"name\":\"Ada\",\"email\":\"ada@example.com\"}]'"
1461
+ );
1462
+ }
1463
+ let founders;
1464
+ try {
1465
+ founders = JSON.parse(flags.json);
1466
+ } catch {
1467
+ throw new Error("--json must be a valid JSON array");
1468
+ }
1469
+ if (!Array.isArray(founders)) throw new Error("--json must be a JSON array");
1470
+ print(await request("PUT", `/api/deals/${encodeURIComponent(dealId)}/founders`, { founders }));
1471
+ return;
1472
+ }
1473
+
1487
1474
  // ----- deals.extra JSONB patches (system-admin only, server-gated) -----
1488
1475
  // Same endpoint as `deal update`, but `extraKey` instead of `field`.
1489
1476
  // Server patches one top-level key via jsonb_set and audits the change
@@ -1700,6 +1687,8 @@ async function main() {
1700
1687
  const linkId = rest[2];
1701
1688
  if (!linkId) throw new Error(`Usage: llama deal link ${sub} <dealId> <linkId>`);
1702
1689
  const path = `/api/deals/${encodeURIComponent(dealId)}/links/${encodeURIComponent(linkId)}`;
1690
+ // @core-api-operation DELETE /api/deals/{dealId}/links/{linkId}
1691
+ // @core-api-operation POST /api/deals/{dealId}/links/{linkId}/restore
1703
1692
  print(await request(
1704
1693
  sub === "delete" ? "DELETE" : "POST",
1705
1694
  sub === "delete" ? path : `${path}/restore`
@@ -1735,6 +1724,36 @@ async function main() {
1735
1724
  return;
1736
1725
  }
1737
1726
 
1727
+ // ----- Canonical source-packet ingest (atomic facts + optional Feed note) -----
1728
+ if (area === "deal" && action === "ingest") {
1729
+ const dealId = rest[0];
1730
+ const { flags } = parseFlags(rest.slice(1), ["file", "idempotency-key"]);
1731
+ if (!dealId || !flags.file || flags.file === true) {
1732
+ throw new Error(
1733
+ "Usage: llama deal ingest <dealId> --file <packet.json> [--idempotency-key <key>]\n" +
1734
+ 'Packet: {"source":{"kind":"meeting_note","title":"Office visit"},' +
1735
+ '"facts":[{"category":"team","claim":"..."}],"note":"..."}'
1736
+ );
1737
+ }
1738
+
1739
+ let packet;
1740
+ try {
1741
+ packet = JSON.parse(await readFile(String(flags.file), "utf8"));
1742
+ } catch (error) {
1743
+ throw new Error(`Cannot read ingest packet ${flags.file}: ${error?.message ?? String(error)}`);
1744
+ }
1745
+ if (!packet || Array.isArray(packet) || typeof packet !== "object") {
1746
+ throw new Error("Ingest packet must be a JSON object");
1747
+ }
1748
+ if (flags["idempotency-key"] !== undefined && flags["idempotency-key"] !== true) {
1749
+ packet.idempotencyKey = String(flags["idempotency-key"]);
1750
+ }
1751
+
1752
+ // @core-api-operation POST /api/deals/{dealId}/ingest
1753
+ print(await request("POST", `/api/deals/${encodeURIComponent(dealId)}/ingest`, packet));
1754
+ return;
1755
+ }
1756
+
1738
1757
  // ----- Deal facts (AI-extracted or human-asserted, with verification) -----
1739
1758
  if (area === "deal" && action === "fact") {
1740
1759
  const sub = rest[0];
@@ -1811,24 +1830,6 @@ async function main() {
1811
1830
  return;
1812
1831
  }
1813
1832
 
1814
- // ----- Persona refresh: re-run a single persona-watcher -----
1815
- // Persona keys are validated server-side. Returns runId or null
1816
- // (debounced / deal inactive). Used by /admin and the per-block
1817
- // "重新生成" flow.
1818
- if (area === "deal" && action === "refresh-persona") {
1819
- const dealId = rest[0];
1820
- const persona = rest[1];
1821
- if (!dealId || !persona) {
1822
- throw new Error(`Usage: llama deal refresh-persona <dealId> <persona-key>`);
1823
- }
1824
- print(await request(
1825
- "POST",
1826
- `/api/deals/${encodeURIComponent(dealId)}/refresh-persona`,
1827
- { persona }
1828
- ));
1829
- return;
1830
- }
1831
-
1832
1833
  // ----- Agent-run revert (legacy 4-section brief model) -----
1833
1834
  // Reverts a single section to the `before` value snapshotted by the
1834
1835
  // watcher run. Does NOT re-fire the watcher (intentional: human action).
@@ -1967,6 +1968,7 @@ async function main() {
1967
1968
  if (!slug) throw new Error("Usage: llama wiki read <slug> [--lang en|zh]");
1968
1969
  const lang = flags.lang === "zh" ? "zh" : "en";
1969
1970
  const path = `/api/wiki/${encodeURIComponent(slug)}?lang=${lang}`;
1971
+ // @core-api-operation GET /api/wiki/{slug}
1970
1972
  print(await request("GET", path));
1971
1973
  return;
1972
1974
  }
@@ -2106,7 +2108,7 @@ Routing — is this the right command?
2106
2108
  const meta = { updated_at: new Date().toISOString(), updated_by: "cli", by_agent: false };
2107
2109
 
2108
2110
  // --source-section <key>: target a structured section (e.g. team /
2109
- // highlights / <persona>_analysis). Without this, blocks land in the
2111
+ // highlights / competitors). Without this, blocks land in the
2110
2112
  // "_other" group at the bottom of the TOC. AI writers want this
2111
2113
  // virtually always — without it they cannot contribute to existing
2112
2114
  // structured sections.
@@ -2298,6 +2300,9 @@ Routing — is this the right command?
2298
2300
  if (flags["errors-only"]) params.set("errors_only", "1");
2299
2301
  }
2300
2302
  const qs = params.toString() ? `?${params.toString()}` : "";
2303
+ // @core-api-operation GET /api/admin/auth-events
2304
+ // @core-api-operation GET /api/admin/deal-events
2305
+ // @core-api-operation GET /api/admin/agent-events
2301
2306
  print(await request("GET", `/api/admin/${sub}${qs}`));
2302
2307
  return;
2303
2308
  }
@@ -2347,63 +2352,6 @@ Routing — is this the right command?
2347
2352
  throw new Error(`Unknown mentions subcommand "${sub}". Use: list / show / resolve / unread.`);
2348
2353
  }
2349
2354
 
2350
- // ----- Skill corrections (persona-owner pushback workflow) -----
2351
- // Persona owners (or system admins) record long-term rules each
2352
- // persona-DD skill must obey. Read by the persona-watcher and prepended
2353
- // to the system prompt at run time. Soft-delete is non-cascading —
2354
- // deleting a row does NOT auto-fire watcher; the next natural run
2355
- // (manual / stale_re_eval) just stops including the deleted rule.
2356
- //
2357
- // Permissions enforced server-side: only the persona owner (per
2358
- // PERSONA_SKILLS in src/lib/persona-skills.ts) or a system admin can
2359
- // POST or DELETE. Anyone can GET. External personas (owner_email=null)
2360
- // are admin-only for write.
2361
- if (area === "skill-correction") {
2362
- const sub = action;
2363
-
2364
- if (sub === "list") {
2365
- const skillSlug = rest[0];
2366
- if (!skillSlug) {
2367
- throw new Error("Usage: llama skill-correction list <skill-slug> [--include-deleted]");
2368
- }
2369
- const { flags } = parseFlags(rest.slice(1));
2370
- const params = new URLSearchParams({ skill: skillSlug });
2371
- if (flags["include-deleted"]) params.set("include_deleted", "1");
2372
- print(await request("GET", `/api/skill-corrections?${params.toString()}`));
2373
- return;
2374
- }
2375
-
2376
- if (sub === "add") {
2377
- const { flags, positional } = parseFlags(rest);
2378
- const skillSlug = positional[0];
2379
- const correctionText = positional.slice(1).join(" ").trim();
2380
- if (!skillSlug || !correctionText) {
2381
- throw new Error(
2382
- `Usage: llama skill-correction add <skill-slug> "<correction text>" ` +
2383
- `[--deal <uuid>] [--block <blockId>]`
2384
- );
2385
- }
2386
- print(await request("POST", "/api/skill-corrections", {
2387
- skill_slug: skillSlug,
2388
- correction_text: correctionText,
2389
- triggered_in_deal_uuid: flags.deal ? String(flags.deal) : null,
2390
- triggered_in_block_id: flags.block ? String(flags.block) : null,
2391
- }));
2392
- return;
2393
- }
2394
-
2395
- if (sub === "delete") {
2396
- const id = rest[0];
2397
- if (!id) throw new Error("Usage: llama skill-correction delete <id>");
2398
- print(await request("DELETE", `/api/skill-corrections/${encodeURIComponent(id)}`));
2399
- return;
2400
- }
2401
-
2402
- throw new Error(
2403
- `Unknown skill-correction subcommand "${sub || ""}". Use: list / add / delete.`
2404
- );
2405
- }
2406
-
2407
2355
  // ----- Memo (long-form HTML investment memo) -----
2408
2356
  // The Memo tab in the deal page renders HTML stored in deal_memos.
2409
2357
  // Two sources of memo content:
@@ -0,0 +1,9 @@
1
+ {
2
+ "format": "llama.core-api-contract.v1",
3
+ "name": "llama-core-api",
4
+ "apiVersion": "3.1.0",
5
+ "openapiVersion": "3.0.3",
6
+ "sha256": "51b134af7a63534406b3a216aeab190fd595f5270b2a10263730662f1c2f771a",
7
+ "pathCount": 212,
8
+ "operationCount": 275
9
+ }