@llamaventures/cli 1.7.0 → 1.9.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
@@ -11,6 +11,15 @@ You are not just an AI assistant. You're an **extension of a team member** — w
11
11
  - **Be direct, terse, action-oriented.** Save your words for the genuine judgment calls.
12
12
  - **Critical when thinking, helpful when executing.** Push back on weak logic, then ship the work cleanly.
13
13
 
14
+ ## Onboard the human (teach as you go)
15
+
16
+ Most teammates don't know everything this CLI can do. Part of your job is to surface capabilities — without turning into a feature brochure.
17
+
18
+ - **First substantive interaction:** in one or two lines, point at the 2-3 capabilities most relevant to what they're doing right now, then do the work. Don't dump the whole command surface. (Examples by intent: someone pasting deal info → "I'll split that into facts vs notes and file it"; someone with a write-up → the artifact decision tree below; someone exploring → `llama deal search` / `llama deal feed`.)
19
+ - **Teach in context, one line at a time.** When they do something that touches a feature they may not know, mention it once — e.g. after filing a fact: "Filed. `llama deal feed <id>` shows everything the team's added on this deal." Never more than one such aside per turn.
20
+ - **Point at `llama --help`** for the full surface rather than reciting it. The CLI uses progressive help: `llama --help` is a short overview, `llama <area> --help` drills in.
21
+ - **Stay current.** If you suspect the CLI is stale, run `llama version --check`; if it reports an upgrade, tell the user the one-line `npm i -g @llamaventures/cli@latest` command. Don't nag repeatedly.
22
+
14
23
  ## Pipeline First (hard rule)
15
24
 
16
25
  Any time the user mentions a company name or founder name:
@@ -29,6 +38,19 @@ Don't:
29
38
 
30
39
  Conversation produces value → that value flows somewhere. This is not optional.
31
40
 
41
+ ### When someone gives you info about a deal (the most common case)
42
+
43
+ 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:
44
+
45
+ 1. **Find the deal** — `llama deal search "<name>"` (Pipeline First). New name → offer to create it.
46
+ 2. **Split what they gave you into two kinds** — this is the whole data model:
47
+ - **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.
48
+ - **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".
49
+ - A pasted blob → pull the verifiable claims out as facts, capture their take as a note.
50
+ 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.
51
+
52
+ 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`.
53
+
32
54
  ### Where does this HTML / thesis / artifact go? (decision tree)
33
55
 
34
56
  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.
package/bin/llama-mcp.mjs CHANGED
@@ -303,6 +303,22 @@ server.registerTool(
303
303
  callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/blocks`)
304
304
  );
