@llamaventures/cli 1.5.0 → 1.6.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,13 +29,69 @@ Don't:
29
29
 
30
30
  Conversation produces value → that value flows somewhere. This is not optional.
31
31
 
32
+ ### Where does this HTML / thesis / artifact go? (decision tree)
33
+
34
+ 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.
35
+
36
+ ```
37
+ HTML / thesis / artifact in hand
38
+
39
+
40
+ ┌─────────────────────────────────────────────────┐
41
+ │ Is it about ONE specific company or deal? │
42
+ │ (deal IC memo · dashboard for X · X 的 thesis │
43
+ │ · founder briefing for X · X 的 2×2 …) │
44
+ └──────────────┬──────────────────────────────────┘
45
+
46
+ yes ────►│ → Llama Command DEAL page
47
+ │ `llama html upload <dealId> --new --title "..." --file <path>`
48
+ │ Renders at /deals/<id>/browse/<slug>.
49
+ │ Use --doc <slug> + --file to update an existing one.
50
+
51
+ no ────►│ Is it cross-deal / institutional knowledge?
52
+ │ (sector landscape · market map · framework · firm-level
53
+ │ thesis · methodology · "AI infra in 2026" …)
54
+
55
+ │ yes ──► → Llama Command WIKI entry
56
+ │ Markdown body:
57
+ │ `llama wiki save <slug> --title "..." \`
58
+ │ ` --content "..." --sources "..."`
59
+ │ Standalone HTML page (full-viewport iframe):
60
+ │ `llama wiki save <slug> --title "..." \`
61
+ │ ` --file <path>.html --sources "..."`
62
+ │ Renders at /wiki/<slug>. Sources mandatory.
63
+
64
+ │ no ──► Likely doesn't need to live anywhere
65
+ │ durable — confirm with the user before
66
+ │ inventing a destination.
67
+
68
+ ┌─────────────────────────────────────────────────┐
69
+ │ Does the user EXPLICITLY say "share with │
70
+ │ founder" / "public share link" / "give it to │
71
+ │ <external person>" / "publish publicly"? │
72
+ └──────────────┬──────────────────────────────────┘
73
+
74
+ no ────►│ → STAY on Llama Command. Don't reach for Netlify.
75
+
76
+ yes ────►│ → Netlify (only this case).
77
+ │ Use the `netlify-access-guard` workflow:
78
+ │ server-side password + edge 401 verification.
79
+ │ Verify enforcement is at the Netlify edge, not a
80
+ │ browser-side JS fake.
81
+ ```
82
+
83
+ **Default bias:** when in doubt, route to Llama Command. It has auth, audit, search, backlinks, and lives next to the rest of the team's context. Netlify is the escape hatch for genuinely-external surfaces — not "where pretty HTML goes."
84
+
85
+ The table below details the exact CLI for each destination.
86
+
32
87
  | Type | Destination | How |
33
88
  |---|---|---|
34
89
  | Deal metadata (status, stage, valuation, founders, notes, etc.) | Pipeline (Postgres) | `llama deal create` / `llama deal update` |
35
90
  | Brief blocks (text / link / embed / callout) | Pipeline | `llama brief add-text` / `add-link` / `add-callout` |
