@llamaventures/cli 1.13.0 → 1.14.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/CHANGELOG.md +385 -0
- package/CONTRIBUTING.md +100 -0
- package/README.md +21 -20
- package/README.zh-CN.md +19 -19
- package/SECURITY.md +62 -0
- package/assets/llama-ventures-logo.svg +25 -0
- package/bin/llama-mcp.mjs +173 -1
- package/bin/llama.mjs +149 -1
- package/lib/client.mjs +78 -0
- package/package.json +11 -1
- package/scripts/verify-agent-routing.mjs +306 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting security issues
|
|
4
|
+
|
|
5
|
+
**Do not file public GitHub issues for security bugs.** A public issue
|
|
6
|
+
gives an attacker a head-start before we can patch.
|
|
7
|
+
|
|
8
|
+
Instead, email [**gavin@llamaventures.vc**](mailto:gavin@llamaventures.vc)
|
|
9
|
+
with:
|
|
10
|
+
|
|
11
|
+
- A short description of the issue
|
|
12
|
+
- Steps to reproduce
|
|
13
|
+
- Expected vs. actual behavior
|
|
14
|
+
- The CLI version (`npm ls -g @llamaventures/cli`)
|
|
15
|
+
- Any other context that helps us reproduce
|
|
16
|
+
|
|
17
|
+
We aim to acknowledge within ~5 push cycles (best effort), patch
|
|
18
|
+
promptly, and credit the reporter (if desired) once the fix ships.
|
|
19
|
+
We do not run a public bug bounty program.
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
|
|
23
|
+
In scope:
|
|
24
|
+
|
|
25
|
+
- Authentication / authorization bypass in the CLI client
|
|
26
|
+
- Token handling vulnerabilities (logging, transmission, storage)
|
|
27
|
+
- Supply-chain attacks against the npm package or its dependencies
|
|
28
|
+
- Anything that lets an unauthorized party execute Llama Command
|
|
29
|
+
operations as another team member
|
|
30
|
+
|
|
31
|
+
Out of scope (still report; lower urgency):
|
|
32
|
+
|
|
33
|
+
- Bugs in third-party services we depend on (Google Cloud, npm,
|
|
34
|
+
Anthropic, etc.)
|
|
35
|
+
- Issues only reproducible against a fork or a modified version of
|
|
36
|
+
this CLI
|
|
37
|
+
|
|
38
|
+
Server-side bugs (`command.llamaventures.vc`) are governed separately;
|
|
39
|
+
report through the same channel.
|
|
40
|
+
|
|
41
|
+
## Supply-chain posture
|
|
42
|
+
|
|
43
|
+
This package follows current best practice:
|
|
44
|
+
|
|
45
|
+
- **`@llamaventures/cli` is published via npm
|
|
46
|
+
[Trusted Publishers](https://docs.npmjs.com/trusted-publishers)** —
|
|
47
|
+
no `NPM_TOKEN` is stored in CI secrets; the GitHub Action exchanges
|
|
48
|
+
an OIDC token at publish time.
|
|
49
|
+
- **All releases ship with `--provenance`** (sigstore-signed). The
|
|
50
|
+
npm registry shows a "Provenance" badge on each version, traceable
|
|
51
|
+
to the exact commit + GitHub Action workflow that built it.
|
|
52
|
+
- **Minimal dependency tree.** The CLI itself is zero-deps; the
|
|
53
|
+
bundled MCP server depends only on
|
|
54
|
+
`@modelcontextprotocol/sdk` (Anthropic-maintained, pinned exact).
|
|
55
|
+
- **Branch protection** on `main` prevents force-push and deletion;
|
|
56
|
+
Dependabot, secret scanning, and push-protection are enabled.
|
|
57
|
+
|
|
58
|
+
## Disclosure
|
|
59
|
+
|
|
60
|
+
We coordinate disclosure when a fix lands. Once a patched version
|
|
61
|
+
ships, we publish a security advisory on this repo and notify
|
|
62
|
+
affected users via the team's internal channels.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 224 76.32">
|
|
3
|
+
<g>
|
|
4
|
+
<g>
|
|
5
|
+
<path d="M69.84,26.89h7.64v4.53h-13.48V7.33h5.84v19.56Z"/>
|
|
6
|
+
<path d="M86.13,7.33v24.04h-5.7V7.33h5.7Z"/>
|
|
7
|
+
<path d="M90.96,16.53c.74-1.5,1.76-2.65,3.04-3.45,1.28-.81,2.72-1.21,4.3-1.21,1.35,0,2.54.28,3.56.83s1.81,1.28,2.36,2.17v-2.73h5.88v19.26h-5.88v-2.73c-.57.9-1.37,1.62-2.39,2.17-1.02.55-2.21.83-3.56.83-1.56,0-2.98-.41-4.27-1.22-1.29-.82-2.3-1.98-3.04-3.49-.75-1.51-1.12-3.25-1.12-5.23s.37-3.72,1.12-5.21ZM103,18.29c-.81-.85-1.81-1.28-2.97-1.28s-2.16.42-2.98,1.26c-.81.84-1.22,2-1.22,3.47s.41,2.64,1.22,3.51c.81.86,1.81,1.29,2.98,1.29s2.16-.42,2.97-1.28c.81-.85,1.22-2.01,1.22-3.49s-.41-2.64-1.22-3.49Z"/>
|
|
8
|
+
<path d="M143.78,14.24c1.4,1.44,2.1,3.44,2.1,6v11.18h-5.82v-10.39c0-1.24-.32-2.19-.97-2.86-.65-.68-1.54-1.01-2.68-1.01s-2.04.34-2.68,1.01c-.65.67-.97,1.63-.97,2.86v10.39h-5.82v-10.39c0-1.24-.32-2.19-.97-2.86-.65-.68-1.54-1.01-2.68-1.01s-2.03.34-2.68,1.01c-.65.67-.98,1.63-.98,2.86v10.39h-5.85V12.28h5.85v2.4c.59-.8,1.37-1.43,2.33-1.9.96-.47,2.04-.7,3.25-.7,1.44,0,2.72.31,3.85.92,1.13.62,2.01,1.5,2.65,2.64.66-1.05,1.56-1.91,2.7-2.57,1.14-.66,2.38-.99,3.72-.99,2.37,0,4.26.72,5.66,2.16Z"/>
|
|
9
|
+
<path d="M149.87,16.6c.74-1.49,1.75-2.63,3.03-3.44s2.71-1.2,4.28-1.2c1.35,0,2.53.27,3.55.82,1.01.55,1.8,1.27,2.34,2.16v-2.72h5.86v19.17h-5.86v-2.72c-.57.89-1.36,1.62-2.38,2.16-1.01.55-2.2.82-3.54.82-1.56,0-2.97-.41-4.25-1.22s-2.29-1.97-3.03-3.47c-.74-1.5-1.11-3.24-1.11-5.21s.37-3.7,1.11-5.19ZM161.86,18.36c-.81-.85-1.8-1.27-2.96-1.27s-2.15.42-2.96,1.25c-.81.84-1.22,1.99-1.22,3.45s.41,2.63,1.22,3.49c.81.86,1.8,1.29,2.96,1.29s2.15-.42,2.96-1.27,1.22-2,1.22-3.47-.4-2.62-1.22-3.47Z"/>
|
|
10
|
+
</g>
|
|
11
|
+
<g>
|
|
12
|
+
<path d="M88.31,42.57l-8.62,24.05-7.19-.14-8.5-23.91h6.3l5.57,17.41,6.27-17.41h6.17Z"/>
|
|
13
|
+
<path d="M106.78,57.98l-13.12.07c.09,1.16.46,2.04,1.12,2.65.66.61,1.46.92,2.42.92,1.42,0,2.41-.6,2.97-1.8h6.17c-.31,1.21-.86,2.45-1.67,3.42-.81.98-1.87,1.77-3.09,2.33s-2.73.9-4.24.9c-1.82,0-3.79-.59-5.21-1.37-1.42-.78-2.42-2.01-3.22-3.45-.8-1.44-1.2-3.11-1.2-5.04s.58-4,1.37-5.45c.79-1.45,2.09-2.48,3.51-3.26,1.42-.78,3.21-1.15,5.06-1.15s3.43.55,4.83,1.31c1.4.75,2.56,2.1,3.35,3.5s1.07,2.89,1.07,4.76c0,.53-.03,1.09-.1,1.67ZM100.83,54.89c0-.98-.33-1.76-1-2.33-.67-.58-1.5-.87-2.5-.87s-1.76.28-2.42.84c-.66.56-1.06,1.34-1.22,2.37h7.13Z"/>
|
|
14
|
+
<path d="M126.7,49.22c1.3,1.41,2.3,3.49,2.3,5.96v11.44h-5.78s-.04-10.69-.04-10.69c0-1.25-.49-2.07-1.13-2.76-.65-.69-1.54-1.06-2.81-1-1.09.05-1.89.57-2.54,1.26s-.91,1.61-.91,2.86v10.32h-5.79v-19.13h5.79v2.47c.58-.82,1.17-1.47,2.15-1.95.98-.48,2.08-.72,3.3-.72,2.18,0,4.17.53,5.47,1.94Z"/>
|
|
15
|
+
<path d="M143.61,61.6v5.02h-2.9c-2.06,0-4.1-.8-5.25-1.81-1.16-1.01-1.43-3.02-1.43-5.31v-7.1s-2.39,0-2.39,0v-4.91h2.39v-4.66s5.79,0,5.79,0v4.66h3.79v4.91h-3.78v7.2c0,.56.15,1.41.41,1.65.27.24.71.37,1.33.37h2.03Z"/>
|
|
16
|
+
<path d="M165.77,47.34v19.01h-5.79l-.03-2.74c-.58.82-1.77,1.75-2.76,2.25-.99.5-2.08.75-3.28.75-1.42,0-2.68-.32-3.77-.95-1.09-.63-1.88-2.02-2.48-3.21-.6-1.2-.9-2.61-.9-4.24v-10.87h5.67v10.1c0,1.24.32,2.21.97,2.9.64.69,1.51,1.03,2.6,1.03s1.99-.34,2.63-1.03c.64-.69.97-1.66.97-2.9v-10.1h6.17Z"/>
|
|
17
|
+
<path d="M178.26,48.17c1-.59,2.41-.82,3.64-.82v6.17h-1.85c-1.42,0-2.38.33-3.09.94-.71.61-1.11,1.55-1.11,3.09v9.06h-5.79v-19.13h5.7v3.1c.67-1.02,1.5-1.83,2.5-2.42Z"/>
|
|
18
|
+
<path d="M202.59,58.03h-13.25c.09,1.19.47,2.09,1.15,2.71.67.63,1.5.94,2.48.94,1.46,0,2.47-.61,3.05-1.84h6.23c-.32,1.25-.9,2.38-1.73,3.38-.83,1-1.88,1.79-3.13,2.36-1.26.57-2.66.85-4.21.85-1.87,0-3.54-.4-5-1.19-1.46-.79-2.6-1.93-3.42-3.41-.82-1.48-1.23-3.21-1.23-5.19s.4-3.71,1.21-5.19c.81-1.48,1.95-2.62,3.41-3.41,1.46-.8,3.14-1.19,5.03-1.19s3.49.39,4.93,1.16c1.44.77,2.56,1.88,3.37,3.31s1.21,3.11,1.21,5.02c0,.55-.03,1.12-.1,1.71ZM196.7,54.79c0-1-.34-1.8-1.03-2.39-.68-.59-1.54-.89-2.57-.89s-1.81.29-2.48.86c-.67.57-1.09,1.37-1.25,2.42h7.33Z"/>
|
|
19
|
+
<path d="M106.77,58.03h-13.25c.09,1.19.47,2.09,1.15,2.71.67.63,1.5.94,2.48.94,1.46,0,2.47-.61,3.05-1.84h6.23c-.32,1.25-.9,2.38-1.73,3.38-.83,1-1.88,1.79-3.13,2.36-1.26.57-2.66.85-4.21.85-1.87,0-3.54-.4-5-1.19-1.46-.79-2.6-1.93-3.42-3.41-.82-1.48-1.23-3.21-1.23-5.19s.4-3.71,1.21-5.19c.81-1.48,1.95-2.62,3.41-3.41,1.46-.8,3.14-1.19,5.03-1.19s3.49.39,4.93,1.16c1.44.77,2.56,1.88,3.37,3.31s1.21,3.11,1.21,5.02c0,.55-.03,1.12-.1,1.71ZM100.88,54.79c0-1-.34-1.8-1.03-2.39-.68-.59-1.54-.89-2.57-.89s-1.81.29-2.48.86c-.67.57-1.09,1.37-1.25,2.42h7.33Z"/>
|
|
20
|
+
<path d="M209.1,65.57c-1.28-.57-2.29-1.35-3.03-2.35-.74-.99-1.15-2.11-1.24-3.34h5.68c.07.66.37,1.2.91,1.61.54.41,1.2.62,1.98.62.72,0,1.27-.14,1.66-.43.39-.28.59-.66.59-1.11,0-.55-.28-.95-.84-1.22-.56-.26-1.47-.56-2.72-.88-1.34-.32-2.47-.66-3.36-1.01-.9-.35-1.67-.92-2.32-1.68-.65-.76-.98-1.8-.98-3.1,0-1.1.3-2.1.89-3,.59-.9,1.47-1.62,2.62-2.14,1.15-.53,2.53-.79,4.12-.79,2.35,0,4.21.59,5.56,1.78,1.36,1.19,2.14,2.77,2.34,4.73h-5.31c-.09-.66-.37-1.19-.86-1.58-.48-.39-1.12-.58-1.9-.58-.67,0-1.19.13-1.55.39s-.54.62-.54,1.08c0,.55.29.96.86,1.23.57.28,1.46.55,2.67.82,1.39.37,2.52.73,3.4,1.08.87.35,1.64.93,2.3,1.72.66.79,1,1.84,1.02,3.17,0,1.12-.31,2.12-.92,3-.62.88-1.5,1.57-2.65,2.07-1.15.5-2.5.75-4.02.75-1.64,0-3.09-.28-4.37-.86Z"/>
|
|
21
|
+
</g>
|
|
22
|
+
</g>
|
|
23
|
+
<path d="M48.7,50.5c3.49.32,6.76,2.94,8.15,6.08,3.09,6.98-1.33,14.02-8.65,15.07-9.08-.47-18.69.74-27.71.12-6.65-.46-10.81-5.53-10.25-12.13l6.28-25.63c-2.05-.02-4.19-.55-6.11-1.26-2.04-.75-4.19-1.79-5.33-3.74-3.04-5.19,1.25-9.8,5.21-12.75-1.81-4.39,2.75-6.87,6.59-6.06.9-3.1,4.51-4.16,7.01-2.12.61.5,1.37,2.23,2.32,1.05.83-1.03,1.11-3.35,1.02-4.64-.06-.81-.79-2.28.57-2.34.91-.04,2.41,1.95,2.85,2.69,1.48,2.49,1.77,5.72.61,8.39,2.83-.4,5.16-1.93,6.73-4.29.48-.72.81-2.17,1.87-1.85.59.18.96,2.01,1.06,2.59.65,3.9-1.25,7.48-4.51,9.55.65,2.66,1.28,5.32,1.63,8.05.99,7.74.41,15.67-1.62,23.2h12.28v.02ZM26.67,18.33c-4.94-4.2-11.7-1.55-15.83,2.4-.27.26-1.14,1.11-1.26,1.39-.09.21.08.17.16.21.66.39,1.73.61,2.01,1.39.47,1.33-.45,2.61-1.85,2.49-.42-.04-1.91-.89-2.08-.76.18,1.93,1.71,2.95,3.37,3.62,3.11,1.24,6.67,1.29,9.98,1.09l.26.14-3.65,14.92c10.85-2.11,18.01-19.14,8.89-26.88h0Z"/>
|
|
24
|
+
<path d="M23.15,21.22c1.95,1.35-.13,4.72-2.34,3.28s-.04-4.95,2.34-3.28Z"/>
|
|
25
|
+
</svg>
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { createRequire } from "module";
|
|
|
11
11
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
import { getAuthHeaders, readBriefing, request } from "../lib/client.mjs";
|
|
14
|
+
import { getAuthHeaders, readBriefing, request, requestSse } from "../lib/client.mjs";
|
|
15
15
|
|
|
16
16
|
const requireFromHere = createRequire(import.meta.url);
|
|
17
17
|
const { version: PKG_VERSION } = requireFromHere("../package.json");
|
|
@@ -40,6 +40,96 @@ async function callApi(method, path, body) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function textResult(text, isError = false) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text }],
|
|
46
|
+
...(isError ? { isError: true } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function splitSources(value) {
|
|
51
|
+
if (Array.isArray(value)) return value.filter(Boolean);
|
|
52
|
+
if (!value || value === true) return undefined;
|
|
53
|
+
return String(value)
|
|
54
|
+
.split(",")
|
|
55
|
+
.map((s) => s.trim())
|
|
56
|
+
.filter(Boolean);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildEnrichmentAgentMessage(args = {}) {
|
|
60
|
+
if (args.message) return String(args.message);
|
|
61
|
+
const sources = splitSources(args.sources) ?? [
|
|
62
|
+
"website",
|
|
63
|
+
"github",
|
|
64
|
+
"linkedin",
|
|
65
|
+
"yc",
|
|
66
|
+
"launch",
|
|
67
|
+
"web",
|
|
68
|
+
"monid",
|
|
69
|
+
];
|
|
70
|
+
const budget = args.budgetCents ?? "50";
|
|
71
|
+
const memo = args.generateMemo
|
|
72
|
+
? "Generate memo only after enrichment because the caller explicitly requested it."
|
|
73
|
+
: "Do not generate memo.";
|
|
74
|
+
return [
|
|
75
|
+
"Run server-side deal enrichment for this deal.",
|
|
76
|
+
`Use sources: ${sources.join(", ")}.`,
|
|
77
|
+
`Private Monid budget cap: ${budget} cents.`,
|
|
78
|
+
"Read the enrichment harness first, then collect current company/founder evidence.",
|
|
79
|
+
"Write canonical evidence links, sourced deal facts, stable deal fields, and typed factual values where supported.",
|
|
80
|
+
"For typed factual values, call read_typed_factual_layer first and use upsert_typed_fact for queryable fields.",
|
|
81
|
+
"Search snippets alone are not high-confidence evidence; fetch direct sources where possible.",
|
|
82
|
+
memo,
|
|
83
|
+
"End with what was written, what was skipped, and open questions.",
|
|
84
|
+
].join(" ");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function summarizeAgentEvents(events = []) {
|
|
88
|
+
return events
|
|
89
|
+
.flatMap((event) => {
|
|
90
|
+
if (event.tool_use?.name) return [{ type: "tool_use", name: event.tool_use.name }];
|
|
91
|
+
if (event.tool_result?.name) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
type: "tool_result",
|
|
95
|
+
name: event.tool_result.name,
|
|
96
|
+
ok: event.tool_result.ok ?? null,
|
|
97
|
+
summary: event.tool_result.summary ?? null,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
if (event.error) return [{ type: "error", error: String(event.error) }];
|
|
102
|
+
return [];
|
|
103
|
+
})
|
|
104
|
+
.slice(-80);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function runDealAgentTool({ dealId, message, title = "MCP agent run" }) {
|
|
108
|
+
try {
|
|
109
|
+
const thread = await request("POST", `/api/deals/${encodeURIComponent(dealId)}/threads`, { title });
|
|
110
|
+
if (!thread?.id) throw new Error("Thread creation did not return an id");
|
|
111
|
+
const result = await requestSse(
|
|
112
|
+
"POST",
|
|
113
|
+
`/api/deals/${encodeURIComponent(dealId)}/threads/${encodeURIComponent(thread.id)}`,
|
|
114
|
+
{ message },
|
|
115
|
+
);
|
|
116
|
+
return textResult(
|
|
117
|
+
JSON.stringify(
|
|
118
|
+
{
|
|
119
|
+
ok: true,
|
|
120
|
+
threadId: thread.id,
|
|
121
|
+
text: result.text,
|
|
122
|
+
toolEvents: summarizeAgentEvents(result.events),
|
|
123
|
+
},
|
|
124
|
+
null,
|
|
125
|
+
2,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return textResult(`Error: ${err?.message ?? String(err)}`, true);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
43
133
|
// Append a block to a deal brief. The /blocks route only accepts atomic
|
|
44
134
|
// full-array PUTs (no POST), so we GET current blocks, prepend the new
|
|
45
135
|
// one (matches UI default since 2026-05-03), and PUT the merged array.
|
|
@@ -786,6 +876,88 @@ server.registerTool(
|
|
|
786
876
|
callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/refresh-persona`, { persona })
|
|
787
877
|
);
|
|
788
878
|
|
|
879
|
+
server.registerTool(
|
|
880
|
+
"deal_agent_run",
|
|
881
|
+
{
|
|
882
|
+
description:
|
|
883
|
+
"Run Llama Command's server-side Deal Agent inside a deal thread. " +
|
|
884
|
+
"Use this when the user explicitly wants the service agent to execute " +
|
|
885
|
+
"a deal-scoped task instead of the local MCP client doing the work.",
|
|
886
|
+
inputSchema: {
|
|
887
|
+
dealId: z.string().describe("deal uuid"),
|
|
888
|
+
message: z.string().describe("task instruction for the server-side Deal Agent"),
|
|
889
|
+
title: z.string().optional().describe("optional thread title; defaults to MCP agent run"),
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
async ({ dealId, message, title }) =>
|
|
893
|
+
runDealAgentTool({ dealId, message, title: title || "MCP agent run" })
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
server.registerTool(
|
|
897
|
+
"deal_enrich",
|
|
898
|
+
{
|
|
899
|
+
description:
|
|
900
|
+
"Run the Llama Command deal enrichment planner/trigger for one deal. " +
|
|
901
|
+
"Default is dry-run: returns evidence plan, source plan, Monid budget/config " +
|
|
902
|
+
"status, and planned writes without changing facts/links/memo. With " +
|
|
903
|
+
"apply=true and executor=server_agent, this starts the server-side Deal " +
|
|
904
|
+
"Agent unless harnessOnly=true. Set apply=true only when the user " +
|
|
905
|
+
"explicitly wants the enrichment run recorded/applied. " +
|
|
906
|
+
"generateMemo never defaults on; pass true only when the user explicitly asks " +
|
|
907
|
+
"for Memo generation after enrichment.",
|
|
908
|
+
inputSchema: {
|
|
909
|
+
dealId: z.string().describe("deal uuid"),
|
|
910
|
+
dryRun: z.boolean().optional().describe("default true unless apply=true"),
|
|
911
|
+
apply: z.boolean().optional().describe("record/apply the enrichment intent server-side"),
|
|
912
|
+
executor: z
|
|
913
|
+
.enum(["server_agent", "external_agent", "planner"])
|
|
914
|
+
.optional()
|
|
915
|
+
.describe("who will execute the harness; server_agent starts Deal Agent when apply=true"),
|
|
916
|
+
sources: z
|
|
917
|
+
.array(z.enum(["website", "github", "linkedin", "yc", "launch", "web", "monid"]))
|
|
918
|
+
.optional()
|
|
919
|
+
.describe("source families to use; defaults to the standard enrichment set"),
|
|
920
|
+
budgetCents: z
|
|
921
|
+
.number()
|
|
922
|
+
.int()
|
|
923
|
+
.min(0)
|
|
924
|
+
.max(500)
|
|
925
|
+
.optional()
|
|
926
|
+
.describe("Monid spend cap for this run, in cents; default is 50 when Monid is requested"),
|
|
927
|
+
generateMemo: z
|
|
928
|
+
.boolean()
|
|
929
|
+
.optional()
|
|
930
|
+
.describe("explicitly request memo regeneration after enrichment; default false"),
|
|
931
|
+
harnessOnly: z
|
|
932
|
+
.boolean()
|
|
933
|
+
.optional()
|
|
934
|
+
.describe("when true, return/apply the enrichment harness endpoint instead of starting Deal Agent"),
|
|
935
|
+
message: z
|
|
936
|
+
.string()
|
|
937
|
+
.optional()
|
|
938
|
+
.describe("optional override instruction for the server-side Deal Agent"),
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
async ({ dealId, dryRun, apply, executor, sources, budgetCents, generateMemo, harnessOnly, message }) => {
|
|
942
|
+
const effectiveExecutor = executor ?? "server_agent";
|
|
943
|
+
if (apply === true && effectiveExecutor === "server_agent" && harnessOnly !== true) {
|
|
944
|
+
return runDealAgentTool({
|
|
945
|
+
dealId,
|
|
946
|
+
title: "MCP enrichment",
|
|
947
|
+
message: buildEnrichmentAgentMessage({ sources, budgetCents, generateMemo, message }),
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
return callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/enrich`, {
|
|
951
|
+
dryRun,
|
|
952
|
+
apply,
|
|
953
|
+
executor: effectiveExecutor,
|
|
954
|
+
sources,
|
|
955
|
+
budgetCents,
|
|
956
|
+
generateMemo,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
);
|
|
960
|
+
|
|
789
961
|
// ============================================================
|
|
790
962
|
// External pitch (founder intake) — no Llama Command token required
|
|
791
963
|
// ============================================================
|
package/bin/llama.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
readCanonicalToken,
|
|
16
16
|
readLegacyConfig,
|
|
17
17
|
request,
|
|
18
|
+
requestSse,
|
|
18
19
|
tryGcloudIdentityToken,
|
|
19
20
|
writeCanonicalToken,
|
|
20
21
|
writeLegacyConfig,
|
|
@@ -199,6 +200,75 @@ async function searchDeals(q, flags) {
|
|
|
199
200
|
return result;
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
function splitCsvFlag(value) {
|
|
204
|
+
if (!value || value === true) return undefined;
|
|
205
|
+
return String(value)
|
|
206
|
+
.split(",")
|
|
207
|
+
.map((s) => s.trim())
|
|
208
|
+
.filter(Boolean);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function boolFlag(flags, ...names) {
|
|
212
|
+
return names.some((name) => flags[name] === true || flags[name] === "true" || flags[name] === "1");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildEnrichmentAgentMessage(flags) {
|
|
216
|
+
if (flags.message && flags.message !== true) return String(flags.message);
|
|
217
|
+
const sources = splitCsvFlag(flags.sources) ?? [
|
|
218
|
+
"website",
|
|
219
|
+
"github",
|
|
220
|
+
"linkedin",
|
|
221
|
+
"yc",
|
|
222
|
+
"launch",
|
|
223
|
+
"web",
|
|
224
|
+
"monid",
|
|
225
|
+
];
|
|
226
|
+
const budget = flags["budget-cents"] || flags.budgetCents || "50";
|
|
227
|
+
const memo = boolFlag(flags, "memo", "generate-memo", "generateMemo")
|
|
228
|
+
? "Generate memo only after enrichment because the caller explicitly requested it."
|
|
229
|
+
: "Do not generate memo.";
|
|
230
|
+
return [
|
|
231
|
+
"Run server-side deal enrichment for this deal.",
|
|
232
|
+
`Use sources: ${sources.join(", ")}.`,
|
|
233
|
+
`Private Monid budget cap: ${budget} cents.`,
|
|
234
|
+
"Read the enrichment harness first, then collect current company/founder evidence.",
|
|
235
|
+
"Write canonical evidence links, sourced deal facts, stable deal fields, and typed factual values where supported.",
|
|
236
|
+
"For typed factual values, call read_typed_factual_layer first and use upsert_typed_fact for queryable fields.",
|
|
237
|
+
"Search snippets alone are not high-confidence evidence; fetch direct sources where possible.",
|
|
238
|
+
memo,
|
|
239
|
+
"End with what was written, what was skipped, and open questions.",
|
|
240
|
+
].join(" ");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function runDealAgentViaThread(dealId, message, title = "CLI agent run") {
|
|
244
|
+
const thread = await request("POST", `/api/deals/${encodeURIComponent(dealId)}/threads`, { title });
|
|
245
|
+
if (!thread?.id) throw new Error("Thread creation did not return an id");
|
|
246
|
+
process.stderr.write(`Running Deal Agent in thread ${thread.id}\n`);
|
|
247
|
+
const result = await requestSse(
|
|
248
|
+
"POST",
|
|
249
|
+
`/api/deals/${encodeURIComponent(dealId)}/threads/${encodeURIComponent(thread.id)}`,
|
|
250
|
+
{ message },
|
|
251
|
+
{
|
|
252
|
+
onEvent(event) {
|
|
253
|
+
if (event.tool_use?.name) {
|
|
254
|
+
process.stderr.write(`[tool] ${event.tool_use.name}\n`);
|
|
255
|
+
}
|
|
256
|
+
if (event.tool_result?.name) {
|
|
257
|
+
const status = event.tool_result.ok ? "ok" : "error";
|
|
258
|
+
process.stderr.write(
|
|
259
|
+
`[tool] ${event.tool_result.name}: ${status} — ${event.tool_result.summary ?? ""}\n`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (event.text) {
|
|
263
|
+
process.stdout.write(event.text);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
if (result.text && !result.text.endsWith("\n")) process.stdout.write("\n");
|
|
269
|
+
return { thread, ...result };
|
|
270
|
+
}
|
|
271
|
+
|
|
202
272
|
const HELP_FULL = `Llama Command CLI
|
|
203
273
|
|
|
204
274
|
Agent onboarding (run once on first install):
|
|
@@ -235,6 +305,11 @@ Deals:
|
|
|
235
305
|
llama deal update <dealId> sector "Developer Tools"
|
|
236
306
|
llama deal update <dealId> foundedYear 2024
|
|
237
307
|
llama deal update <dealId> leadInvestor "Acme Capital"
|
|
308
|
+
llama deal enrich <dealId> [--dry-run] [--apply] [--executor server_agent|external_agent|planner]
|
|
309
|
+
[--sources website,github,linkedin,yc,monid] [--budget-cents 50]
|
|
310
|
+
[--memo] [--prompt] [--harness-only]
|
|
311
|
+
dry-run returns the harness; --apply --executor server_agent runs the server Deal Agent.
|
|
312
|
+
llama deal agent run <dealId> --message "collect founder evidence and update typed facts"
|
|
238
313
|
llama deal extra set <dealId> <key> <value> # system-admin only
|
|
239
314
|
Patch one top-level key in deals.extra JSONB. Value is parsed as
|
|
240
315
|
JSON when possible ('{"a":1}', 'true', '3'), else stored as a
|
|
@@ -439,7 +514,7 @@ Common:
|
|
|
439
514
|
llama agent-onboard print the AI-agent workflow contract
|
|
440
515
|
|
|
441
516
|
Command groups — run \`llama help <group>\` for that group's commands:
|
|
442
|
-
deal create · show · feed · update · search · collaborators · links · delete
|
|
517
|
+
deal create · show · feed · update · enrich · search · collaborators · links · delete
|
|
443
518
|
brief brief blocks: list · add · edit · history · refresh
|
|
444
519
|
facts deal facts + skill corrections (the sourced, trust-rated layer)
|
|
445
520
|
timeline timeline · posts · mentions
|
|
@@ -1157,6 +1232,79 @@ https://command.llamaventures.vc/settings/tokens, run
|
|
|
1157
1232
|
return;
|
|
1158
1233
|
}
|
|
1159
1234
|
|
|
1235
|
+
if (area === "deal" && action === "agent") {
|
|
1236
|
+
const sub = rest[0];
|
|
1237
|
+
const dealId = rest[1];
|
|
1238
|
+
const { flags, positional } = parseFlags(rest.slice(2), ["message"]);
|
|
1239
|
+
const message =
|
|
1240
|
+
flags.message && flags.message !== true ? String(flags.message) : positional.join(" ").trim();
|
|
1241
|
+
if (sub !== "run" || !dealId || !message) {
|
|
1242
|
+
throw new Error(`Usage: llama deal agent run <dealId> --message "what the server agent should do"`);
|
|
1243
|
+
}
|
|
1244
|
+
await runDealAgentViaThread(dealId, message, "CLI agent run");
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// ----- Deal enrichment: evidence plan + server-side enrichment trigger -----
|
|
1249
|
+
// The server owns Monid credentials and all write/audit behavior. CLI only
|
|
1250
|
+
// passes intent; default is dry-run so agents can inspect the harness before
|
|
1251
|
+
// creating facts/links or touching memo state.
|
|
1252
|
+
if (area === "deal" && action === "enrich") {
|
|
1253
|
+
const dealId = rest[0];
|
|
1254
|
+
if (!dealId) {
|
|
1255
|
+
throw new Error(
|
|
1256
|
+
"Usage: llama deal enrich <dealId> [--dry-run] [--apply] " +
|
|
1257
|
+
"[--executor server_agent|external_agent|planner] " +
|
|
1258
|
+
"[--sources website,github,linkedin,yc,monid] [--budget-cents 50] [--memo] [--prompt]"
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
const { flags } = parseFlags(rest.slice(1), [
|
|
1262
|
+
"dry-run",
|
|
1263
|
+
"apply",
|
|
1264
|
+
"executor",
|
|
1265
|
+
"sources",
|
|
1266
|
+
"budget-cents",
|
|
1267
|
+
"memo",
|
|
1268
|
+
"generate-memo",
|
|
1269
|
+
"generateMemo",
|
|
1270
|
+
"prompt",
|
|
1271
|
+
"handoff",
|
|
1272
|
+
"harness-only",
|
|
1273
|
+
"message",
|
|
1274
|
+
]);
|
|
1275
|
+
const sources = splitCsvFlag(flags.sources);
|
|
1276
|
+
const budgetCents =
|
|
1277
|
+
flags["budget-cents"] !== undefined && flags["budget-cents"] !== true
|
|
1278
|
+
? Number(flags["budget-cents"])
|
|
1279
|
+
: undefined;
|
|
1280
|
+
const apply = boolFlag(flags, "apply");
|
|
1281
|
+
const executor = flags.executor && flags.executor !== true ? String(flags.executor) : "server_agent";
|
|
1282
|
+
|
|
1283
|
+
if (apply && executor === "server_agent" && !boolFlag(flags, "harness-only")) {
|
|
1284
|
+
await runDealAgentViaThread(dealId, buildEnrichmentAgentMessage(flags), "CLI enrichment");
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const result = await request(
|
|
1289
|
+
"POST",
|
|
1290
|
+
`/api/deals/${encodeURIComponent(dealId)}/enrich`,
|
|
1291
|
+
{
|
|
1292
|
+
dryRun: apply ? false : true,
|
|
1293
|
+
apply,
|
|
1294
|
+
executor,
|
|
1295
|
+
sources,
|
|
1296
|
+
budgetCents,
|
|
1297
|
+
generateMemo: boolFlag(flags, "memo", "generate-memo", "generateMemo"),
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
if (flags.prompt === true || flags.handoff === true) {
|
|
1301
|
+
print(result?.agentHarness?.handoffPrompt || result?.agentHarness?.systemInjection || "");
|
|
1302
|
+
} else {
|
|
1303
|
+
print(result);
|
|
1304
|
+
}
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1160
1308
|
// ----- Collaborators (deal team — non-owner contributors) -----
|
|
1161
1309
|
// Accepts --user as numeric id OR @llamaventures.vc email; emails are
|
|
1162
1310
|
// resolved to id via /api/users so the CLI matches how the web picker
|
package/lib/client.mjs
CHANGED
|
@@ -220,6 +220,10 @@ export async function request(method, endpoint, body) {
|
|
|
220
220
|
return requestWithRetry(method, endpoint, body, /* allowRetry */ true);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
export async function requestSse(method, endpoint, body, opts = {}) {
|
|
224
|
+
return requestSseWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
|
|
225
|
+
}
|
|
226
|
+
|
|
223
227
|
async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
224
228
|
const authHeaders = await getAuthHeaders();
|
|
225
229
|
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
|
@@ -268,6 +272,80 @@ async function requestWithRetry(method, endpoint, body, allowRetry) {
|
|
|
268
272
|
return data;
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
276
|
+
const authHeaders = await getAuthHeaders();
|
|
277
|
+
if (Object.keys(authHeaders).length === 0) throw noAuthError();
|
|
278
|
+
const res = await fetch(`${getBaseUrl()}${endpoint}`, {
|
|
279
|
+
method,
|
|
280
|
+
headers: {
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
...authHeaders,
|
|
283
|
+
},
|
|
284
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (res.status === 401 && allowRetry && (await bearerCameFromOAuth())) {
|
|
288
|
+
let refreshed = null;
|
|
289
|
+
try {
|
|
290
|
+
const { forceRefresh } = await import("./oauth-refresh.mjs");
|
|
291
|
+
refreshed = await forceRefresh();
|
|
292
|
+
} catch {
|
|
293
|
+
refreshed = null;
|
|
294
|
+
}
|
|
295
|
+
if (refreshed) {
|
|
296
|
+
return requestSseWithRetry(method, endpoint, body, opts, /* allowRetry */ false);
|
|
297
|
+
}
|
|
298
|
+
throw unauthorizedError();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (res.status === 401) throw unauthorizedError();
|
|
302
|
+
|
|
303
|
+
if (!res.ok) {
|
|
304
|
+
const text = await res.text();
|
|
305
|
+
let data;
|
|
306
|
+
try {
|
|
307
|
+
data = text ? JSON.parse(text) : null;
|
|
308
|
+
} catch {
|
|
309
|
+
data = text;
|
|
310
|
+
}
|
|
311
|
+
const message = typeof data === "object" && data?.error ? data.error : `HTTP ${res.status}`;
|
|
312
|
+
throw new Error(message);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const reader = res.body?.getReader();
|
|
316
|
+
const decoder = new TextDecoder();
|
|
317
|
+
const events = [];
|
|
318
|
+
let text = "";
|
|
319
|
+
let buf = "";
|
|
320
|
+
if (!reader) return { text, events };
|
|
321
|
+
|
|
322
|
+
const handleFrame = (frame) => {
|
|
323
|
+
const dataLine = frame.split("\n").find((line) => line.startsWith("data: "));
|
|
324
|
+
if (!dataLine) return;
|
|
325
|
+
let event;
|
|
326
|
+
try {
|
|
327
|
+
event = JSON.parse(dataLine.slice(6));
|
|
328
|
+
} catch {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
events.push(event);
|
|
332
|
+
opts.onEvent?.(event);
|
|
333
|
+
if (event.text) text += event.text;
|
|
334
|
+
if (event.error) throw new Error(String(event.error));
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
while (true) {
|
|
338
|
+
const { done, value } = await reader.read();
|
|
339
|
+
if (done) break;
|
|
340
|
+
buf += decoder.decode(value, { stream: true });
|
|
341
|
+
const frames = buf.split("\n\n");
|
|
342
|
+
buf = frames.pop() || "";
|
|
343
|
+
for (const frame of frames) handleFrame(frame);
|
|
344
|
+
}
|
|
345
|
+
if (buf.trim()) handleFrame(buf);
|
|
346
|
+
return { text, events };
|
|
347
|
+
}
|
|
348
|
+
|
|
271
349
|
export function print(data) {
|
|
272
350
|
if (typeof data === "string") {
|
|
273
351
|
console.log(data);
|
package/package.json
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npm run test:agent-routing",
|
|
8
|
+
"test:agent-routing": "node scripts/verify-agent-routing.mjs"
|
|
9
|
+
},
|
|
6
10
|
"bin": {
|
|
7
11
|
"llama": "bin/llama.mjs",
|
|
8
12
|
"llama-mcp": "bin/llama-mcp.mjs"
|
|
9
13
|
},
|
|
10
14
|
"files": [
|
|
15
|
+
"assets/",
|
|
11
16
|
"bin/",
|
|
12
17
|
"lib/",
|
|
18
|
+
"scripts/verify-agent-routing.mjs",
|
|
13
19
|
"AGENT_BRIEFING.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"CONTRIBUTING.md",
|
|
14
22
|
"README.md",
|
|
23
|
+
"README.zh-CN.md",
|
|
24
|
+
"SECURITY.md",
|
|
15
25
|
"LICENSE"
|
|
16
26
|
],
|
|
17
27
|
"engines": {
|