@llamaventures/cli 1.14.1 → 1.15.1

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
@@ -20,6 +20,17 @@ Most teammates don't know everything this CLI can do. Part of your job is to sur
20
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
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
22
 
23
+ ## Runtime skill library
24
+
25
+ This npm package is public, but Llama OS skills are private. Do not assume the skill text is bundled locally. For team-token sessions, discover the live runtime library through Llama Command:
26
+
27
+ - Start with `llama agent bootstrap` or MCP `agent_bootstrap` when you need the current Command + Llama OS contract.
28
+ - Use `llama skills search "<task>"` or MCP `skills_search` before choosing a Llama workflow.
29
+ - Use `llama skills show <slug>` or MCP `skills_read` only for the relevant skill.
30
+ - Use `llama explain <command-url-or-object>` or MCP `object_inspect` for 404s, deleted wiki pages, notifier links, deal URLs, and unknown Command objects before telling the user "the system is broken."
31
+
32
+ The boundary matters: public CLI/MCP discovers skills, but authenticated Command decides which skill content the token may read.
33
+
23
34
  ## Pipeline First (hard rule)
24
35
 
25
36
  Any time the user mentions a company name or founder name:
@@ -27,6 +38,9 @@ Any time the user mentions a company name or founder name:
27
38
  1. **Run `llama deal search "<name>"` BEFORE web search.** Always. No exceptions.
28
39
  2. If pipeline has it → pull the data, integrate into your reply silently.
29
40
  3. If pipeline doesn't have it → ask once: "New name. Add to pipeline? (Y/n)". On yes, `llama deal create`.
41
+ - Use `--status Outreached` when we only contacted/logged the company and have no response or effective relationship yet.
42
+ - Use `--status Sourced` only once there is a response, intro, meeting, or another real relationship signal.
43
+ - Also set `--source-direction Inbound` if the deal came into the firm; set `--source-direction Outbound` if Llama found/listed/reached out first.
30
44
  4. If user gives you new facts (status / valuation / founder note) → `llama deal update` immediately, tell the user **one line** afterward.
31
45
 
32
46
  Don't:
@@ -190,12 +204,20 @@ llama deal show <dealId>
190
204
  llama deal list [--owner ...] [--status ...]
191
205
 
192
206
  # Pipeline — write
193
- llama deal create "Company" --description "..."
207
+ llama deal create "Company" --description "..." --source-direction Outbound --status Outreached
208
+ llama deal create "Company" --description "..." --source-direction Inbound --status Sourced
194
209
  llama deal update <dealId> <field> <value>
195
- # writable: status theirStage stage notes dealOwner source description website
210
+ # writable: status theirStage stage notes dealOwner source sourceDirection description website
196
211
  # location founders proposedAmount roundSize valuation sector subsector
197
212
  # foundedYear leadInvestor investors (each write logs a deal_events row)
198
213
 
214
+ # Our Stage vocabulary starts with:
215
+ # Outreached → Sourced → First Meeting → Diligence → Partner Meeting → Term Sheet → Invested
216
+ # `Outreached` is relationship memory only. Do not inflate it to `Sourced`
217
+ # unless a real relationship signal exists.
218
+ # `sourceDirection` is separate: Inbound = came into the firm; Outbound =
219
+ # we found/listed/reached out first.
220
+
199
221
  # Brief blocks
200
222
  llama brief blocks <dealId>
201
223
  llama brief add-text <dealId> --heading "..." --body "..."
@@ -259,7 +281,7 @@ Run `llama --help` for the full surface (~40 commands).
259
281
 
260
282
  ## MCP-native agents
261
283
 