36
91
  | **HTML artifact, internal — IC report, dashboard, market map, 2×2, any hand-authored page** | **Llama Command native** (Postgres + sandboxed iframe at `/deals/<id>/browse/<slug>`) | Default path when the user says "deploy to llama", "deploy to llama command", "部署到 llama command", "put this HTML on the deal page", "在 deal 里看这个". **You MUST declare intent — "new artifact" vs "update existing":**<br><br>**New artifact:** `llama html upload <dealId> --new --title "<artifact name>" --file <path>` (CLI slugifies the title; pass `--doc <slug>` to override).<br>**Update existing:** `llama html upload <dealId> --doc <slug> --file <path>` (slug must already exist — run `llama html docs <dealId>` first to see what's there).<br><br>The bare form `llama html upload <id> --file <path>` REFUSES if `main` already has content. Do NOT default to Netlify for internal pages. |
37
92
  | HTML artifact, external — founder-facing share link | Netlify | Only when the user explicitly says "share link", "give it to the founder", "publish publicly". Use the `netlify-access-guard` workflow (server-side password + edge 401 verification). |
38
- | Insights, decisions, framework improvements | Wiki | `llama wiki save` (with attribution — see below) |
93
+ | Insights, decisions, framework improvements | Wiki (markdown) | `llama wiki save <slug> --content "..."` (with attribution — see below) |
94
+ | **HTML wiki entry — standalone HTML page hosted at `/wiki/<slug>`** (sector landscape, market map, dashboard, hand-styled thesis page) | **Wiki (HTML)** | `llama wiki save <slug> --title "..." --file <path.html> --sources "..."`. Auto-detects content_type=html from extension. Public page is full-viewport sandboxed iframe takeover (no wiki chrome). Sources/status/title still required; appears in `wiki search` + backlinks. Use when the user says "deploy this HTML to wiki", "wiki 词条", "make this page a wiki entry". HTML must be self-contained (inline CSS/JS, image data URIs or external URLs) — asset bundles aren't supported on wiki yet. |
39
95
  | Large files (deck / PDF / transcript) | Drive deal folder | the deal's `folder_url` (from `llama deal show`) → upload via your filesystem / Drive tool |
40
96
  | Cross-team mentions | Inbox + email | `llama post <dealId> "@<teammate> ..."` — server fires email + UI badge to the recipient |
41
97
 
@@ -138,7 +194,17 @@ llama html reset <dealId> [--doc <slug>] # soft-
138
194
 
139
195
  # Wiki (knowledge base)
140
196
  llama wiki search "<query>"
141
- llama wiki save <slug> --title "..." --content "..."
197
+ llama wiki read <slug> [--lang en|zh]
198
+
199
+ # Markdown entry (default):
200
+ llama wiki save <slug> --title "..." --content "..." --sources "url1;url2"
201
+
202
+ # HTML entry — standalone page at /wiki/<slug>, full-viewport sandboxed iframe:
203
+ llama wiki save <slug> --title "..." --file path.html --sources "..." [--content-type html]
204
+ # .html / .htm extension auto-implies content_type=html.
205
+ # --content-type html (or markdown) overrides the inference.
206
+ # Refuses to switch content_type on an existing slug; delete + re-create
207
+ # if you really mean to change format.
142
208
 
143
209
  # Timeline + posts
144
210
  llama timeline <dealId>
@@ -165,7 +231,7 @@ Tools available:
165
231
  - `auth_status` — verify creds + identity (call first if anything 401s)
166
232
  - `deal_search` / `deal_show` / `deal_create` / `deal_update`
167
233
  - `brief_blocks` / `brief_add_text` / `brief_add_link` / `brief_add_callout`
168
- - `wiki_search` / `wiki_save`
234
+ - `wiki_search` / `wiki_save` (accepts `content_type: 'markdown' | 'html'` — HTML entries render as full-viewport sandboxed iframe at `/wiki/<slug>`)
169
235
  - `timeline` / `post`
170
236
  - `mentions_list`
171
237
  - `pitch_start` / `pitch_send_message` / `pitch_upload_file` / `pitch_status` / `pitch_finalize` — public intake (no Llama token needed; for founders / EAs / external agents)
package/bin/llama-mcp.mjs CHANGED
@@ -328,24 +328,56 @@ server.registerTool(
328
328
  "wiki_save",
329
329
  {
330
330
  description:
331
- "Create or update a wiki page. Content should be markdown with attribution " +
332
- "blocks (**[Name · YYYY-MM-DD · source · fact|opinion]**) for traceability. " +
333
- "`sources` is a separate citation list (URLs, doc names, or meeting references) " +
334
- " at least one is required; URLs embedded inside `content` do not count.",
331
+ "Create or update a wiki page Llama's CROSS-DEAL / institutional " +
332
+ "knowledge surface (sector landscape · market map · thesis · framework · " +
333
+ "methodology · anything not tied to ONE specific deal). Renders at " +
334
+ "/wiki/<slug>. " +
335
+ "**Routing — decide BEFORE calling:** " +
336
+ "(a) Deal-specific HTML (IC memo for X, dashboard for X) → use " +
337
+ "`html_upload` instead, NOT this. " +
338
+ "(b) Cross-deal / institutional (this tool) → /wiki/<slug>. " +
339
+ "(c) Founder-facing public share → Netlify only when user explicitly " +
340
+ "says so; Llama Command outranks Netlify for everything internal. " +
341
+ "By default `content` is markdown with attribution blocks " +
342
+ "(**[Name · YYYY-MM-DD · source · fact|opinion]**) for traceability. " +
343
+ "Set `content_type: 'html'` to deploy a standalone HTML page as the " +
344
+ "wiki entry (full-viewport sandboxed iframe takeover on /wiki/<slug>; " +
345
+ "the HTML itself is the page — no wiki chrome). `sources` is a " +
346
+ "separate citation list (URLs, doc names, or meeting references) — " +
347
+ "at least one required; URLs inside `content` do not count. For HTML " +
348
+ "asset bundles use the `llama wiki save --file ... --assets ...` CLI " +
349
+ "path; MCP only supports single-file HTML.",
335
350
  inputSchema: {
336
351
  slug: z.string().describe("kebab-case slug"),
337
352
  title: z.string(),
338
- content: z.string().describe("markdown content"),
353
+ content: z
354
+ .string()
355
+ .describe(
356
+ "body — markdown source by default, or raw HTML when content_type='html'"
357
+ ),
339
358
  sources: z
340
359
  .array(z.string())
341
360
  .min(1)
342
361
  .describe(
343
362
  "citation list — URLs, doc names, or meeting references. At least one required."
344
363
  ),
364
+ content_type: z
365
+ .enum(["markdown", "html"])
366
+ .optional()
367
+ .describe(
368
+ "'markdown' (default) renders via the wiki markdown pipeline. " +
369
+ "'html' stores the body as a standalone HTML page (sandboxed iframe)."
370
+ ),
345
371
  },
346
372
  },
347
- async ({ slug, title, content, sources }) =>
348
- callApi("POST", "/api/wiki/save", { slug, title, content, sources })
373
+ async ({ slug, title, content, sources, content_type }) =>
374
+ callApi("POST", "/api/wiki/save", {
375
+ slug,
376
+ title,
377
+ content,
378
+ sources,
379
+ ...(content_type ? { content_type } : {}),
380
+ })
349
381
  );
350
382
 
351
383
  // ============================================================
@@ -707,7 +739,16 @@ server.registerTool(
707
739
  "html_upload",
708
740
  {
709
741
  description:
710
- "Upload (PUT) a new HTML version for a deal's /browse page. " +
742
+ "Upload (PUT) a new HTML version for a SPECIFIC DEAL's /browse page " +
743
+ "(deal-scoped artifact: IC memo for X · dashboard for X · 2×2 for X). " +
744
+ "Renders at /deals/<id>/browse/<slug>. " +
745
+ "**Routing — pick the right destination BEFORE calling this:** " +
746
+ "(a) Deal-specific HTML (this tool) → /deals/<id>/browse/<slug>. " +
747
+ "(b) Cross-deal / institutional / thesis / sector landscape → use " +
748
+ "`wiki_save` with content_type='html' instead (/wiki/<slug>). " +
749
+ "(c) Founder-facing public share link → escape to Netlify only when " +
750
+ "the user explicitly says 'share with founder' / 'publish publicly'; " +
751
+ "Llama Command outranks Netlify for everything internal. " +
711
752
  "Creates a NEW version row — the previous version is retained " +
712
753
  "and restorable. Triggers SSE push so any open viewer auto- " +
713
754
  "refreshes. Constraints: HTML body MUST start with " +
package/bin/llama.mjs CHANGED
@@ -324,10 +324,25 @@ Mentions / Inbox:
324
324
  llama mentions resolve <mentionId> # mark thread resolved (idempotent)
325
325
  llama mentions unread # just the badge count
326
326
 
327
+ Where does this HTML / thesis / artifact go?
328
+ About ONE specific deal? ........ llama html upload <dealId> --new --title "..." --file <path>
329
+ (renders at /deals/<id>/browse/<slug>; see "Deal page HTML" below)
330
+ Cross-deal / institutional? ..... llama wiki save <slug> --title "..." --file <path>.html --sources "..."
331
+ (renders at /wiki/<slug>; see "Wiki" below)
332
+ Founder-facing public share? .... Netlify (with netlify-access-guard skill), only when user explicitly
333
+ says "share publicly". Llama Command outranks Netlify for everything
334
+ internal — don't reach for Netlify by default.
335
+
327
336
  Wiki:
328
337
  llama wiki search <query>
329
338
  llama wiki read <slug>
330
- llama wiki save <slug> --title "..." --content "..." --sources "url1;url2" [--type company] [--related "A;B"]
339
+ Markdown entry (default):
340
+ llama wiki save <slug> --title "..." --content "..." --sources "url1;url2" [--type company] [--related "A;B"]
341
+ HTML entry — standalone HTML page at /wiki/<slug> (full-viewport sandboxed iframe):
342
+ llama wiki save <slug> --title "..." --file path.html --sources "..." [--content-type html]
343
+ (.html / .htm extension auto-implies content_type=html)
344
+ ➜ Use Wiki when the artifact is NOT tied to one specific deal — sector landscape, market map,
345
+ thesis, framework, methodology. For deal-specific HTML use "llama html upload <dealId>" instead.
331
346
 
332
347
  Memo (long-form HTML investment memo — Memo tab in the UI):
333
348
  llama memo show <dealId> [--out <path>] [--json] # default: html → stdout (pipeable to file / browser)
@@ -336,6 +351,9 @@ Memo (long-form HTML investment memo — Memo tab in the UI):
336
351
  llama memo reset <dealId> [--all] # default drops manual override; --all drops every version
337
352
 
338
353
  Deal page HTML (hand-authored sandboxed pages on /deals/<id>/browse/<slug>):
354
+ ➜ Use this for DEAL-SPECIFIC artifacts: IC memo for X, dashboard for X, 2×2 for X.
355
+ For cross-deal / institutional pages (sector landscape, market map, thesis) use
356
+ "llama wiki save <slug> --file ..." instead — see "Wiki" above.
339
357
  Each deal can host many HTML artifacts (IC report, dashboard, market map, …).
340
358
  Each one has a stable slug. UPLOAD must declare intent — update an existing
341
359
  artifact or add a new one — to avoid silent overwrites.
@@ -1337,27 +1355,75 @@ https://command.llamaventures.vc/settings/tokens, run
1337
1355
  }
1338
1356
 
1339
1357
  // ----- Wiki: save (create or update) -----
1358
+ // Two body modes:
1359
+ // --content "..." inline markdown OR raw HTML (string)
1360
+ // --file <path> read body from a file; if .html/.htm,
1361
+ // content_type auto-detects to 'html'
1362
+ // --content-type <markdown|html> overrides auto-detect.
1363
+ // Refuses content_type mismatch on existing entries (server-side check;
1364
+ // CLI surfaces the server error verbatim).
1340
1365
  if (area === "wiki" && action === "save") {
1341
1366
  const { flags, positional } = parseFlags(rest);
1342
1367
  const slug = positional[0];
1343
1368
  const title = flags.title;
1344
- const content = flags.content;
1369
+ const inlineContent = flags.content;
1370
+ const filePath = flags.file;
1345
1371
  const sourcesRaw = flags.sources;
1346
- if (!slug || !title || !content || !sourcesRaw) {
1372
+ if (!slug || !title || !sourcesRaw || (!inlineContent && !filePath)) {
1347
1373
  throw new Error(
1348
- `Usage: llama wiki save <slug> --title "..." --content "..." --sources "url1;url2" [--type company] [--related "A;B"] [--lang en|zh]`
1374
+ `Usage:
1375
+ llama wiki save <slug> --title "..." --content "..." --sources "url1;url2" [--type company] [--related "A;B"] [--lang en|zh] [--content-type markdown|html]
1376
+ or
1377
+ llama wiki save <slug> --title "..." --file path/to/article.{md,html} --sources "url1;url2" [--type company] [--related "A;B"] [--lang en|zh] [--content-type markdown|html]
1378
+
1379
+ Pass either --content (inline) or --file (read from disk). With --file, content_type auto-detects from extension (.html/.htm → html, else markdown). Use --content-type to override.
1380
+
1381
+ Routing — is this the right command?
1382
+ ✓ Cross-deal / institutional knowledge (sector landscape, market map, thesis, framework, methodology)
1383
+ → YES, you're in the right place.
1384
+ ✗ Deal-specific HTML (IC memo for X, dashboard for X, 2×2 for one company)
1385
+ → use \`llama html upload <dealId> --new --title "..." --file <path>\` instead.
1386
+ ✗ Founder-facing public share link
1387
+ → escape to Netlify only when the user explicitly says "share publicly" / "give it to the founder";
1388
+ Llama Command outranks Netlify for everything internal.`
1349
1389
  );
1350
1390
  }
1391
+ if (inlineContent && filePath) {
1392
+ throw new Error("Pass either --content OR --file, not both.");
1393
+ }
1394
+ // Read body — either inline or from file.
1395
+ let body;
1396
+ let inferredType = "markdown";
1397
+ if (filePath) {
1398
+ const { readFileSync } = await import("fs");
1399
+ body = readFileSync(String(filePath), "utf-8");
1400
+ const lower = String(filePath).toLowerCase();
1401
+ if (lower.endsWith(".html") || lower.endsWith(".htm")) {
1402
+ inferredType = "html";
1403
+ }
1404
+ } else {
1405
+ body = String(inlineContent);
1406
+ }
1407
+ // Determine content_type: explicit flag wins over file-extension inference.
1408
+ let contentType = inferredType;
1409
+ if (flags["content-type"]) {
1410
+ const v = String(flags["content-type"]).toLowerCase();
1411
+ if (v !== "markdown" && v !== "html") {
1412
+ throw new Error(`--content-type must be 'markdown' or 'html', got "${v}"`);
1413
+ }
1414
+ contentType = v;
1415
+ }
1351
1416
  const splitCsv = (v) => String(v).split(/[;|]/).map((s) => s.trim()).filter(Boolean);
1352
1417
  const payload = {
1353
1418
  slug,
1354
1419
  title: String(title),
1355
- content: String(content),
1420
+ content: body,
1356
1421
  sources: splitCsv(sourcesRaw),
1357
1422
  type: flags.type ? String(flags.type) : undefined,
1358
1423
  related: flags.related ? splitCsv(flags.related) : undefined,
1359
1424
  lang: flags.lang === "zh" ? "zh" : "en",
1360
1425
  status: flags.status ? String(flags.status) : undefined,
1426
+ content_type: contentType,
1361
1427
  };
1362
1428
  print(await request("POST", "/api/wiki/save", payload));
1363
1429
  return;
@@ -2052,7 +2118,18 @@ https://command.llamaventures.vc/settings/tokens, run
2052
2118
  "\n" +
2053
2119
  "Default (no --doc, no --new) targets slug 'main' but REFUSES if 'main'\n" +
2054
2120
  "already has content — pass --doc main to update it explicitly, or\n" +
2055
- "--new --title \"...\" to add a NEW artifact alongside.",
2121
+ "--new --title \"...\" to add a NEW artifact alongside.\n" +
2122
+ "\n" +
2123
+ "Routing — is this the right command?\n" +
2124
+ " ✓ DEAL-specific HTML (IC memo for X, dashboard for X, 2×2 for X)\n" +
2125
+ " → YES, you're in the right place. Pass <dealId> + --new / --doc.\n" +
2126
+ " ✗ Cross-deal / institutional knowledge (sector landscape, market map,\n" +
2127
+ " thesis, framework, methodology, anything not tied to one company)\n" +
2128
+ " → use `llama wiki save <slug> --title \"...\" --file <path>.html --sources \"...\"`\n" +
2129
+ " instead (renders at /wiki/<slug>).\n" +
2130
+ " ✗ Founder-facing public share link\n" +
2131
+ " → escape to Netlify only when the user explicitly says \"share publicly\";\n" +
2132
+ " Llama Command outranks Netlify for everything internal.",
2056
2133
  );
2057
2134
  }
2058
2135
  const knownFlags = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "bin": {