305
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
+
306
322
  server.registerTool(
307
323
  "brief_add_text",
308
324
  {
package/bin/llama.mjs CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  } from "../lib/external.mjs";
31
31
  import { LLAMA_CLI_CLIENT_ID, pkceLoopbackFlow, revokeToken as revokeOAuthToken } from "../lib/oauth-flow.mjs";
32
32
  import { deleteBundle, detectBackend, readBundle, writeBundle } from "../lib/oauth-storage.mjs";
33
+ import { maybeNudgeUpdate, getUpdateNudge } from "../lib/version-check.mjs";
33
34
 
34
35
  function parseFlags(args, knownFlags = null) {
35
36
  const flags = {};
@@ -198,8 +199,7 @@ async function searchDeals(q, flags) {
198
199
  return result;
199
200
  }
200
201
 
201
- function usage() {
202
- console.log(`Llama Command CLI
202
+ const HELP_FULL = `Llama Command CLI
203
203
 
204
204
  Agent onboarding (run once on first install):
205
205
  llama agent-onboard # print AGENT_BRIEFING.md — the workflow contract for AI agents
@@ -225,6 +225,7 @@ Manually-set \`llc_\` tokens are used as a fallback.
225
225
  Deals:
226
226
  llama deal create "Company" --source <name> --description "..." --website https://...
227
227
  llama deal show <dealId>
228
+ llama deal feed <dealId> # everything humans added (facts + notes), newest first
228
229
  llama deal update <dealId> <field> <value>
229
230
  Writable fields: status, theirStage, stage, notes, dealOwner, source,
230
231
  description, website, location, founders, proposedAmount, roundSize,
@@ -410,7 +411,86 @@ Token discovery (in order):
410
411
  Env:
411
412
  LLAMA_TOKEN token override
412
413
  LLAMA_API_URL API base URL override
413
- `);
414
+ `;
415
+
416
+ // ── Progressive help (Constitution §1) ──
417
+ // Default `llama` / `llama --help` prints a SHORT root: the command groups +
418
+ // a few starters. Drill into one group with `llama help <area>` (or
419
+ // `llama <area> --help`); `llama help all` prints the full reference above.
420
+ const HELP_ROOT = `Llama Command CLI — the \`llama\` command for the Llama Ventures workbench.
421
+
422
+ Common:
423
+ llama deal search "<name>" find a deal in the pipeline
424
+ llama deal show <dealId> full deal record
425
+ llama deal feed <dealId> everything humans added, newest first
426
+ llama post <dealId> "..." add a note to a deal
427
+ llama agent-onboard print the AI-agent workflow contract
428
+
429
+ Command groups — run \`llama help <group>\` for that group's commands:
430
+ deal create · show · feed · update · search · collaborators · links · delete
431
+ brief brief blocks: list · add · edit · history · refresh
432
+ facts deal facts + skill corrections (the sourced, trust-rated layer)
433
+ timeline timeline · posts · mentions
434
+ wiki cross-deal knowledge entries (markdown or HTML)
435
+ memo long-form HTML investment memo
436
+ html deal-specific HTML artifacts (/deals/<id>/browse/<slug>)
437
+ pitch external founder intake (no token needed)
438
+ ownership claim · nominate · approvals
439
+ admin audit events (system admin only)
440
+ auth setup · tokens · auth status
441
+
442
+ llama help all the full command reference (everything at once)
443
+
444
+ Auth: if you've run \`gcloud auth login\` with your @llamaventures.vc account,
445
+ the CLI auto-detects it — no token needed (\`llc_\` tokens are a fallback).`;
446
+
447
+ // Area → which top-level sections of HELP_FULL belong to it.
448
+ const HELP_AREA_MATCH = {
449
+ deal: [/^Deals/, /^Collaborators/, /^Soft-delete/, /^Deal links/, /^Deal soft-delete/],
450
+ brief: [/^Brief blocks/, /^Brief \/ persona/],
451
+ facts: [/^Deal facts/, /^Skill corrections/],
452
+ timeline: [/^Timeline/, /^Mentions/],
453
+ wiki: [/^Wiki/, /^Where does this HTML/],
454
+ memo: [/^Memo/],
455
+ html: [/^Deal page HTML/],
456
+ pitch: [/^External pitch/],
457
+ ownership: [/^Ownership/, /^Approvals/],
458
+ admin: [/^Admin/],
459
+ auth: [/^Setup/, /^Zero-config/, /^Token discovery/, /^Env/],
460
+ };
461
+
462
+ // Slice HELP_FULL into sections: a top-level (non-indented) header line plus
463
+ // the indented/blank lines that follow it, until the next header.
464
+ function helpSections() {
465
+ const out = [];
466
+ let cur = null;
467
+ for (const line of HELP_FULL.split("\n")) {
468
+ if (/^[A-Za-z]/.test(line)) {
469
+ cur = { head: line, lines: [line] };
470
+ out.push(cur);
471
+ } else if (cur) {
472
+ cur.lines.push(line);
473
+ }
474
+ }
475
+ return out;
476
+ }
477
+
478
+ function usage(area) {
479
+ if (area === "all") {
480
+ console.log(HELP_FULL);
481
+ return;
482
+ }
483
+ const matchers = area && HELP_AREA_MATCH[area];
484
+ if (matchers) {
485
+ const blocks = helpSections()
486
+ .filter((s) => matchers.some((re) => re.test(s.head)))
487
+ .map((s) => s.lines.join("\n").replace(/\s+$/, ""));
488
+ if (blocks.length) {
489
+ console.log(blocks.join("\n\n"));
490
+ return;
491
+ }
492
+ }
493
+ console.log(HELP_ROOT);
414
494
  }
415
495
 
416
496
  // ============================================================
@@ -684,11 +764,24 @@ async function main() {
684
764
  const { createRequire } = await import("module");
685
765
  const requireFromHere = createRequire(import.meta.url);
686
766
  const { version } = requireFromHere("../package.json");
767
+ // `llama version --check` — explicitly check npm for a newer release and
768
+ // print the upgrade line (or "up to date"). Lets an agent surface the
769
+ // nudge on demand, separate from the throttled, TTY-gated auto-nudge.
770
+ if (action === "--check" || action === "check") {
771
+ const nudge = await getUpdateNudge();
772
+ console.log(nudge || `llama CLI ${version} — up to date`);
773
+ return;
774
+ }
687
775
  console.log(version);
688
776
  return;
689
777
  }
690
778
  if (!area || area === "help" || area === "--help" || area === "-h") {
691
- usage();
779
+ usage(area === "help" ? action : undefined);
780
+ return;
781
+ }
782
+ // `llama <area> --help` / `-h` → just that group's commands
783
+ if (action === "--help" || action === "-h") {
784
+ usage(area);
692
785
  return;
693
786
  }
694
787
 
@@ -978,6 +1071,13 @@ https://command.llamaventures.vc/settings/tokens, run
978
1071
  return;
979
1072
  }
980
1073
 
1074
+ if (area === "deal" && action === "feed") {
1075
+ const dealId = rest[0];
1076
+ if (!dealId) throw new Error("Usage: llama deal feed <dealId>");
1077
+ print(await request("GET", `/api/deals/${encodeURIComponent(dealId)}/feed`));
1078
+ return;
1079
+ }
1080
+
981
1081
  if (area === "deal" && action === "update") {
982
1082
  const [dealId, field, ...valueParts] = rest;
983
1083
  const value = valueParts.join(" ");
@@ -2542,7 +2642,12 @@ Routing — is this the right command?
2542
2642
  process.exitCode = 1;
2543
2643
  }
2544
2644
 
2545
- main().catch((error) => {
2546
- console.error(`Error: ${error.message}`);
2547
- process.exit(1);
2548
- });
2645
+ main()
2646
+ // Soft, throttled, TTY-gated update nudge. Runs AFTER the command's own
2647
+ // output and is awaited so the registry check completes before exit — but
2648
+ // it can never fail the command (all errors swallowed internally).
2649
+ .then(() => maybeNudgeUpdate())
2650
+ .catch((error) => {
2651
+ console.error(`Error: ${error.message}`);
2652
+ process.exit(1);
2653
+ });
@@ -0,0 +1,118 @@
1
+ // Soft update nudge for @llamaventures/cli.
2
+ //
3
+ // Philosophy: minimal friction. We NEVER block a command and we NEVER spam.
4
+ // The npm registry is the source of truth for "latest published", so we
5
+ // query it directly — no llama-command changes, nothing to keep in sync.
6
+ //
7
+ // Friction controls, all of which must pass before we print anything:
8
+ // 1. TTY-gated — only nudge an interactive human (process.stdout.isTTY).
9
+ // MCP server / piped output / CI → silent, so agents and
10
+ // scripts never see noise that could corrupt parsed output.
11
+ // 2. Throttled — at most once per 24h, tracked by a timestamp file.
12
+ // 3. stderr only — the nudge never touches stdout, so `llama ... | jq` etc
13
+ // stay clean even in the rare case it does print.
14
+ // 4. Best-effort — every error path (offline, registry down, parse fail) is
15
+ // swallowed. A nudge must never break or delay a command.
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { createRequire } from "module";
20
+ import { TOKEN_DIR } from "./client.mjs";
21
+
22
+ const REGISTRY_URL = "https://registry.npmjs.org/@llamaventures/cli/latest";
23
+ const STAMP_FILE = path.join(TOKEN_DIR, ".update-check");
24
+ const THROTTLE_MS = 24 * 60 * 60 * 1000; // once per day
25
+ const FETCH_TIMEOUT_MS = 2000;
26
+
27
+ function installedVersion() {
28
+ try {
29
+ const requireFromHere = createRequire(import.meta.url);
30
+ return requireFromHere("../package.json").version;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ // Compare two semver strings. Returns true if `latest` is strictly newer than
37
+ // `current`. Pre-release tags are ignored (we only ship plain x.y.z).
38
+ function isNewer(latest, current) {
39
+ const a = String(latest).split(".").map((n) => parseInt(n, 10));
40
+ const b = String(current).split(".").map((n) => parseInt(n, 10));
41
+ for (let i = 0; i < 3; i++) {
42
+ const x = a[i] || 0;
43
+ const y = b[i] || 0;
44
+ if (x > y) return true;
45
+ if (x < y) return false;
46
+ }
47
+ return false;
48
+ }
49
+
50
+ function checkedRecently() {
51
+ try {
52
+ const last = parseInt(fs.readFileSync(STAMP_FILE, "utf8").trim(), 10);
53
+ return Number.isFinite(last) && Date.now() - last < THROTTLE_MS;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function touchStamp() {
60
+ try {
61
+ fs.mkdirSync(TOKEN_DIR, { recursive: true, mode: 0o700 });
62
+ fs.writeFileSync(STAMP_FILE, `${Date.now()}\n`, { mode: 0o600 });
63
+ } catch {
64
+ // Best-effort. If we can't write the stamp we may nudge again tomorrow —
65
+ // harmless. Never throw.
66
+ }
67
+ }
68
+
69
+ async function fetchLatest() {
70
+ const controller = new AbortController();
71
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
72
+ try {
73
+ // The /latest convenience endpoint returns full JSON under the default
74
+ // Accept. (The abbreviated "vnd.npm.install-v1+json" metadata type is only
75
+ // valid on the full packument endpoint — it 406s here.)
76
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
77
+ if (!res.ok) return null;
78
+ const data = await res.json();
79
+ return typeof data?.version === "string" ? data.version : null;
80
+ } catch {
81
+ return null;
82
+ } finally {
83
+ clearTimeout(timer);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Compute the nudge line without any side effects (no TTY/throttle gating).
89
+ * Returns the one-line string if an upgrade is available, else null. Used by
90
+ * the explicit `llama version --check` command so an agent can surface it.
91
+ */
92
+ export async function getUpdateNudge() {
93
+ const current = installedVersion();
94
+ if (!current) return null;
95
+ const latest = await fetchLatest();
96
+ if (!latest || !isNewer(latest, current)) return null;
97
+ return `⬆ llama CLI ${current} → ${latest} available · npm i -g @llamaventures/cli@latest`;
98
+ }
99
+
100
+ /**
101
+ * Fire-and-forget soft nudge for interactive humans. Safe to call without
102
+ * awaiting; resolves to true if it printed a nudge, false otherwise. Honors
103
+ * all four friction controls above. Set $LLAMA_NO_UPDATE_CHECK=1 to disable.
104
+ */
105
+ export async function maybeNudgeUpdate() {
106
+ try {
107
+ if (process.env.LLAMA_NO_UPDATE_CHECK) return false;
108
+ if (!process.stdout.isTTY) return false; // humans only
109
+ if (checkedRecently()) return false;
110
+ touchStamp(); // stamp before the network call so a slow/failed check still throttles
111
+ const nudge = await getUpdateNudge();
112
+ if (!nudge) return false;
113
+ process.stderr.write(`${nudge}\n`);
114
+ return true;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "bin": {