262
- If you support [MCP](https://modelcontextprotocol.io), **prefer the MCP server over parsing CLI output.** The same package ships `llama-mcp` (20 typed tools, identical auth chain).
284
+ If you support [MCP](https://modelcontextprotocol.io), **prefer the MCP server over parsing CLI output.** The same package ships `llama-mcp` (56 typed tools, identical auth chain).
263
285
 
264
286
  Add to your MCP client config (Claude Desktop / Claude Code / Cursor / OpenClaw / Codex / etc.):
265
287
 
@@ -270,6 +292,9 @@ Add to your MCP client config (Claude Desktop / Claude Code / Cursor / OpenClaw
270
292
  Tools available:
271
293
 
272
294
  - `auth_status` — verify creds + identity (call first if anything 401s)
295
+ - `agent_bootstrap` — fetch the live Command + Llama OS runtime manifest
296
+ - `skills_search` / `skills_read` — discover and read authenticated runtime skills
297
+ - `object_inspect` — explain Command URLs, 404s, deleted objects, and lifecycle trail
273
298
  - `deal_search` / `deal_show` / `deal_create` / `deal_update`
274
299
  - `brief_blocks` / `brief_add_text` / `brief_add_link` / `brief_add_callout`
275
300
  - `wiki_search` / `wiki_save` (accepts `content_type: 'markdown' | 'html'` — HTML entries render as full-viewport sandboxed iframe at `/wiki/<slug>`) / `wiki_delete` / `wiki_restore` (soft-delete, reversible)
package/CHANGELOG.md CHANGED
@@ -6,6 +6,36 @@ this project adheres to [Semantic Versioning](https://semver.org).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.15.1] — 2026-06-16
10
+
11
+ ### Changed
12
+ - Documented the new `Outreached` Our Stage across CLI, MCP, and agent
13
+ briefing surfaces. Agents should use `Outreached` for logged outreach
14
+ without a response/effective relationship, and reserve `Sourced` for real
15
+ relationship signals.
16
+ - Added `sourceDirection` / `--source-direction` guidance across CLI, MCP, and
17
+ agent briefing surfaces. Use `Inbound` for deals that came into the firm and
18
+ `Outbound` for companies Llama found/listed/reached out to first.
19
+
20
+ ## [1.15.0] — 2026-06-15
21
+
22
+ ### Added
23
+ - **`llama agent bootstrap`** — fetches the live Llama Command + Llama OS
24
+ runtime manifest, including the authenticated skill bundle metadata and
25
+ object-inspection contract.
26
+ - **`llama skills search|show`** — discovers and reads runtime Llama OS skills
27
+ from Llama Command. The public npm package does not bundle private skill
28
+ content; Command returns only what the authenticated token may see.
29
+ - **`llama explain <url-or-object>`** — asks Llama Command to explain URLs,
30
+ deleted objects, 404s, and lifecycle history before an agent guesses that
31
+ the system is broken.
32
+ - MCP parity tools: **`agent_bootstrap`**, **`skills_search`**,
33
+ **`skills_read`**, and **`object_inspect`**.
34
+
35
+ ### Changed
36
+ - `AGENT_BRIEFING.md` now teaches agents to use the authenticated runtime
37
+ skill gateway instead of assuming a local private `llama-os` checkout.
38
+
9
39
  ## [1.14.1] — 2026-06-15
10
40
 
11
41
  ### Added
package/README.md CHANGED
@@ -48,7 +48,7 @@
48
48
  ```
49
49
  @llamaventures/cli
50
50
  ├── bin/llama interactive CLI for humans + bash
51
- └── bin/llama-mcp stdio MCP server, 52 typed tools — for any MCP-native agent
51
+ └── bin/llama-mcp stdio MCP server, 56 typed tools — for any MCP-native agent
52
52
  ```
53
53
 
54
54
  Both binaries share `lib/client.mjs` — the **same** auth chain, **same** HTTP
@@ -180,11 +180,14 @@ llama token show
180
180
 
181
181
  # Pipeline — read
182
182
  llama deal search "acme ai"
183
+ llama deal list --owner alex --status Outreached
184
+ llama deal list --source-direction Outbound --status Outreached
183
185
  llama deal list --owner alex --status Diligence
184
186
  llama deal show <dealId>
185
187
 
186
188
  # Pipeline — write
187
- llama deal create "Acme AI" --description "..." --source Gavin
189
+ llama deal create "Acme AI" --description "..." --source Gavin --source-direction Outbound --status Outreached
190
+ llama deal create "Acme AI" --description "..." --source Gavin --source-direction Inbound --status Sourced
188
191
  llama deal update <dealId> status Diligence
189
192
  llama deal enrich <dealId> --dry-run
190
193
  llama deal enrich <dealId> --apply --executor server_agent
@@ -193,6 +196,13 @@ llama deal agent run <dealId> --message "collect founder evidence and update typ
193
196
  llama deal delete <dealId> # soft (audit-logged)
194
197
  llama deal restore <dealId>
195
198
 
199
+ # Status semantics
200
+ # Outreached = contact/logged, but no response or effective relationship yet.
201
+ # Sourced = response, intro, meeting, or another real relationship signal exists.
202
+ # sourceDirection is separate:
203
+ # Inbound = came into the firm.
204
+ # Outbound = we found/listed/reached out first.
205
+
196
206
  # Deal Brief — ordered, typed blocks (text · link · embed · callout)
197
207
  llama brief blocks <dealId>
198
208
  llama brief add-text <dealId> --heading "..." --body "..."
@@ -211,6 +221,12 @@ llama approvals decide <approvalId> approved --note "..."
211
221
  llama timeline <dealId>
212
222
  llama post <dealId> "message body" [--link url]
213
223
 
224
+ # Agent runtime — live Command + private Llama OS skill gateway
225
+ llama agent bootstrap
226
+ llama skills search "wiki delete tombstone"
227
+ llama skills show llama-command
228
+ llama explain https://command.llamaventures.vc/wiki/some-page
229
+
214
230
  # Wiki
215
231
  llama wiki search "<query>"
216
232
  llama wiki read <slug> [--lang en|zh]
@@ -249,13 +265,14 @@ agents can pattern-match without parsing prose.
249
265
  ## MCP server
250
266
 
251
267
  The bundled `llama-mcp` is a **stdio Model Context Protocol** server exposing
252
- **52 typed tools** that mirror the most-used CLI surface. Every tool is named
268
+ **56 typed tools** that mirror the most-used CLI surface. Every tool is named
253
269
  and scoped — there is no generic API passthrough, by design (a public-package
254
270
  escape hatch reachable from a prompt-injectable agent context is exactly the
255
271
  shape we want to avoid).
256
272
 
257
273
  Coverage is grouped around the workflows agents actually need: auth
258
- diagnostics; deal search/show/create/update/feed; server-side deal agent runs;
274
+ diagnostics; live agent bootstrap; authenticated Llama OS skill search/read;
275
+ Command URL/object inspection; deal search/show/create/update/feed; server-side deal agent runs;
259
276
  deal enrichment harnesses;
260
277
  trust-rated facts; brief blocks and version history; wiki read/write/delete/restore;
261
278
  timeline posts and mentions; skill corrections; refresh triggers; external pitch intake;
@@ -277,6 +294,11 @@ The `agent_briefing` MCP **prompt** also returns
277
294
  [`AGENT_BRIEFING.md`](AGENT_BRIEFING.md) verbatim, so any new agent loading the
278
295
  server can self-onboard without leaving the protocol.
279
296
 
297
+ For current Llama OS skills, use the runtime tools instead of looking for a
298
+ local private repo: `agent_bootstrap`, `skills_search`, `skills_read`, and
299
+ `object_inspect`. The public npm package does not bundle private skill text;
300
+ Command returns only the content visible to the authenticated token.
301
+
280
302
  ### Wire into your agent
281
303
 
282
304
  <details open>
package/README.zh-CN.md CHANGED
@@ -48,7 +48,7 @@
48
48
  ```
49
49
  @llamaventures/cli
50
50
  ├── bin/llama 给人 + bash 用的交互式 CLI
51
- └── bin/llama-mcp 给 MCP 原生 agent 用的 stdio MCP server,52 个工具
51
+ └── bin/llama-mcp 给 MCP 原生 agent 用的 stdio MCP server,56 个工具
52
52
  ```
53
53
 
54
54
  两个 binary 共享 `lib/client.mjs`——**同一**认证链、**同一** HTTP 客户端、
@@ -201,11 +201,14 @@ llama token show
201
201
 
202
202
  # Pipeline——读
203
203
  llama deal search "acme ai"
204
+ llama deal list --owner alex --status Outreached
205
+ llama deal list --source-direction Outbound --status Outreached
204
206
  llama deal list --owner alex --status Diligence
205
207
  llama deal show <dealId>
206
208
 
207
209
  # Pipeline——写
208
- llama deal create "Acme AI" --description "..." --source Gavin
210
+ llama deal create "Acme AI" --description "..." --source Gavin --source-direction Outbound --status Outreached
211
+ llama deal create "Acme AI" --description "..." --source Gavin --source-direction Inbound --status Sourced
209
212
  llama deal update <dealId> status Diligence
210
213
  llama deal enrich <dealId> --dry-run
211
214
  llama deal enrich <dealId> --apply --executor server_agent
@@ -214,6 +217,13 @@ llama deal agent run <dealId> --message "collect founder evidence and update typ
214
217
  llama deal delete <dealId> # 软删除(审计日志记录)
215
218
  llama deal restore <dealId>
216
219
 
220
+ # Status 语义
221
+ # Outreached = 只是联系/记录了,还没有回复或有效关系。
222
+ # Sourced = 已有回复、intro、会议,或其它真实关系信号。
223
+ # sourceDirection 是单独维度:
224
+ # Inbound = 自然流入公司。
225
+ # Outbound = 我们主动发现/建名单/触达。
226
+
217
227
  # Deal Brief——有序的、有类型的 block(text · link · embed · callout)
218
228
  llama brief blocks <dealId>
219
229
  llama brief add-text <dealId> --heading "..." --body "..."
@@ -232,6 +242,12 @@ llama approvals decide <approvalId> approved --note "..."
232
242
  llama timeline <dealId>
233
243
  llama post <dealId> "消息内容" [--link url]
234
244
 
245
+ # Agent runtime——Command 上的 Llama OS skill gateway
246
+ llama agent bootstrap
247
+ llama skills search "wiki delete tombstone"
248
+ llama skills show llama-command
249
+ llama explain https://command.llamaventures.vc/wiki/some-page
250
+
235
251
  # Wiki
236
252
  llama wiki search "<query>"
237
253
  llama wiki save <slug> --title "..." --content "..."
@@ -261,11 +277,12 @@ MCP server 在 `isError: true` 内容里返回相同的前缀,agent 不用解
261
277
  ## MCP server
262
278
 
263
279
  随包发布的 `llama-mcp` 是一个 **stdio Model Context Protocol** server,
264
- 暴露 **52 个 typed tools**——基本镜像 CLI 最常用的命令。每个 tool 都是
280
+ 暴露 **56 个 typed tools**——基本镜像 CLI 最常用的命令。每个 tool 都是
265
281
  具名、scoped 的;**没有**通用的 API passthrough,这是有意设计的(公开
266
282
  package 里一个能被 prompt-injection 触达的逃生通道,正是我们要避开的形状)。
267
283
 
268
- 覆盖面按 agent 真正会用的工作流分组:auth 诊断;deal search/show/create/
284
+ 覆盖面按 agent 真正会用的工作流分组:auth 诊断;live agent bootstrap;
285
+ 授权后的 Llama OS skill search/read;Command URL/object inspect;deal search/show/create/
269
286
  update/feed;服务端 deal agent run;deal enrichment harness;带 trust ladder 的事实;brief blocks
270
287
  和版本历史;wiki 读写删恢复;timeline posts 和 mentions;skill corrections;
271
288
  refresh triggers;外部 pitch intake;memo show/regenerate/save/reset;
@@ -286,6 +303,10 @@ printf '%s\n' \
286
303
  [`AGENT_BRIEFING.md`](AGENT_BRIEFING.md)——刚装上 server 的 agent
287
304
  不用离开协议就能给自己 onboard。
288
305
 
306
+ 要读当前 Llama OS skills,用 runtime tools:`agent_bootstrap`、
307
+ `skills_search`、`skills_read`、`object_inspect`。公开 npm 包不打包私有
308
+ skill 正文;Command 只按当前 token 的权限返回可见内容。
309
+
289
310
  ### 接到你的 agent 上
290
311
 
291
312
  <details open>
package/bin/llama-mcp.mjs CHANGED
@@ -195,6 +195,82 @@ server.registerTool(
195
195
  }
196
196
  );
197
197
 
198
+ server.registerTool(
199
+ "agent_bootstrap",
200
+ {
201
+ description:
202
+ "Fetch the live Llama Command + Llama OS runtime manifest. Use this at " +
203
+ "the start of an agent session to discover current skills, the skill " +
204
+ "bundle version, and the object-inspection contract. Unlike the bundled " +
205
+ "agent_briefing prompt, this comes from authenticated Command runtime.",
206
+ inputSchema: {
207
+ limit: z.number().optional().describe("number of skill summaries to include; default 25"),
208
+ },
209
+ },
210
+ async ({ limit } = {}) => {
211
+ const params = new URLSearchParams();
212
+ if (limit) params.set("limit", String(limit));
213
+ return callApi("GET", `/api/agent/manifest${params.toString() ? `?${params}` : ""}`);
214
+ }
215
+ );
216
+
217
+ server.registerTool(
218
+ "skills_search",
219
+ {
220
+ description:
221
+ "Search the authenticated Llama OS runtime skill library. Call this " +
222
+ "before choosing a workflow for Llama pipeline/wiki/DD/research/ops tasks. " +
223
+ "Returns summaries only; call skills_read for the exact SKILL.md.",
224
+ inputSchema: {
225
+ q: z.string().describe("workflow/task query, e.g. 'wiki delete tombstone' or 'deal DD memo'"),
226
+ limit: z.number().optional().describe("default 20"),
227
+ },
228
+ },
229
+ async ({ q, limit }) => {
230
+ const params = new URLSearchParams({ q });
231
+ if (limit) params.set("limit", String(limit));
232
+ return callApi("GET", `/api/agent/skills?${params}`);
233
+ }
234
+ );
235
+
236
+ server.registerTool(
237
+ "skills_read",
238
+ {
239
+ description:
240
+ "Read one runtime Llama OS skill by slug. Use after skills_search. " +
241
+ "Returns the full SKILL.md content from Llama Command; public npm does " +
242
+ "not bundle private skill text.",
243
+ inputSchema: {
244
+ slug: z.string().describe("skill slug, e.g. llama-command or llama-wiki"),
245
+ },
246
+ },
247
+ async ({ slug }) => callApi("GET", `/api/agent/skills/${encodeURIComponent(slug)}`)
248
+ );
249
+
250
+ server.registerTool(
251
+ "object_inspect",
252
+ {
253
+ description:
254
+ "Explain a Llama Command URL or object id. Use for 404s, deleted wiki " +
255
+ "pages, notifier links, deal URLs, brief blocks, HTML docs, and unknown " +
256
+ "Command objects before guessing that the system is broken.",
257
+ inputSchema: {
258
+ q: z.string().optional().describe("URL or compact query, e.g. wiki:my-slug or a Command URL"),
259
+ type: z.string().optional().describe("explicit object type if not using q"),
260
+ id: z.string().optional().describe("explicit object id if not using q"),
261
+ lang: z.enum(["en", "zh"]).optional().describe("wiki language; default en"),
262
+ },
263
+ },
264
+ async ({ q, type, id, lang } = {}) => {
265
+ const params = new URLSearchParams();
266
+ if (q) params.set("q", q);
267
+ if (type) params.set("type", type);
268
+ if (id) params.set("id", id);
269
+ if (lang) params.set("lang", lang);
270
+ return callApi("GET", `/api/agent/explain?${params}`);
271
+ }
272
+ );
273
+
198
274
  // ============================================================
199
275
  // Deals — read
200
276
  // ============================================================
@@ -204,7 +280,7 @@ server.registerTool(
204
280
  {
205
281
  description:
206
282
  "Search the Llama Ventures deal pipeline. Fuzzy match on company name, " +
207
- "founders, description, founder info, notes, deal owner, source, and location. " +
283
+ "founders, description, founder info, notes, deal owner, source, source direction, and location. " +
208
284
  "Returns up to `limit` deals (default 200, cap 1000).",
209
285
  inputSchema: {
210
286
  q: z.string().optional().describe("fuzzy search query across all text fields"),
@@ -215,7 +291,7 @@ server.registerTool(
215
291
  .string()
216
292
  .optional()
217
293
  .describe(
218
- "exact match on 'Our Stage' (Sourced, First Meeting, Diligence, Partner Meeting, Term Sheet, Invested, Passed, Stalled, Future, Unknown)"
294
+ "exact match on 'Our Stage' (Outreached, Sourced, First Meeting, Diligence, Partner Meeting, Term Sheet, Invested, Passed, Stalled, Future, Unknown). Outreached means contact was logged but no effective relationship/response exists yet."
219
295
  ),
220
296
  theirStage: z.string().optional().describe("exact match on 'Their Stage'"),
221
297
  stage: z
@@ -224,6 +300,10 @@ server.registerTool(
224
300
  .describe(
225
301
  "exact match on Round (Pre-Seed, Seed, Series A, Series B, Series C+, Stealth)"
226
302
  ),
303
+ sourceDirection: z
304
+ .string()
305
+ .optional()
306
+ .describe("exact match on source direction: Inbound, Outbound, or Unknown"),
227
307
  limit: z.number().optional().describe("max results (default 200, cap 1000)"),
228
308
  offset: z.number().optional(),
229
309
  },
@@ -242,7 +322,7 @@ server.registerTool(
242
322
  {
243
323
  description:
244
324
  "Get the full canonical record for one deal by uuid. Includes status, " +
245
- "stage, founders, owner, source, valuation, all whitelisted writable fields, " +
325
+ "stage, founders, owner, source, sourceDirection, valuation, all whitelisted writable fields, " +
246
326
  "and the `extra` JSONB blob.",
247
327
  inputSchema: {
248
328
  dealId: z.string().describe("deal uuid"),
@@ -272,8 +352,17 @@ server.registerTool(
272
352
  .string()
273
353
  .optional()
274
354
  .describe("Pre-Seed | Seed | Series A | Series B | Series C+ | Stealth"),
275
- status: z.string().optional().describe("Our Stage workflow position"),
355
+ status: z
356
+ .string()
357
+ .optional()
358
+ .describe(
359
+ "Our Stage workflow position. Use Outreached when we only contacted/logged them and have no response/effective relationship; use Sourced only once there is a response, intro, meeting, or other real relationship signal."
360
+ ),
276
361
  source: z.string().optional().describe("free-form sourced-by; recommend nominating a user"),
362
+ sourceDirection: z
363
+ .string()
364
+ .optional()
365
+ .describe("Separate direction tag: Inbound if the deal came into the firm; Outbound if Llama found/listed/reached out first; Unknown if unclear."),
277
366
  notes: z.string().optional(),
278
367
  location: z.string().optional(),
279
368
  },
@@ -286,7 +375,7 @@ server.registerTool(
286
375
  {
287
376
  description:
288
377
  "Update a single whitelisted field on a deal. Writable fields: status, theirStage, " +
289
- "notes, stage, dealOwner, source, description, website, location, founders, " +
378
+ "notes, stage, dealOwner, source, sourceDirection, description, website, location, founders, " +
290
379
  "proposedAmount, roundSize, valuation, sector, subsector, foundedYear, leadInvestor, " +
291
380
  "investors. Logs a field_change event in deal_events.",
292
381
  inputSchema: {
@@ -1501,7 +1590,9 @@ server.registerPrompt(
1501
1590
  "contract: identity, Pipeline First rule, content capture, autonomy " +
1502
1591
  "levels (L0/L1/L2/L3), communication style, error recovery, CLI/MCP " +
1503
1592
  "reference, and boundaries. Read this once, internalise it, operate " +
1504
- "accordingly. Same content as `llama agent-onboard` from the CLI.",
1593
+ "accordingly. Same content as `llama agent-onboard` from the CLI. " +
1594
+ "For the live private Llama OS skill library, call agent_bootstrap, " +
1595
+ "skills_search, and skills_read.",
1505
1596
  },
1506
1597
  async () => {
1507
1598
  // Gate the briefing behind a /api/me check so unauthenticated MCP
package/bin/llama.mjs CHANGED
@@ -141,7 +141,7 @@ function clientSideMatch(deal, filters) {
141
141
  const fields = [
142
142
  deal.companyName, deal.founders, deal.founderInfo,
143
143
  deal.description, deal.notes, deal.dealOwner,
144
- deal.source, deal.location,
144
+ deal.source, deal.sourceDirection, deal.location,
145
145
  ];
146
146
  if (!fields.some((f) => incl(f, filters.q))) return false;
147
147
  }
@@ -151,6 +151,7 @@ function clientSideMatch(deal, filters) {
151
151
  if (filters.status && !eq(deal.status, filters.status)) return false;
152
152
  if (filters.theirStage && !eq(deal.theirStage, filters.theirStage)) return false;
153
153
  if (filters.stage && !eq(deal.stage, filters.stage)) return false;
154
+ if (filters.sourceDirection && !eq(deal.sourceDirection, filters.sourceDirection)) return false;
154
155
  return true;
155
156
  }
156
157
 
@@ -158,11 +159,14 @@ function clientSideMatch(deal, filters) {
158
159
  function buildDealsQuery(q, flags) {
159
160
  const params = new URLSearchParams();
160
161
  if (q) params.set("q", q);
161
- for (const key of ["companyName", "founder", "owner", "status", "theirStage", "stage", "limit", "offset"]) {
162
+ for (const key of ["companyName", "founder", "owner", "status", "theirStage", "stage", "sourceDirection", "limit", "offset"]) {
162
163
  if (flags[key] !== undefined && flags[key] !== true) {
163
164
  params.set(key, String(flags[key]));
164
165
  }
165
166
  }
167
+ if (flags["source-direction"] !== undefined && flags["source-direction"] !== true) {
168
+ params.set("sourceDirection", String(flags["source-direction"]));
169
+ }
166
170
  return params;
167
171
  }
168
172
 
@@ -185,6 +189,7 @@ async function searchDeals(q, flags) {
185
189
  status: flags.status,
186
190
  theirStage: flags.theirStage,
187
191
  stage: flags.stage,
192
+ sourceDirection: flags.sourceDirection || flags["source-direction"],
188
193
  };
189
194
  const filtered = result.filter((d) => clientSideMatch(d, filters));
190
195
  const limit = Number(flags.limit) > 0 ? Number(flags.limit) : 200;
@@ -273,6 +278,10 @@ const HELP_FULL = `Llama Command CLI
273
278
 
274
279
  Agent onboarding (run once on first install):
275
280
  llama agent-onboard # print AGENT_BRIEFING.md — the workflow contract for AI agents
281
+ llama agent bootstrap # fetch live Command + Llama OS skill manifest
282
+ llama skills search "pipeline update" # discover relevant runtime skills
283
+ llama skills show llama-pipeline # read a skill from Command
284
+ llama explain <url-or-object> # explain Command URL/object status + lifecycle
276
285
 
277
286
  External pitch — talk to Llama Ventures' intake agent (no token required):
278
287
  llama pitch start --name "Jane Doe" --email "jane@acme.ai"
@@ -293,15 +302,16 @@ auto-detects \`gcloud auth print-identity-token\` and uses Bearer auth.
293
302
  Manually-set \`llc_\` tokens are used as a fallback.
294
303
 
295
304
  Deals:
296
- llama deal create "Company" --source <name> --description "..." --website https://...
305
+ llama deal create "Company" --source <name> --source-direction Inbound|Outbound --description "..." --status Outreached|Sourced --website https://...
297
306
  llama deal show <dealId>
298
307
  llama deal feed <dealId> # every contribution (facts + notes), human-typed or assistant-drafted, newest first
299
308
  llama deal update <dealId> <field> <value>
300
- Writable fields: status, theirStage, stage, notes, dealOwner, source,
309
+ Writable fields: status, theirStage, stage, notes, dealOwner, source, sourceDirection,
301
310
  description, website, location, founders, founderInfo, proposedAmount,
302
311
  roundSize, valuation, deckLink, folderUrl, sector, subsector,
303
312
  foundedYear, leadInvestor, investors, agentActive.
304
313
  e.g. llama deal update <dealId> website https://acme.ai
314
+ llama deal update <dealId> status Outreached
305
315
  llama deal update <dealId> sector "Developer Tools"
306
316
  llama deal update <dealId> foundedYear 2024
307
317
  llama deal update <dealId> leadInvestor "Acme Capital"
@@ -316,7 +326,7 @@ Deals:
316
326
  string. Audited to deal_events as field_change "extra.<key>".
317
327
  llama deal extra unset <dealId> <key> # delete the key (admin)
318
328
  llama deal search <query> [--founder name] [--owner <user-key>] [--status Diligence]
319
- [--theirStage Raising] [--stage Seed]
329
+ [--theirStage Raising] [--stage Seed] [--source-direction Inbound]
320
330
  [--limit 200] [--offset 0]
321
331
  llama deal list [--owner ...] [--status ...] [...same flags as search]
322
332
 
@@ -512,6 +522,9 @@ Common:
512
522
  llama deal feed <dealId> every contribution (facts + notes), newest first
513
523
  llama post <dealId> "..." add a note to a deal
514
524
  llama agent-onboard print the AI-agent workflow contract
525
+ llama agent bootstrap live Llama OS skill manifest from Command
526
+ llama skills search "<query>" discover which skill to read
527
+ llama explain <url-or-object> explain Command URLs, 404s, deleted objects
515
528
 
516
529
  Command groups — run \`llama help <group>\` for that group's commands:
517
530
  deal create · show · feed · update · enrich · search · collaborators · links · delete
@@ -524,6 +537,8 @@ Command groups — run \`llama help <group>\` for that group's commands:
524
537
  pitch external founder intake (no token needed)
525
538
  ownership claim · nominate · approvals
526
539
  admin audit events (system admin only)
540
+ agent bootstrap · skills · explain for AI agents
541
+ skills search · show runtime Llama OS skills
527
542
  auth setup · tokens · auth status
528
543
 
529
544
  llama help all the full command reference (everything at once)
@@ -543,6 +558,8 @@ const HELP_AREA_MATCH = {
543
558
  pitch: [/^External pitch/],
544
559
  ownership: [/^Ownership/, /^Approvals/],
545
560
  admin: [/^Admin/],
561
+ agent: [/^Agent onboarding/],
562
+ skills: [/^Agent onboarding/],
546
563
  auth: [/^Setup/, /^Zero-config/, /^Token discovery/, /^Env/],
547
564
  };
548
565
 
@@ -931,6 +948,95 @@ https://command.llamaventures.vc/settings/tokens, run
931
948
  return;
932
949
  }
933
950
 
951
+ // Live runtime bootstrap from Llama Command. Unlike agent-onboard, this is
952
+ // not bundled in the public npm package; Command returns the current
953
+ // authenticated skill manifest and object-inspection contract.
954
+ if (area === "agent" && action === "bootstrap") {
955
+ const { flags } = parseFlags(rest, ["json", "limit"]);
956
+ const params = new URLSearchParams();
957
+ if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
958
+ const manifest = await request("GET", `/api/agent/manifest${params.toString() ? `?${params}` : ""}`);
959
+ if (flags.json) {
960
+ print(manifest);
961
+ } else {
962
+ process.stdout.write(`${manifest.briefing || JSON.stringify(manifest, null, 2)}\n`);
963
+ }
964
+ return;
965
+ }
966
+
967
+ if (area === "skills" || (area === "agent" && action === "skills")) {
968
+ const sub = area === "skills" ? action : rest[0];
969
+ const args = area === "skills" ? rest : rest.slice(1);
970
+ if (!sub || sub === "list") {
971
+ const { flags } = parseFlags(args, ["json", "limit"]);
972
+ const params = new URLSearchParams();
973
+ if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
974
+ const result = await request("GET", `/api/agent/skills${params.toString() ? `?${params}` : ""}`);
975
+ print(result);
976
+ return;
977
+ }
978
+ if (sub === "search") {
979
+ const { flags, positional } = parseFlags(args, ["json", "limit"]);
980
+ const q = positional.join(" ").trim();
981
+ if (!q) throw new Error("Usage: llama skills search <query> [--limit 20]");
982
+ const params = new URLSearchParams({ q });
983
+ if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
984
+ const result = await request("GET", `/api/agent/skills?${params}`);
985
+ print(result);
986
+ return;
987
+ }
988
+ if (sub === "show" || sub === "read") {
989
+ const { flags, positional } = parseFlags(args, ["json"]);
990
+ const slug = positional[0];
991
+ if (!slug) throw new Error("Usage: llama skills show <slug> [--json]");
992
+ const result = await request("GET", `/api/agent/skills/${encodeURIComponent(slug)}`);
993
+ if (flags.json) {
994
+ print(result);
995
+ } else {
996
+ process.stdout.write(`${result.skill?.content || JSON.stringify(result, null, 2)}\n`);
997
+ }
998
+ return;
999
+ }
1000
+ throw new Error(`Unknown skills subcommand "${sub}". Use: list / search / show.`);
1001
+ }
1002
+
1003
+ if (area === "explain" || (area === "agent" && action === "explain")) {
1004
+ const args = area === "explain" ? [action, ...rest].filter(Boolean) : rest;
1005
+ const { flags, positional } = parseFlags(args, ["json", "type", "id", "lang"]);
1006
+ const params = new URLSearchParams();
1007
+ const q = positional.join(" ").trim();
1008
+ if (q) params.set("q", q);
1009
+ if (flags.type && flags.type !== true) params.set("type", String(flags.type));
1010
+ if (flags.id && flags.id !== true) params.set("id", String(flags.id));
1011
+ if (flags.lang === "zh") params.set("lang", "zh");
1012
+ if (!params.has("q") && !(params.has("type") && params.has("id"))) {
1013
+ throw new Error("Usage: llama explain <url-or-object> OR llama explain --type <type> --id <id>");
1014
+ }
1015
+ const result = await request("GET", `/api/agent/explain?${params}`);
1016
+ if (flags.json) {
1017
+ print(result);
1018
+ } else {
1019
+ const target = result.result?.target;
1020
+ const lifecycle = result.result?.lifecycle || [];
1021
+ const lines = [
1022
+ `${target?.objectType || "object"} ${target?.objectId || ""}`,
1023
+ `Status: ${target?.status || "unknown"}`,
1024
+ `Title: ${target?.title || "Untitled"}`,
1025
+ target?.detail ? `Detail: ${target.detail}` : null,
1026
+ target?.url ? `URL: ${target.url}` : null,
1027
+ `Lifecycle events: ${lifecycle.length}`,
1028
+ ].filter(Boolean);
1029
+ if (lifecycle[0]) {
1030
+ lines.push(
1031
+ `Latest lifecycle: ${lifecycle[0].action} by ${lifecycle[0].actor_label || "unknown"} at ${lifecycle[0].created_at}`,
1032
+ );
1033
+ if (lifecycle[0].reason) lines.push(`Reason: ${lifecycle[0].reason}`);
1034
+ }
1035
+ process.stdout.write(`${lines.join("\n")}\n`);
1036
+ }
1037
+ return;
1038
+ }
1039
+
934
1040
  // `llama pitch ...` — external founder-pitch family. No Llama token
935
1041
  // required; bootstraps a session against /api/external/* via PoW + cookie.
936
1042
  // See lib/external.mjs and AGENT_BRIEFING.md for the full surface.
@@ -1143,6 +1249,7 @@ https://command.llamaventures.vc/settings/tokens, run
1143
1249
  const body = {
1144
1250
  companyName,
1145
1251
  source: flags.source,
1252
+ sourceDirection: flags.sourceDirection || flags["source-direction"],
1146
1253
  description: flags.description,
1147
1254
  website: flags.website,
1148
1255
  notes: flags.notes,
@@ -1219,7 +1326,7 @@ https://command.llamaventures.vc/settings/tokens, run
1219
1326
  const q = positional.join(" ").trim();
1220
1327
  if (!q && Object.keys(flags).length === 0) {
1221
1328
  throw new Error(
1222
- `Usage: llama deal search <query> [--founder ...] [--owner ...] [--status ...] [--stage ...] [--limit N]`
1329
+ `Usage: llama deal search <query> [--founder ...] [--owner ...] [--status ...] [--stage ...] [--source-direction Inbound|Outbound] [--limit N]`
1223
1330
  );
1224
1331
  }
1225
1332
  print(await searchDeals(q, flags));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "1.14.1",
3
+ "version": "1.15.1",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -3,12 +3,23 @@
3
3
  import assert from "node:assert/strict";
4
4
  import { spawn } from "node:child_process";
5
5
  import { createServer } from "node:http";
6
+ import { existsSync } from "node:fs";
6
7
  import { mkdtemp, rm } from "node:fs/promises";
7
8
  import os from "node:os";
8
9
  import path from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
11
 
11
12
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+ assert.equal(
14
+ existsSync(path.join(repoRoot, "docs/agent-skills.bundle.json")),
15
+ false,
16
+ "public llama-cli must not bundle private Llama OS skill content",
17
+ );
18
+ assert.equal(
19
+ existsSync(path.join(repoRoot, "src/data/llama-os-skills.bundle.json")),
20
+ false,
21
+ "public llama-cli must not copy the Command-side skill mirror",
22
+ );
12
23
  const calls = [];
13
24
  let threadSeq = 0;
14
25
 
@@ -49,7 +60,81 @@ const server = createServer(async (req, res) => {
49
60
  try {
50
61
  const body = await readJson(req);
51
62
  const url = new URL(req.url, "http://localhost");
52
- calls.push({ method: req.method, path: url.pathname, body });
63
+ calls.push({
64
+ method: req.method,
65
+ path: url.pathname,
66
+ query: Object.fromEntries(url.searchParams.entries()),
67
+ body,
68
+ });
69
+
70
+ if (req.method === "GET" && url.pathname === "/api/agent/manifest") {
71
+ writeJson(res, {
72
+ ok: true,
73
+ briefing: "runtime briefing: use skills_search, skills_read, and object_inspect",
74
+ llama_os: {
75
+ visible_skill_count: 49,
76
+ included_skill_count: Number(url.searchParams.get("limit") || 25),
77
+ },
78
+ skills: [
79
+ {
80
+ slug: "llama-command",
81
+ description: "Llama Command runtime skill",
82
+ },
83
+ ],
84
+ });
85
+ return;
86
+ }
87
+
88
+ if (req.method === "GET" && url.pathname === "/api/agent/skills") {
89
+ writeJson(res, {
90
+ ok: true,
91
+ q: url.searchParams.get("q"),
92
+ count: 1,
93
+ skills: [
94
+ {
95
+ slug: "llama-command",
96
+ description: "Llama Command runtime skill",
97
+ },
98
+ ],
99
+ });
100
+ return;
101
+ }
102
+
103
+ if (req.method === "GET" && url.pathname === "/api/agent/skills/llama-command") {
104
+ writeJson(res, {
105
+ ok: true,
106
+ skill: {
107
+ slug: "llama-command",
108
+ content: "---\nname: llama-command\n---\n# Llama Command runtime skill\n",
109
+ },
110
+ });
111
+ return;
112
+ }
113
+
114
+ if (req.method === "GET" && url.pathname === "/api/agent/explain") {
115
+ writeJson(res, {
116
+ ok: true,
117
+ result: {
118
+ target: {
119
+ objectType: "wiki_article",
120
+ objectId: "missing-page",
121
+ status: "deleted",
122
+ title: "Missing Page",
123
+ detail: "Deleted by Kevin Yu",
124
+ url: "https://command.llamaventures.vc/wiki/missing-page",
125
+ },
126
+ lifecycle: [
127
+ {
128
+ action: "deleted",
129
+ actor_label: "Kevin Yu",
130
+ created_at: "2026-06-15T19:02:00Z",
131
+ reason: "user_deleted",
132
+ },
133
+ ],
134
+ },
135
+ });
136
+ return;
137
+ }
53
138
 
54
139
  if (req.method === "POST" && /^\/api\/deals\/[^/]+\/threads$/.test(url.pathname)) {
55
140
  threadSeq += 1;
@@ -228,6 +313,31 @@ const baseUrl = `http://${address.address}:${address.port}`;
228
313
  const homeDir = await mkdtemp(path.join(os.tmpdir(), "llama-cli-routing-"));
229
314
 
230
315
  try {
316
+ resetCalls();
317
+ const bootstrapRun = await runCli(["agent", "bootstrap", "--limit", "3"], baseUrl, homeDir);
318
+ assert.match(bootstrapRun.stdout, /runtime briefing/);
319
+ assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
320
+ assert.equal(calls[0].query.limit, "3");
321
+
322
+ resetCalls();
323
+ const skillSearchRun = await runCli(["skills", "search", "pipeline", "--limit", "5"], baseUrl, homeDir);
324
+ assert.match(skillSearchRun.stdout, /llama-command/);
325
+ assert.deepEqual(paths(), ["GET /api/agent/skills"]);
326
+ assert.equal(calls[0].query.q, "pipeline");
327
+ assert.equal(calls[0].query.limit, "5");
328
+
329
+ resetCalls();
330
+ const skillShowRun = await runCli(["skills", "show", "llama-command"], baseUrl, homeDir);
331
+ assert.match(skillShowRun.stdout, /# Llama Command runtime skill/);
332
+ assert.deepEqual(paths(), ["GET /api/agent/skills/llama-command"]);
333
+
334
+ resetCalls();
335
+ const explainRun = await runCli(["explain", "https://command.llamaventures.vc/wiki/missing-page"], baseUrl, homeDir);
336
+ assert.match(explainRun.stdout, /Status: deleted/);
337
+ assert.match(explainRun.stdout, /Deleted by Kevin Yu/);
338
+ assert.deepEqual(paths(), ["GET /api/agent/explain"]);
339
+ assert.equal(calls[0].query.q, "https://command.llamaventures.vc/wiki/missing-page");
340
+
231
341
  resetCalls();
232
342
  const enrichRun = await runCli(
233
343
  [
@@ -299,6 +409,37 @@ try {
299
409
  assert.equal(payload.threadId, "thread-1");
300
410
  assert.equal(payload.text, "agent done");
301
411
 
412
+ resetCalls();
413
+ const mcpBootstrap = await callMcpTool("agent_bootstrap", { limit: 2 }, baseUrl, homeDir);
414
+ const bootstrapPayload = JSON.parse(mcpBootstrap.content?.[0]?.text ?? "{}");
415
+ assert.equal(bootstrapPayload.ok, true);
416
+ assert.deepEqual(paths(), ["GET /api/agent/manifest"]);
417
+ assert.equal(calls[0].query.limit, "2");
418
+
419
+ resetCalls();
420
+ const mcpSkills = await callMcpTool("skills_search", { q: "command", limit: 4 }, baseUrl, homeDir);
421
+ const skillsPayload = JSON.parse(mcpSkills.content?.[0]?.text ?? "{}");
422
+ assert.equal(skillsPayload.skills?.[0]?.slug, "llama-command");
423
+ assert.deepEqual(paths(), ["GET /api/agent/skills"]);
424
+ assert.equal(calls[0].query.q, "command");
425
+
426
+ resetCalls();
427
+ const mcpSkillRead = await callMcpTool("skills_read", { slug: "llama-command" }, baseUrl, homeDir);
428
+ const skillPayload = JSON.parse(mcpSkillRead.content?.[0]?.text ?? "{}");
429
+ assert.match(skillPayload.skill?.content ?? "", /# Llama Command runtime skill/);
430
+ assert.deepEqual(paths(), ["GET /api/agent/skills/llama-command"]);
431
+
432
+ resetCalls();
433
+ const mcpInspect = await callMcpTool(
434
+ "object_inspect",
435
+ { q: "https://command.llamaventures.vc/wiki/missing-page" },
436
+ baseUrl,
437
+ homeDir,
438
+ );
439
+ const inspectPayload = JSON.parse(mcpInspect.content?.[0]?.text ?? "{}");
440
+ assert.equal(inspectPayload.result?.target?.status, "deleted");
441
+ assert.deepEqual(paths(), ["GET /api/agent/explain"]);
442
+
302
443
  console.log("agent routing verification passed");
303
444
  } finally {
304
445
  await close(server);