@llamaventures/cli 2.6.0 → 2.8.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 +13 -0
- package/CHANGELOG.md +45 -0
- package/README.md +30 -0
- package/README.zh-CN.md +28 -0
- package/bin/llama-mcp.mjs +4 -358
- package/bin/llama.mjs +53 -7
- package/contracts/core-api.json +2 -2
- package/lib/build-manifest.json +4 -4
- package/lib/client.mjs +1 -7
- package/lib/deal-actions.mjs +76 -2
- package/lib/mcp-api-tools.mjs +414 -0
- package/package.json +2 -2
package/AGENT_BRIEFING.md
CHANGED
|
@@ -93,6 +93,8 @@ Before researching or creating a company:
|
|
|
93
93
|
1. `llama deal search "<company, founder or topic>"`. Core API 5.41.0+ also searches descriptions and sector/subsector in either stored language. Use `--sector "robotics"` alone or combine it with a query and `--state`/`--limit`; filters apply before the limit. These are literal substrings, not semantic synonyms. A sector filter requires server confirmation; never treat an older unfiltered response as sector-filtered results.
|
|
94
94
|
Use short keywords. Inspect `searchMatch` fields and `search.has_more`; truncated results are not exhaustive. On zero matches, broaden the sector or wording. Current Page updates are immediately searchable without manual index or tag maintenance.
|
|
95
95
|
2. Read the best candidate with `--detail overview` and verify its specific category; a broad sector match does not establish a narrower classification.
|
|
96
|
+
For "which Deals are invested / in diligence / latest investments", never guess from names: run `llama deal search --status Invested --limit 200` (Core API 5.42.0+) and order by `investmentDate`/`markedInvestedAt`, stating how many Deals you checked.
|
|
97
|
+
To answer from Information, prefer a targeted read over `--detail memory` (Core API 5.42.0+): `--information-q "<text>"` for snippets, `--information-type <type>` for one kind of record, and `--information-id <id>` for full values. Values over 8,000 characters in text/type reads are omitted; read that id. Never treat an unconfirmed (older server) response as filtered.
|
|
96
98
|
3. Expand only the resource needed for the task.
|
|
97
99
|
4. Create only when no existing company matches.
|
|
98
100
|
|
|
@@ -252,6 +254,17 @@ request, and verify the exact Artifact version and hash through Command.
|
|
|
252
254
|
separate domains. Deal Memory is also separate. None expands the four-action
|
|
253
255
|
Occam Deal space.
|
|
254
256
|
|
|
257
|
+
## Wiki publishing
|
|
258
|
+
|
|
259
|
+
Read `contract.wiki_publishing` from authenticated bootstrap and use
|
|
260
|
+
`llama help wiki` for the save commands. New authorized publications default
|
|
261
|
+
to complete self-contained HTML. Preserve full report content, charts,
|
|
262
|
+
interactions and existing language/version markers, with rendering assets
|
|
263
|
+
embedded. Read the exact slug and language before edits and preserve its
|
|
264
|
+
format. Honor explicit Markdown requests. For MCP, set `content_type: html`
|
|
265
|
+
and the intended `lang`. Verify the save receipt and canonical page before
|
|
266
|
+
reporting completion.
|
|
267
|
+
|
|
255
268
|
## Report UX friction
|
|
256
269
|
|
|
257
270
|
When a user, agent or both encounter a concrete obstacle (including a task that
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,51 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.8.0] - 2026-11-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Export the API-backed MCP tool catalog from `lib/mcp-api-tools.mjs`.
|
|
14
|
+
`registerApiTools(server, { request, formatError, version, only })` registers
|
|
15
|
+
the Deal, Deal Memory, bootstrap, Page schema, skills, preference and Wiki
|
|
16
|
+
tools against an injected request function, so a remote MCP host can serve
|
|
17
|
+
the same catalog as the stdio server. The module also exports
|
|
18
|
+
`writeDealCommandSchema`. Tools that need local credentials or files stay in
|
|
19
|
+
`llama-mcp`. No tool name, schema or behavior changed for stdio users.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `write_deal` and `wiki_save` descriptions each name the other as the
|
|
24
|
+
alternative destination: a file or HTML about one company is a Deal
|
|
25
|
+
Artifact; knowledge not tied to one company belongs in the Wiki.
|
|
26
|
+
|
|
27
|
+
## [2.7.1] - 2026-11-10
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Add `llama help wiki`, save usage guidance, and MCP publishing descriptions
|
|
32
|
+
that lead agents to the current authenticated Wiki publishing contract and
|
|
33
|
+
complete self-contained HTML. Existing Markdown save behavior is preserved.
|
|
34
|
+
- Allow MCP `wiki_save` to send `lang`, matching CLI and Core support for
|
|
35
|
+
language-specific publications and updates.
|
|
36
|
+
|
|
37
|
+
## [2.7.0] - 2026-11-05
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Read Deal Information selectively with `llama deal read <dealId>
|
|
42
|
+
--information-id <ids> | --information-type <types> | --information-q <text>`
|
|
43
|
+
or MCP `read_deal.informationIds/informationTypes/informationQuery`, instead
|
|
44
|
+
of loading all memory with `--detail memory`. Core API 5.42.0+ returns full
|
|
45
|
+
values for id reads and snippets for text reads, omits values over 8,000
|
|
46
|
+
characters in text/type reads, and echoes the applied filter; responses from
|
|
47
|
+
an older Core that ignored the filter are rejected.
|
|
48
|
+
- Filter Deal search by pipeline status with `llama deal search --status
|
|
49
|
+
Invested[,Diligence]` or MCP `search_deals.status`, alone or combined with a
|
|
50
|
+
query, sector, state and limit, so "every invested Deal" is one bounded query
|
|
51
|
+
instead of a guess. Core API 5.42.0+ echoes the applied statuses; an older
|
|
52
|
+
Core is rejected.
|
|
53
|
+
|
|
9
54
|
## [2.6.0] - 2026-11-02
|
|
10
55
|
|
|
11
56
|
### Added
|
package/README.md
CHANGED
|
@@ -68,6 +68,15 @@ llama deal search "humanoid" --sector "robotics" --state active
|
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
`--sector` searches sector and subsector and can be used without a query.
|
|
71
|
+
`--status` matches the current pipeline status exactly (any of a comma list)
|
|
72
|
+
and also works alone; with Core API 5.42.0+ it enumerates, for example, every
|
|
73
|
+
invested Deal in one bounded query:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
llama deal search --status Invested --limit 200
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Results carry `investmentDate` and `markedInvestedAt` for ordering.
|
|
71
80
|
MCP `search_deals` accepts the same optional `q`, `sector`, `state` and `limit`.
|
|
72
81
|
If Core does not confirm the sector filter in its response, the client reports
|
|
73
82
|
an unsupported-server error instead of returning an unfiltered pipeline.
|
|
@@ -80,6 +89,27 @@ wording, then read candidate Deals to verify specific categories and claims.
|
|
|
80
89
|
Search reads current Page data directly, so updates and clears are reflected
|
|
81
90
|
immediately without a separate indexing job or manual tag maintenance.
|
|
82
91
|
|
|
92
|
+
|
|
93
|
+
### Read Information selectively
|
|
94
|
+
|
|
95
|
+
With Core API 5.42.0 or newer, read only the Information you need instead of
|
|
96
|
+
`--detail memory` (which can return megabytes on a large Deal):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
llama deal read <dealId> --information-q "ARR"
|
|
100
|
+
llama deal read <dealId> --information-type founder_claim,team
|
|
101
|
+
llama deal read <dealId> --information-id <recordId>,<recordId>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`--information-q` matches literal text in a record's type, subject, labels and
|
|
105
|
+
value and returns `match.snippets` around each hit. In text and type reads, a
|
|
106
|
+
value over 8,000 characters comes back as `{ omitted, chars }`; read that record
|
|
107
|
+
with `--information-id` for its full value. Filters combine with AND, return at
|
|
108
|
+
most 50 records, and `informationFilter` echoes what Core applied
|
|
109
|
+
(`matched > returned` means narrow the filter). MCP `read_deal` accepts the same
|
|
110
|
+
filters as `informationIds`, `informationTypes` and `informationQuery`. If Core
|
|
111
|
+
does not confirm the filter, the client reports an unsupported-server error
|
|
112
|
+
instead of returning unfiltered memory.
|
|
83
113
|
### Create
|
|
84
114
|
|
|
85
115
|
```json
|
package/README.zh-CN.md
CHANGED
|
@@ -70,6 +70,34 @@ llama deal search "humanoid" --sector "robotics" --state active
|
|
|
70
70
|
更宽的行业或其他措辞,再读取候选 Deal 核实具体分类和结论。搜索直接读取当前
|
|
71
71
|
Page 数据,更新和清空立即生效,无需另外维护索引或标签。
|
|
72
72
|
|
|
73
|
+
|
|
74
|
+
`--status` 按当前 pipeline 状态精确匹配(可用逗号给多个),也可以单独使用。
|
|
75
|
+
Core API 5.42.0 及以上版本可以一次列出所有已投 Deal:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
llama deal search --status Invested --limit 200
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
结果里带 `investmentDate` 和 `markedInvestedAt`,可以按投资时间排序。
|
|
82
|
+
|
|
83
|
+
### 按需读取 Information
|
|
84
|
+
|
|
85
|
+
Core API 5.42.0 及以上版本可以只读需要的 Information,不必用 `--detail memory`
|
|
86
|
+
(大 Deal 可能一次返回数 MB):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
llama deal read <dealId> --information-q "ARR"
|
|
90
|
+
llama deal read <dealId> --information-type founder_claim,team
|
|
91
|
+
llama deal read <dealId> --information-id <recordId>,<recordId>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`--information-q` 在记录的 type、subject、labels 和 value 中做字面匹配,并在
|
|
95
|
+
`match.snippets` 里返回命中处的上下文。按文本或类型读取时,超过 8,000 字符的
|
|
96
|
+
value 会以 `{ omitted, chars }` 返回,需要全文时用 `--information-id` 读取该记录。
|
|
97
|
+
多个条件按 AND 组合,最多返回 50 条;`informationFilter` 回显 Core 实际应用的
|
|
98
|
+
条件(`matched > returned` 表示需要再收窄)。MCP `read_deal` 用
|
|
99
|
+
`informationIds`、`informationTypes`、`informationQuery` 接受同样的条件。如果
|
|
100
|
+
Core 没有确认过滤条件,客户端会报"服务端不支持",而不是返回未过滤的全部内容。
|
|
73
101
|
### 创建 Deal
|
|
74
102
|
|
|
75
103
|
```json
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { buildArtifactReadPath, buildWikiReadPath } from "../lib/content-read.mjs";
|
|
4
3
|
import { createRequire } from "node:module";
|
|
5
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
6
|
import { z } from "zod";
|
|
7
|
+
import { registerApiTools, structuredBootstrapManifest, textResult } from "../lib/mcp-api-tools.mjs";
|
|
8
8
|
import {
|
|
9
9
|
formatErrorForDisplay,
|
|
10
10
|
getAuthHeaders,
|
|
11
11
|
readBriefing,
|
|
12
12
|
request,
|
|
13
13
|
setClientRuntime,
|
|
14
|
-
structuredBootstrapManifest,
|
|
15
14
|
} from "../lib/client.mjs";
|
|
16
15
|
import {
|
|
17
16
|
clearExternalSession,
|
|
@@ -20,16 +19,7 @@ import {
|
|
|
20
19
|
startExternalSession,
|
|
21
20
|
uploadExternalFile,
|
|
22
21
|
} from "../lib/external.mjs";
|
|
23
|
-
import {
|
|
24
|
-
buildDealReadPath,
|
|
25
|
-
buildDealSearchPath,
|
|
26
|
-
verifyDealSearchResult,
|
|
27
|
-
compactDealWriteResult,
|
|
28
|
-
prepareDealCommand,
|
|
29
|
-
} from "../lib/deal-actions.mjs";
|
|
30
22
|
import { submitFeedback, readFeedback, resolveFeedback } from "../lib/feedback.mjs";
|
|
31
|
-
import { buildDealMemoryPath } from "../lib/deal-memory-actions.mjs";
|
|
32
|
-
import { buildPageSchemaPath, MAX_PAGE_SCHEMA_FIELDS } from "../lib/page-schema.mjs";
|
|
33
23
|
|
|
34
24
|
const requireFromHere = createRequire(import.meta.url);
|
|
35
25
|
const { version: PKG_VERSION } = requireFromHere("../package.json");
|
|
@@ -37,13 +27,6 @@ setClientRuntime({ client: "mcp" });
|
|
|
37
27
|
|
|
38
28
|
const server = new McpServer({ name: "llama-mcp", version: PKG_VERSION });
|
|
39
29
|
|
|
40
|
-
function textResult(text, isError = false) {
|
|
41
|
-
return {
|
|
42
|
-
content: [{ type: "text", text }],
|
|
43
|
-
...(isError ? { isError: true } : {}),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
30
|
async function callApi(method, path, body, transform = (value) => value) {
|
|
48
31
|
try {
|
|
49
32
|
const result = transform(await request(method, path, body));
|
|
@@ -84,168 +67,9 @@ server.registerTool("feedback_resolve", {
|
|
|
84
67
|
catch (error) { return textResult(formatErrorForDisplay(error), true); }
|
|
85
68
|
});
|
|
86
69
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
originatingChatRecordId: z.string().uuid().optional(),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const writeDealCommandSchema = z.discriminatedUnion("operation", [
|
|
94
|
-
z.object({
|
|
95
|
-
operation: z.literal("input.submit"),
|
|
96
|
-
dealId: z.string().uuid(),
|
|
97
|
-
format: z.string().min(1).max(120).optional(),
|
|
98
|
-
content: z.any(),
|
|
99
|
-
source: z.record(z.string(), z.any()).optional(),
|
|
100
|
-
origin: originSchema,
|
|
101
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
102
|
-
}).strict(),
|
|
103
|
-
z.object({
|
|
104
|
-
operation: z.literal("information.put"),
|
|
105
|
-
dealId: z.string().uuid(),
|
|
106
|
-
informationId: z.string().uuid().optional(),
|
|
107
|
-
type: z.string().min(1).max(120),
|
|
108
|
-
labels: z.array(z.string()).optional(),
|
|
109
|
-
subject: z.record(z.string(), z.any()).optional(),
|
|
110
|
-
value: z.any(),
|
|
111
|
-
expectedVersion: z.number().int().positive().optional(),
|
|
112
|
-
origin: originSchema,
|
|
113
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
114
|
-
}).strict(),
|
|
115
|
-
z.object({
|
|
116
|
-
operation: z.literal("page.patch"),
|
|
117
|
-
dealId: z.string().uuid(),
|
|
118
|
-
patch: z.record(z.string(), z.any()),
|
|
119
|
-
expectedRevision: z.number().int().nonnegative().optional(),
|
|
120
|
-
origin: originSchema,
|
|
121
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
122
|
-
}).strict(),
|
|
123
|
-
z.object({
|
|
124
|
-
operation: z.literal("artifact.put"),
|
|
125
|
-
dealId: z.string().uuid(),
|
|
126
|
-
artifactId: z.string().uuid().optional().describe("Reuse the existing ID to append a complete file version, including on rename. Omit for a new document without a title collision, or explicitly use a fresh UUID for a distinct same-name source."),
|
|
127
|
-
kind: z.string().min(1).max(120),
|
|
128
|
-
title: z.string().min(1).max(500),
|
|
129
|
-
mimeType: z.string().min(1).max(240),
|
|
130
|
-
contentBase64: z.string().min(1).optional(),
|
|
131
|
-
storageKey: z.string().min(1).optional(),
|
|
132
|
-
storageUrl: z.string().url().optional(),
|
|
133
|
-
byteSize: z.number().int().nonnegative().optional(),
|
|
134
|
-
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
135
|
-
metadata: z.record(z.string(), z.any()).optional(),
|
|
136
|
-
origin: originSchema,
|
|
137
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
138
|
-
}).strict(),
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
// Deal is deliberately a closed four-tool surface. Do not add split resource
|
|
142
|
-
// tools here: one more tool is one more branch every agent must reason about.
|
|
143
|
-
server.registerTool(
|
|
144
|
-
"search_deals",
|
|
145
|
-
{
|
|
146
|
-
description: "Search current Deal metadata by company name/key, description, sector/subsector or founder name in either stored language. Use short keywords; matching is a case-insensitive literal substring, not semantic search. Combine q with sector/state/limit, or use sector alone for category discovery. Sector filters before limit and requires a supporting Core server. Exact names rank first. Inspect searchMatch fields and search.has_more; truncated results are not exhaustive. On zero matches, try broader or alternative wording before concluding no Deal exists. Read candidates to verify specific categories and claims. Always search before creating a company.",
|
|
147
|
-
inputSchema: {
|
|
148
|
-
q: z.string().optional(),
|
|
149
|
-
sector: z.string().trim().min(1).max(200).optional(),
|
|
150
|
-
state: z.enum(["active", "archived", "trashed"]).optional(),
|
|
151
|
-
limit: z.number().int().min(1).max(2000).optional(),
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
// @core-api-operation GET /api/occam/deals
|
|
155
|
-
async ({ q, sector, state, limit } = {}) => callApi(
|
|
156
|
-
"GET", buildDealSearchPath(q, { sector, state, limit }), undefined,
|
|
157
|
-
(result) => verifyDealSearchResult(result, { sector }),
|
|
158
|
-
),
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
server.registerTool(
|
|
162
|
-
"read_deal",
|
|
163
|
-
{
|
|
164
|
-
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Use artifactId to read original source text with page/paragraph anchors. Continue with nextOffset and source.sha256. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
165
|
-
inputSchema: {
|
|
166
|
-
dealId: z.string().min(1),
|
|
167
|
-
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
168
|
-
artifactId: z.string().uuid().optional(),
|
|
169
|
-
version: z.number().int().min(1).optional(),
|
|
170
|
-
offset: z.number().int().min(0).optional(),
|
|
171
|
-
limit: z.number().int().min(1).max(50000).optional(),
|
|
172
|
-
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
async ({ dealId, detail, artifactId, ...options }) => {
|
|
176
|
-
if (artifactId) {
|
|
177
|
-
if (detail) throw new Error("Use artifactId or detail, not both");
|
|
178
|
-
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
179
|
-
return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
|
|
180
|
-
}
|
|
181
|
-
if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
|
|
182
|
-
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
183
|
-
return callApi("GET", buildDealReadPath(dealId, detail || "overview"));
|
|
184
|
-
},
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
server.registerTool(
|
|
188
|
-
"create_deal",
|
|
189
|
-
{
|
|
190
|
-
description: "Create one Deal intent. Core owns Drive, initial resources, idempotency, and Events.",
|
|
191
|
-
inputSchema: {
|
|
192
|
-
companyName: z.string().min(1).max(240),
|
|
193
|
-
companyKey: z.string().min(1).max(240).optional(),
|
|
194
|
-
page: z.record(z.string(), z.any()).optional(),
|
|
195
|
-
information: z.array(z.record(z.string(), z.any())).optional(),
|
|
196
|
-
origin: originSchema,
|
|
197
|
-
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
async (input) => callApi("POST", "/api/occam/deals/commands", prepareDealCommand("create", input)),
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
server.registerTool(
|
|
204
|
-
"write_deal",
|
|
205
|
-
{
|
|
206
|
-
description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. Before artifact.put, read Deal files: reuse artifactId for a revision; a same-kind/title collision without an ID returns OCCAM_CONFLICT. Titles are candidates, not identity. Select the intended existing ID from the request/content, or explicitly generate a fresh UUID for a distinct same-name source. Send complete revised bytes, preserve applicable metadata, and verify the ID/version. Core hosts text/html in Command; do not mirror HTML to Drive or require a Drive link. Non-HTML source files, including original transcripts and recordings, go to Drive through artifact.put. Keep source-file bodies out of Input, Information, Page and metadata; save extracted facts and summaries with source references and preserve the user request. page.patch does not edit file bytes. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker, value.rawText quoted verbatim from origin.originalUserUtterance, and value.summary (the agent's restatement); it is rejected otherwise.",
|
|
207
|
-
// MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
|
|
208
|
-
// command preserves the real discriminated union instead of flattening four
|
|
209
|
-
// incompatible operations into false affordances.
|
|
210
|
-
inputSchema: { command: writeDealCommandSchema },
|
|
211
|
-
},
|
|
212
|
-
async ({ command }) => callApi(
|
|
213
|
-
"POST",
|
|
214
|
-
"/api/occam/deals/commands",
|
|
215
|
-
prepareDealCommand("write", command),
|
|
216
|
-
(result) => compactDealWriteResult(command, result),
|
|
217
|
-
),
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
// Deal Memory is a separate sidecar domain. These do not add an Occam Deal
|
|
221
|
-
// business resource or widen the deliberately closed four-tool Deal surface.
|
|
222
|
-
server.registerTool(
|
|
223
|
-
"get_deal_memory",
|
|
224
|
-
{
|
|
225
|
-
description: "Read the complete canonical Markdown Deal Story and its opaque concurrency version. A returned placeholder is an existing Story whose version is required for a write; only 404 means no Story exists.",
|
|
226
|
-
inputSchema: { dealId: z.string().uuid() },
|
|
227
|
-
},
|
|
228
|
-
// @core-api-operation GET /api/deal-memory/{dealId}/story
|
|
229
|
-
async ({ dealId }) => callApi("GET", buildDealMemoryPath(dealId)),
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
server.registerTool(
|
|
233
|
-
"update_deal_memory",
|
|
234
|
-
{
|
|
235
|
-
description: "Replace the complete Markdown Deal Story with one coherent current understanding; never append an update log, restate Live Deal Page/Deal Information fields, or write when understanding would not materially improve. Frontmatter must contain matching deal_id plus stable uuid and created timestamps; updated must strictly advance. Pass the version from get_deal_memory for every returned Story, including a placeholder; only 404 permits creation without expected_version.",
|
|
236
|
-
inputSchema: {
|
|
237
|
-
dealId: z.string().uuid(),
|
|
238
|
-
markdown: z.string().min(1).max(1_048_576),
|
|
239
|
-
expected_version: z.string().min(1).optional(),
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
// @core-api-operation PUT /api/deal-memory/{dealId}/story
|
|
243
|
-
async ({ dealId, markdown, expected_version }) => callApi(
|
|
244
|
-
"PUT",
|
|
245
|
-
buildDealMemoryPath(dealId),
|
|
246
|
-
{ markdown, ...(expected_version ? { expected_version } : {}) },
|
|
247
|
-
),
|
|
248
|
-
);
|
|
70
|
+
// Deal, Deal Memory, bootstrap, Page schema, skills, preferences and Wiki tools
|
|
71
|
+
// are shared with remote MCP hosts; see lib/mcp-api-tools.mjs.
|
|
72
|
+
registerApiTools(server, { request, formatError: formatErrorForDisplay, version: PKG_VERSION });
|
|
249
73
|
|
|
250
74
|
server.registerTool(
|
|
251
75
|
"auth_status",
|
|
@@ -265,184 +89,6 @@ server.registerTool(
|
|
|
265
89
|
},
|
|
266
90
|
);
|
|
267
91
|
|
|
268
|
-
server.registerTool(
|
|
269
|
-
"agent_bootstrap",
|
|
270
|
-
{
|
|
271
|
-
description: "Mandatory before Llama work: fetch the authenticated two-part private Brain (Investment Framework V3 + Llama Command operating skill), a compact Live Deal Page schema index, runtime contract, and visible Llama OS skills. Read exact Page fields separately with get_live_deal_page_schema.",
|
|
272
|
-
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
273
|
-
},
|
|
274
|
-
async ({ limit } = {}) => {
|
|
275
|
-
const params = new URLSearchParams({ clientVersion: PKG_VERSION });
|
|
276
|
-
params.set("page_schema", "progressive");
|
|
277
|
-
if (limit) params.set("limit", String(limit));
|
|
278
|
-
try {
|
|
279
|
-
const result = await request("GET", `/api/agent/manifest?${params}`);
|
|
280
|
-
return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
|
|
281
|
-
} catch (error) {
|
|
282
|
-
return textResult(formatErrorForDisplay(error), true);
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
server.registerTool(
|
|
288
|
-
"get_live_deal_page_schema",
|
|
289
|
-
{
|
|
290
|
-
description: "Read the Live Deal Page schema progressively. Call with no selector for the compact index; before page.patch, request only exact fields or one section. This is read-only context, not a fifth Deal action.",
|
|
291
|
-
inputSchema: {
|
|
292
|
-
fields: z.array(z.string().min(1)).min(1).max(MAX_PAGE_SCHEMA_FIELDS).optional(),
|
|
293
|
-
section: z.string().min(1).optional(),
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
async ({ fields, section } = {}) => {
|
|
297
|
-
try {
|
|
298
|
-
// @core-api-operation GET /api/agent/page-schema
|
|
299
|
-
return await callApi("GET", buildPageSchemaPath({ fields, section }));
|
|
300
|
-
} catch (error) {
|
|
301
|
-
return textResult(formatErrorForDisplay(error), true);
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
server.registerTool(
|
|
307
|
-
"skills_search",
|
|
308
|
-
{
|
|
309
|
-
description: "Search authenticated runtime skills; read only the relevant result.",
|
|
310
|
-
inputSchema: { q: z.string().min(1), limit: z.number().int().min(1).max(100).optional() },
|
|
311
|
-
},
|
|
312
|
-
async ({ q, limit }) => {
|
|
313
|
-
const params = new URLSearchParams({ q });
|
|
314
|
-
if (limit) params.set("limit", String(limit));
|
|
315
|
-
return callApi("GET", `/api/agent/skills?${params}`);
|
|
316
|
-
},
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
server.registerTool(
|
|
320
|
-
"skills_read",
|
|
321
|
-
{
|
|
322
|
-
description: "Read one authenticated runtime skill by slug.",
|
|
323
|
-
inputSchema: { slug: z.string().min(1) },
|
|
324
|
-
},
|
|
325
|
-
async ({ slug }) => callApi("GET", `/api/agent/skills/${encodeURIComponent(slug)}`),
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
server.registerTool(
|
|
329
|
-
"pref_list",
|
|
330
|
-
{
|
|
331
|
-
description: "List standing agent preferences.",
|
|
332
|
-
inputSchema: { status: z.enum(["active", "proposed", "retired", "all"]).optional() },
|
|
333
|
-
},
|
|
334
|
-
async ({ status } = {}) => {
|
|
335
|
-
const params = new URLSearchParams();
|
|
336
|
-
if (status) params.set("status", status);
|
|
337
|
-
return callApi("GET", `/api/agent/preferences${params.size ? `?${params}` : ""}`);
|
|
338
|
-
},
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
server.registerTool(
|
|
342
|
-
"pref_add",
|
|
343
|
-
{
|
|
344
|
-
description: "Save a durable user or team preference; procedures belong in skills.",
|
|
345
|
-
inputSchema: {
|
|
346
|
-
key: z.string().min(1),
|
|
347
|
-
content: z.string().min(1).max(280),
|
|
348
|
-
scope: z.enum(["user", "team"]).optional(),
|
|
349
|
-
evidence: z.string().optional(),
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
async ({ key, content, scope, evidence }) =>
|
|
353
|
-
callApi("POST", "/api/agent/preferences", { key, content, scope, evidence }),
|
|
354
|
-
);
|
|
355
|
-
|
|
356
|
-
server.registerTool(
|
|
357
|
-
"pref_set_status",
|
|
358
|
-
{
|
|
359
|
-
description: "Activate or retire a standing preference.",
|
|
360
|
-
inputSchema: { id: z.number().int().positive(), status: z.enum(["active", "retired"]) },
|
|
361
|
-
},
|
|
362
|
-
async ({ id, status }) => callApi("PATCH", `/api/agent/preferences/${id}`, { status }),
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
server.registerTool(
|
|
366
|
-
"object_inspect",
|
|
367
|
-
{
|
|
368
|
-
description: "Explain a Command URL or object before guessing that it is broken.",
|
|
369
|
-
inputSchema: {
|
|
370
|
-
q: z.string().optional(),
|
|
371
|
-
type: z.string().optional(),
|
|
372
|
-
id: z.string().optional(),
|
|
373
|
-
lang: z.enum(["en", "zh"]).optional(),
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
async ({ q, type, id, lang } = {}) => {
|
|
377
|
-
const params = new URLSearchParams();
|
|
378
|
-
if (q) params.set("q", q);
|
|
379
|
-
if (type) params.set("type", type);
|
|
380
|
-
if (id) params.set("id", id);
|
|
381
|
-
if (lang) params.set("lang", lang);
|
|
382
|
-
return callApi("GET", `/api/agent/explain?${params}`);
|
|
383
|
-
},
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
server.registerTool(
|
|
387
|
-
"wiki_search",
|
|
388
|
-
{
|
|
389
|
-
description: "Search institutional knowledge in the internal Wiki.",
|
|
390
|
-
inputSchema: { q: z.string().min(1) },
|
|
391
|
-
},
|
|
392
|
-
async ({ q }) => callApi("GET", `/api/wiki/search?q=${encodeURIComponent(q)}`),
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
server.registerTool(
|
|
396
|
-
"wiki_read",
|
|
397
|
-
{
|
|
398
|
-
description: "Read one Wiki article. format=text extracts its original upload or page body. attachment follows an ID listed in attachments. Continuations require offset and sha256.",
|
|
399
|
-
inputSchema: {
|
|
400
|
-
slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
|
|
401
|
-
format: z.literal("text").optional(), attachment: z.string().optional(),
|
|
402
|
-
offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
|
|
403
|
-
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
// @core-api-operation GET /api/wiki/{slug}
|
|
407
|
-
async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
server.registerTool(
|
|
411
|
-
"wiki_save",
|
|
412
|
-
{
|
|
413
|
-
description: "Create or update cross-Deal institutional knowledge, not Deal data.",
|
|
414
|
-
inputSchema: {
|
|
415
|
-
slug: z.string().min(1),
|
|
416
|
-
title: z.string().min(1),
|
|
417
|
-
content: z.string(),
|
|
418
|
-
sources: z.array(z.string()).min(1),
|
|
419
|
-
content_type: z.enum(["markdown", "html"]).optional(),
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
async ({ slug, title, content, sources, content_type }) =>
|
|
423
|
-
callApi("POST", "/api/wiki/save", { slug, title, content, sources, content_type }),
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
server.registerTool(
|
|
427
|
-
"wiki_delete",
|
|
428
|
-
{
|
|
429
|
-
description: "Soft-delete a Wiki article.",
|
|
430
|
-
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
431
|
-
},
|
|
432
|
-
async ({ slug, lang }) =>
|
|
433
|
-
callApi("DELETE", `/api/wiki/${encodeURIComponent(slug)}?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
server.registerTool(
|
|
437
|
-
"wiki_restore",
|
|
438
|
-
{
|
|
439
|
-
description: "Restore a soft-deleted Wiki article.",
|
|
440
|
-
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
441
|
-
},
|
|
442
|
-
async ({ slug, lang }) =>
|
|
443
|
-
callApi("POST", `/api/wiki/${encodeURIComponent(slug)}/restore?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
444
|
-
);
|
|
445
|
-
|
|
446
92
|
server.registerTool(
|
|
447
93
|
"pitch_start",
|
|
448
94
|
{
|
package/bin/llama.mjs
CHANGED
|
@@ -36,7 +36,9 @@ import {
|
|
|
36
36
|
DEAL_ACTIONS,
|
|
37
37
|
buildDealReadPath,
|
|
38
38
|
buildDealSearchPath,
|
|
39
|
+
informationReadFilter,
|
|
39
40
|
verifyDealSearchResult,
|
|
41
|
+
verifyInformationReadResult,
|
|
40
42
|
compactDealWriteResult,
|
|
41
43
|
prepareDealCommand,
|
|
42
44
|
readJsonInput,
|
|
@@ -54,7 +56,7 @@ const { version: PKG_VERSION } = requireFromHere("../package.json");
|
|
|
54
56
|
const HELP_ROOT = `Llama Command CLI 2 — small authenticated tools for agents.
|
|
55
57
|
|
|
56
58
|
Deal has exactly four actions:
|
|
57
|
-
llama deal search [query] [--sector <text>] [--state active|archived|trashed] [--limit 10]
|
|
59
|
+
llama deal search [query] [--sector <text>] [--status <status>] [--state active|archived|trashed] [--limit 10]
|
|
58
60
|
llama deal read <dealId> [--detail overview|memory|files|conversation|history|all]
|
|
59
61
|
llama deal create --json <file|->
|
|
60
62
|
llama deal write --json <file|->
|
|
@@ -82,12 +84,39 @@ Separate preserved domains:
|
|
|
82
84
|
llama admin auth-events|deal-events|agent-events|feedback
|
|
83
85
|
llama pitch start|say|upload|status|finalize|end
|
|
84
86
|
|
|
85
|
-
Run \`llama help deal\` for the mutation contract.`;
|
|
87
|
+
Run \`llama help deal\` for the mutation contract and \`llama help wiki\` for publishing.`;
|
|
88
|
+
|
|
89
|
+
const HELP_WIKI = `Llama Wiki — publish complete institutional knowledge pages
|
|
90
|
+
|
|
91
|
+
llama agent bootstrap
|
|
92
|
+
llama wiki search <query>
|
|
93
|
+
llama wiki read <slug> [--lang en|zh]
|
|
94
|
+
llama wiki save <slug> --title "..." --file page.html --content-type html --lang en --sources "url1;url2"
|
|
95
|
+
llama wiki delete <slug> [--lang en|zh]
|
|
96
|
+
llama wiki restore <slug> [--lang en|zh]
|
|
97
|
+
|
|
98
|
+
Follow the current authenticated contract.wiki_publishing guidance. New
|
|
99
|
+
publications default to complete self-contained HTML. Preserve the report's
|
|
100
|
+
full content, charts, interactions and existing language/version controls.
|
|
101
|
+
Embed CSS, JavaScript, chart data and rendering assets in the HTML.
|
|
102
|
+
|
|
103
|
+
--file reads the full file; .html/.htm selects HTML automatically. When sending
|
|
104
|
+
HTML through --content, explicitly set --content-type html. Use --lang zh for
|
|
105
|
+
Chinese content. Sources are required; separate them with semicolons or pipes.
|
|
106
|
+
|
|
107
|
+
Honor an explicit Markdown request with --content-type markdown. Read an
|
|
108
|
+
existing slug and language before editing and preserve its content_type.
|
|
109
|
+
Format conversion or replacement needs authorization for that change.
|
|
110
|
+
|
|
111
|
+
Read the receipt's canonical_url, content_type, langs_available, verified and
|
|
112
|
+
warnings, then read back the article and check the rendered page and its
|
|
113
|
+
interactions before reporting completion. Report any checks still pending.
|
|
114
|
+
Wiki publication requires the user's request or authorization.`;
|
|
86
115
|
|
|
87
116
|
const HELP_DEAL = `Llama Command CLI 2 — Deal contract
|
|
88
117
|
|
|
89
118
|
Exactly four actions:
|
|
90
|
-
llama deal search [query] [--sector <text>] [--state active|archived|trashed] [--limit 10]
|
|
119
|
+
llama deal search [query] [--sector <text>] [--status <status>] [--state active|archived|trashed] [--limit 10]
|
|
91
120
|
llama deal read <dealId> [--detail overview|memory|files|conversation|history|all]
|
|
92
121
|
llama deal create --json <file|->
|
|
93
122
|
llama deal write --json <file|->
|
|
@@ -99,10 +128,23 @@ Combine --sector with a query/state/limit, or search a sector without a query:
|
|
|
99
128
|
llama deal search "humanoid" --sector "robotics"
|
|
100
129
|
Metadata search and --sector require Core API 5.41.0 or newer. A sector filter
|
|
101
130
|
must be confirmed by Core; old unfiltered responses are rejected.
|
|
131
|
+
--status matches the current pipeline status exactly (any of a comma list) and
|
|
132
|
+
works alone; it requires Core API 5.42.0+. To list every invested Deal:
|
|
133
|
+
llama deal search --status Invested --limit 200
|
|
134
|
+
then order by investmentDate / markedInvestedAt.
|
|
102
135
|
Use short keywords. searchMatch lists matched fields; search.has_more means
|
|
103
136
|
results are truncated. On zero matches, try broader or alternative wording.
|
|
104
137
|
Read candidate Deals to verify categories and substantive claims.
|
|
105
138
|
|
|
139
|
+
Read Information selectively (Core API 5.42.0+), instead of --detail memory:
|
|
140
|
+
llama deal read <dealId> --information-q "ARR" literal text; returns snippets
|
|
141
|
+
llama deal read <dealId> --information-type founder_claim exact types, comma-separated
|
|
142
|
+
llama deal read <dealId> --information-id <id>[,<id>] full values, up to 50
|
|
143
|
+
Filters combine with AND. Values over 8,000 characters in a text or type read
|
|
144
|
+
come back as { omitted, chars }; read that record by id. informationFilter
|
|
145
|
+
echoes what Core applied; matched > returned means narrow the filter. An older
|
|
146
|
+
Core that ignores the filter is rejected rather than shown as filtered.
|
|
147
|
+
|
|
106
148
|
Read original source content:
|
|
107
149
|
llama deal read <dealId> --artifact <artifactId> [--version N] [--offset N --sha256 HASH] [--limit N] [--output <file>]
|
|
108
150
|
llama wiki read <slug> --format text [--attachment <referenceId>] [--offset N --sha256 HASH] [--output <file>]
|
|
@@ -268,6 +310,7 @@ function usage(area) {
|
|
|
268
310
|
area === "memory" ? HELP_MEMORY :
|
|
269
311
|
area === "page-schema" ? HELP_PAGE_SCHEMA :
|
|
270
312
|
area === "feedback" ? HELP_FEEDBACK :
|
|
313
|
+
area === "wiki" ? HELP_WIKI :
|
|
271
314
|
HELP_ROOT,
|
|
272
315
|
);
|
|
273
316
|
}
|
|
@@ -540,7 +583,7 @@ async function handleWiki(action, rest) {
|
|
|
540
583
|
}
|
|
541
584
|
if (action === "save") {
|
|
542
585
|
if (!slug || typeof flags.title !== "string" || typeof flags.sources !== "string") {
|
|
543
|
-
throw new Error('Usage: llama wiki save <slug> --title "..." --
|
|
586
|
+
throw new Error('Usage: llama wiki save <slug> --title "..." --file page.html --content-type html --sources "url1;url2". See llama help wiki for HTML and Markdown publishing.');
|
|
544
587
|
}
|
|
545
588
|
if (flags.content && flags.file) throw new Error("Use either --content or --file, not both.");
|
|
546
589
|
const content = flags.file ? await readFile(String(flags.file), "utf8") : flags.content;
|
|
@@ -749,7 +792,7 @@ async function main() {
|
|
|
749
792
|
}
|
|
750
793
|
|
|
751
794
|
if (area === "deal" && action === "search") {
|
|
752
|
-
const { flags, positional } = parseFlags(rest, ["state", "limit", "sector"]);
|
|
795
|
+
const { flags, positional } = parseFlags(rest, ["state", "limit", "sector", "status"]);
|
|
753
796
|
// @core-api-operation GET /api/occam/deals
|
|
754
797
|
const result = await request("GET", buildDealSearchPath(positional.join(" ").trim(), flags));
|
|
755
798
|
print(verifyDealSearchResult(result, flags));
|
|
@@ -757,16 +800,19 @@ async function main() {
|
|
|
757
800
|
}
|
|
758
801
|
if (area === "deal" && action === "read") {
|
|
759
802
|
const dealId = rest[0];
|
|
760
|
-
const { flags } = parseFlags(rest.slice(1), ["detail", "artifact", "version", "format", "offset", "limit", "sha256", "output"]);
|
|
803
|
+
const { flags } = parseFlags(rest.slice(1), ["detail", "artifact", "version", "format", "offset", "limit", "sha256", "output", "information-id", "information-type", "information-q"]);
|
|
804
|
+
const filter = informationReadFilter({ ids: flags["information-id"], types: flags["information-type"], q: flags["information-q"] });
|
|
761
805
|
if (flags.artifact !== undefined) {
|
|
762
806
|
if (flags.detail !== undefined) throw new Error("Use --artifact or --detail, not both");
|
|
807
|
+
if (filter) throw new Error("Use --artifact or --information-*, not both");
|
|
763
808
|
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
764
809
|
const result = await request("GET", buildArtifactReadPath(dealId, flags.artifact, flags));
|
|
765
810
|
print(await saveContentSource(result, flags.output));
|
|
766
811
|
} else {
|
|
767
812
|
if (["version", "format", "offset", "limit", "sha256", "output"].some(key => flags[key] !== undefined)) throw new Error("Content options require --artifact");
|
|
768
813
|
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
769
|
-
|
|
814
|
+
const result = await request("GET", buildDealReadPath(dealId, flags.detail === true ? "overview" : flags.detail || "overview", filter));
|
|
815
|
+
print(verifyInformationReadResult(result, filter));
|
|
770
816
|
}
|
|
771
817
|
return;
|
|
772
818
|
}
|
package/contracts/core-api.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "llama.core-api-contract.v1",
|
|
3
3
|
"name": "llama-core-api",
|
|
4
|
-
"apiVersion": "5.
|
|
4
|
+
"apiVersion": "5.42.0",
|
|
5
5
|
"openapiVersion": "3.0.3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "c244351c5d6b4f9f917a234229a3e7044558e8c4870decf0e8b1518a166f8799",
|
|
7
7
|
"pathCount": 133,
|
|
8
8
|
"operationCount": 164
|
|
9
9
|
}
|
package/lib/build-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"format": "llama.cli-build.v1",
|
|
3
3
|
"packageName": "@llamaventures/cli",
|
|
4
|
-
"packageVersion": "2.
|
|
5
|
-
"sourceSha": "
|
|
4
|
+
"packageVersion": "2.8.0",
|
|
5
|
+
"sourceSha": "ec9daefda51090ca3331a22db1db45b3c56c3452",
|
|
6
6
|
"sourceKind": "github",
|
|
7
7
|
"sourceDirty": false,
|
|
8
8
|
"coreApiContract": {
|
|
9
9
|
"format": "llama.core-api-contract.v1",
|
|
10
10
|
"name": "llama-core-api",
|
|
11
|
-
"apiVersion": "5.
|
|
11
|
+
"apiVersion": "5.42.0",
|
|
12
12
|
"openapiVersion": "3.0.3",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "c244351c5d6b4f9f917a234229a3e7044558e8c4870decf0e8b1518a166f8799"
|
|
14
14
|
}
|
|
15
15
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -594,13 +594,7 @@ export function formatErrorForDisplay(error) {
|
|
|
594
594
|
return `Error${code}: ${message}`;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
export
|
|
598
|
-
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
599
|
-
return manifest;
|
|
600
|
-
}
|
|
601
|
-
const { briefing: _renderedDuplicate, ...structured } = manifest;
|
|
602
|
-
return structured;
|
|
603
|
-
}
|
|
597
|
+
export { structuredBootstrapManifest } from "./mcp-api-tools.mjs";
|
|
604
598
|
|
|
605
599
|
async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
|
|
606
600
|
const authHeaders = await getAuthHeaders();
|
package/lib/deal-actions.mjs
CHANGED
|
@@ -169,11 +169,27 @@ export function buildDealSearchPath(query, flags = {}) {
|
|
|
169
169
|
if (query) params.set("q", query);
|
|
170
170
|
const sector = dealSearchSector(flags);
|
|
171
171
|
if (sector !== undefined) params.set("sector", sector);
|
|
172
|
+
const status = dealSearchStatus(flags);
|
|
173
|
+
if (status !== undefined) params.set("status", status.join(","));
|
|
172
174
|
if (flags.state && flags.state !== true) params.set("state", String(flags.state));
|
|
173
175
|
if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
|
|
174
176
|
return `/api/occam/deals${params.size ? `?${params}` : ""}`;
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
/** Exact pipeline statuses (Core API 5.42.0+), e.g. "Invested" or ["Invested", "Diligence"]. */
|
|
180
|
+
function dealSearchStatus(flags) {
|
|
181
|
+
if (flags.status === undefined) return undefined;
|
|
182
|
+
if (flags.status === true) throw new Error("--status requires a pipeline status, e.g. Invested");
|
|
183
|
+
const values = (Array.isArray(flags.status) ? flags.status : String(flags.status).split(","))
|
|
184
|
+
.map((value) => String(value).trim())
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
if (!values.length) throw new Error("--status requires a pipeline status, e.g. Invested");
|
|
187
|
+
if (values.length > 10 || values.some((value) => value.length > 60)) {
|
|
188
|
+
throw new Error("--status accepts at most 10 statuses of at most 60 characters");
|
|
189
|
+
}
|
|
190
|
+
return [...new Set(values)];
|
|
191
|
+
}
|
|
192
|
+
|
|
177
193
|
function dealSearchSector(flags) {
|
|
178
194
|
if (flags.sector === undefined) return undefined;
|
|
179
195
|
if (typeof flags.sector !== "string" || !flags.sector.trim() || flags.sector.trim().length > 200) {
|
|
@@ -183,6 +199,10 @@ function dealSearchSector(flags) {
|
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
export function verifyDealSearchResult(result, flags = {}) {
|
|
202
|
+
const status = dealSearchStatus(flags);
|
|
203
|
+
if (status !== undefined && JSON.stringify(result?.search?.filters?.status ?? null) !== JSON.stringify(status)) {
|
|
204
|
+
throw new Error("Core did not confirm the requested status filter. Filtering by pipeline status requires Core API 5.42.0 or newer; this response was not accepted as filtered results.");
|
|
205
|
+
}
|
|
186
206
|
const sector = dealSearchSector(flags);
|
|
187
207
|
if (sector !== undefined && result?.search?.filters?.sector !== sector) {
|
|
188
208
|
throw new Error("Core did not confirm the requested sector filter. Upgrade the Llama Command server before retrying --sector; this response was not accepted as filtered results.");
|
|
@@ -190,7 +210,55 @@ export function verifyDealSearchResult(result, flags = {}) {
|
|
|
190
210
|
return result;
|
|
191
211
|
}
|
|
192
212
|
|
|
193
|
-
|
|
213
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
214
|
+
|
|
215
|
+
function listInput(value) {
|
|
216
|
+
if (value === undefined || value === null) return undefined;
|
|
217
|
+
const values = (Array.isArray(value) ? value : String(value).split(","))
|
|
218
|
+
.map((item) => String(item).trim())
|
|
219
|
+
.filter(Boolean);
|
|
220
|
+
return values.length ? [...new Set(values)] : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Targeted Information read: ids (full values), exact types, and/or literal
|
|
225
|
+
* text. Returns undefined when no filter was requested. Core API 5.42.0+.
|
|
226
|
+
*/
|
|
227
|
+
export function informationReadFilter({ ids, types, q } = {}) {
|
|
228
|
+
const filter = {};
|
|
229
|
+
const idList = listInput(ids);
|
|
230
|
+
const typeList = listInput(types);
|
|
231
|
+
if (idList) {
|
|
232
|
+
if (idList.length > 50) throw new Error("--information-id accepts at most 50 ids");
|
|
233
|
+
if (idList.some((id) => !UUID_RE.test(id))) throw new Error("--information-id must be Information record UUIDs");
|
|
234
|
+
filter.ids = idList.map((id) => id.toLowerCase());
|
|
235
|
+
}
|
|
236
|
+
if (typeList) {
|
|
237
|
+
if (typeList.length > 20) throw new Error("--information-type accepts at most 20 types");
|
|
238
|
+
if (typeList.some((type) => type.length > 120)) throw new Error("--information-type values must be at most 120 characters");
|
|
239
|
+
filter.types = typeList;
|
|
240
|
+
}
|
|
241
|
+
if (q !== undefined && q !== null) {
|
|
242
|
+
if (q === true) throw new Error("--information-q requires text");
|
|
243
|
+
const text = String(q).trim();
|
|
244
|
+
if (text.length < 2 || text.length > 200) throw new Error("--information-q must be 2-200 characters");
|
|
245
|
+
filter.q = text;
|
|
246
|
+
}
|
|
247
|
+
return Object.keys(filter).length ? filter : undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Reject an unfiltered response from a Core that predates targeted reads. */
|
|
251
|
+
export function verifyInformationReadResult(result, filter) {
|
|
252
|
+
if (!filter) return result;
|
|
253
|
+
const applied = result?.informationFilter;
|
|
254
|
+
const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
255
|
+
if (!applied || !same(applied.ids, filter.ids) || !same(applied.types, filter.types) || !same(applied.q, filter.q)) {
|
|
256
|
+
throw new Error("Core did not confirm the requested Information filter. Targeted Information reads require Core API 5.42.0 or newer; this response was not accepted as filtered results. Use --detail memory on an older server.");
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function buildDealReadPath(dealId, detail = "overview", filter) {
|
|
194
262
|
if (!dealId || dealId.startsWith("--")) {
|
|
195
263
|
throw new Error("Usage: llama deal read <dealId> [--detail overview|memory|files|conversation|history|all]");
|
|
196
264
|
}
|
|
@@ -203,7 +271,13 @@ export function buildDealReadPath(dealId, detail = "overview") {
|
|
|
203
271
|
all: "information,artifacts,chat,feed",
|
|
204
272
|
}[detail];
|
|
205
273
|
if (expansion === undefined) throw new Error(`Unknown detail: ${detail}`);
|
|
206
|
-
|
|
274
|
+
const params = new URLSearchParams();
|
|
275
|
+
if (expansion) params.set("expand", expansion);
|
|
276
|
+
if (filter?.ids) params.set("information_id", filter.ids.join(","));
|
|
277
|
+
if (filter?.types) params.set("information_type", filter.types.join(","));
|
|
278
|
+
if (filter?.q !== undefined) params.set("information_q", filter.q);
|
|
279
|
+
const query = params.toString().replace(/%2C/g, ",");
|
|
280
|
+
return `/api/occam/deals/${encodeURIComponent(dealId)}${query ? `?${query}` : ""}`;
|
|
207
281
|
}
|
|
208
282
|
|
|
209
283
|
/**
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
// The API-backed Llama tool catalog, independent of transport and of how the
|
|
2
|
+
// caller authenticates. The stdio server (bin/llama-mcp.mjs) passes the local
|
|
3
|
+
// HTTP client; a remote MCP endpoint hosted by Llama Command passes its own
|
|
4
|
+
// in-process request function. Nothing here may touch local credentials,
|
|
5
|
+
// local files, or process state: tools that need those stay in bin/.
|
|
6
|
+
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { buildArtifactReadPath, buildWikiReadPath } from "./content-read.mjs";
|
|
9
|
+
import {
|
|
10
|
+
buildDealReadPath,
|
|
11
|
+
buildDealSearchPath,
|
|
12
|
+
verifyDealSearchResult,
|
|
13
|
+
informationReadFilter,
|
|
14
|
+
verifyInformationReadResult,
|
|
15
|
+
compactDealWriteResult,
|
|
16
|
+
prepareDealCommand,
|
|
17
|
+
} from "./deal-actions.mjs";
|
|
18
|
+
import { buildDealMemoryPath } from "./deal-memory-actions.mjs";
|
|
19
|
+
import { buildPageSchemaPath, MAX_PAGE_SCHEMA_FIELDS } from "./page-schema.mjs";
|
|
20
|
+
|
|
21
|
+
export function textResult(text, isError = false) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text }],
|
|
24
|
+
...(isError ? { isError: true } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The manifest also carries a rendered duplicate of itself; tools return the structured half. */
|
|
29
|
+
export function structuredBootstrapManifest(manifest) {
|
|
30
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
31
|
+
return manifest;
|
|
32
|
+
}
|
|
33
|
+
const { briefing: _renderedDuplicate, ...structured } = manifest;
|
|
34
|
+
return structured;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const originSchema = z.object({
|
|
38
|
+
kind: z.enum(["user", "agent", "system"]),
|
|
39
|
+
originalUserUtterance: z.string().min(1).optional(),
|
|
40
|
+
originatingChatRecordId: z.string().uuid().optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const writeDealCommandSchema = z.discriminatedUnion("operation", [
|
|
44
|
+
z.object({
|
|
45
|
+
operation: z.literal("input.submit"),
|
|
46
|
+
dealId: z.string().uuid(),
|
|
47
|
+
format: z.string().min(1).max(120).optional(),
|
|
48
|
+
content: z.any(),
|
|
49
|
+
source: z.record(z.string(), z.any()).optional(),
|
|
50
|
+
origin: originSchema,
|
|
51
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
52
|
+
}).strict(),
|
|
53
|
+
z.object({
|
|
54
|
+
operation: z.literal("information.put"),
|
|
55
|
+
dealId: z.string().uuid(),
|
|
56
|
+
informationId: z.string().uuid().optional(),
|
|
57
|
+
type: z.string().min(1).max(120),
|
|
58
|
+
labels: z.array(z.string()).optional(),
|
|
59
|
+
subject: z.record(z.string(), z.any()).optional(),
|
|
60
|
+
value: z.any(),
|
|
61
|
+
expectedVersion: z.number().int().positive().optional(),
|
|
62
|
+
origin: originSchema,
|
|
63
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
64
|
+
}).strict(),
|
|
65
|
+
z.object({
|
|
66
|
+
operation: z.literal("page.patch"),
|
|
67
|
+
dealId: z.string().uuid(),
|
|
68
|
+
patch: z.record(z.string(), z.any()),
|
|
69
|
+
expectedRevision: z.number().int().nonnegative().optional(),
|
|
70
|
+
origin: originSchema,
|
|
71
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
72
|
+
}).strict(),
|
|
73
|
+
z.object({
|
|
74
|
+
operation: z.literal("artifact.put"),
|
|
75
|
+
dealId: z.string().uuid(),
|
|
76
|
+
artifactId: z.string().uuid().optional().describe("Reuse the existing ID to append a complete file version, including on rename. Omit for a new document without a title collision, or explicitly use a fresh UUID for a distinct same-name source."),
|
|
77
|
+
kind: z.string().min(1).max(120),
|
|
78
|
+
title: z.string().min(1).max(500),
|
|
79
|
+
mimeType: z.string().min(1).max(240),
|
|
80
|
+
contentBase64: z.string().min(1).optional(),
|
|
81
|
+
storageKey: z.string().min(1).optional(),
|
|
82
|
+
storageUrl: z.string().url().optional(),
|
|
83
|
+
byteSize: z.number().int().nonnegative().optional(),
|
|
84
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
85
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
86
|
+
origin: originSchema,
|
|
87
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
88
|
+
}).strict(),
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Register the API-backed tools on an MCP server.
|
|
93
|
+
*
|
|
94
|
+
* @param server an McpServer
|
|
95
|
+
* @param deps.request async (method, path, body) => parsed JSON; throws on failure
|
|
96
|
+
* @param deps.formatError (error) => string shown to the agent
|
|
97
|
+
* @param deps.version client version reported to bootstrap
|
|
98
|
+
* @param deps.only optional list of tool names; others are not registered
|
|
99
|
+
*/
|
|
100
|
+
export function registerApiTools(server, { request, formatError, version, only } = {}) {
|
|
101
|
+
if (typeof request !== "function") throw new Error("registerApiTools requires a request function");
|
|
102
|
+
const describeError = formatError ?? ((error) => `Error: ${error?.message ?? String(error)}`);
|
|
103
|
+
const formatErrorForDisplay = describeError;
|
|
104
|
+
const allowed = only ? new Set(only) : null;
|
|
105
|
+
const registerTool = (name, ...rest) => {
|
|
106
|
+
if (allowed && !allowed.has(name)) return;
|
|
107
|
+
server.registerTool(name, ...rest);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
async function callApi(method, path, body, transform = (value) => value) {
|
|
111
|
+
try {
|
|
112
|
+
const result = transform(await request(method, path, body));
|
|
113
|
+
return textResult(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return textResult(describeError(error), true);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// Deal is deliberately a closed four-tool surface. Do not add split resource
|
|
121
|
+
// tools here: one more tool is one more branch every agent must reason about.
|
|
122
|
+
registerTool(
|
|
123
|
+
"search_deals",
|
|
124
|
+
{
|
|
125
|
+
description: "Search current Deal metadata by company name/key, description, sector/subsector or founder name in either stored language. Use short keywords; matching is a case-insensitive literal substring, not semantic search. Combine q with sector/status/state/limit, or use sector alone for category discovery. status filters by exact pipeline status (e.g. [\"Invested\"]); to list every invested Deal use status [\"Invested\"] with limit 200 and order by investmentDate/markedInvestedAt, instead of guessing from names. Sector filters before limit and requires a supporting Core server. Exact names rank first. Inspect searchMatch fields and search.has_more; truncated results are not exhaustive. On zero matches, try broader or alternative wording before concluding no Deal exists. Read candidates to verify specific categories and claims. Always search before creating a company.",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
q: z.string().optional(),
|
|
128
|
+
sector: z.string().trim().min(1).max(200).optional(),
|
|
129
|
+
status: z.array(z.string().min(1).max(60)).min(1).max(10).optional(),
|
|
130
|
+
state: z.enum(["active", "archived", "trashed"]).optional(),
|
|
131
|
+
limit: z.number().int().min(1).max(2000).optional(),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
// @core-api-operation GET /api/occam/deals
|
|
135
|
+
async ({ q, sector, status, state, limit } = {}) => callApi(
|
|
136
|
+
"GET", buildDealSearchPath(q, { sector, status, state, limit }), undefined,
|
|
137
|
+
(result) => verifyDealSearchResult(result, { sector, status }),
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
registerTool(
|
|
142
|
+
"read_deal",
|
|
143
|
+
{
|
|
144
|
+
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Read Information selectively with informationIds (full values, up to 50), informationTypes, and/or informationQuery (literal text; returns match.snippets, and values over 8,000 characters as { omitted, chars }: then read that id). Prefer these over detail=memory, which returns up to ~2 MB. Use artifactId to read original source text with page/paragraph anchors. Continue with nextOffset and source.sha256. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
dealId: z.string().min(1),
|
|
147
|
+
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
148
|
+
artifactId: z.string().uuid().optional(),
|
|
149
|
+
version: z.number().int().min(1).optional(),
|
|
150
|
+
offset: z.number().int().min(0).optional(),
|
|
151
|
+
limit: z.number().int().min(1).max(50000).optional(),
|
|
152
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
153
|
+
informationIds: z.array(z.string().uuid()).min(1).max(50).optional(),
|
|
154
|
+
informationTypes: z.array(z.string().min(1).max(120)).min(1).max(20).optional(),
|
|
155
|
+
informationQuery: z.string().min(2).max(200).optional(),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
async ({ dealId, detail, artifactId, informationIds, informationTypes, informationQuery, ...options }) => {
|
|
159
|
+
const filter = informationReadFilter({ ids: informationIds, types: informationTypes, q: informationQuery });
|
|
160
|
+
if (artifactId) {
|
|
161
|
+
if (detail) throw new Error("Use artifactId or detail, not both");
|
|
162
|
+
if (filter) throw new Error("Use artifactId or information filters, not both");
|
|
163
|
+
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
164
|
+
return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
|
|
165
|
+
}
|
|
166
|
+
if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
|
|
167
|
+
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
168
|
+
return callApi("GET", buildDealReadPath(dealId, detail || "overview", filter), undefined,
|
|
169
|
+
(result) => verifyInformationReadResult(result, filter));
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
registerTool(
|
|
174
|
+
"create_deal",
|
|
175
|
+
{
|
|
176
|
+
description: "Create one Deal intent. Core owns Drive, initial resources, idempotency, and Events.",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
companyName: z.string().min(1).max(240),
|
|
179
|
+
companyKey: z.string().min(1).max(240).optional(),
|
|
180
|
+
page: z.record(z.string(), z.any()).optional(),
|
|
181
|
+
information: z.array(z.record(z.string(), z.any())).optional(),
|
|
182
|
+
origin: originSchema,
|
|
183
|
+
idempotencyKey: z.string().min(1).max(240).optional(),
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
async (input) => callApi("POST", "/api/occam/deals/commands", prepareDealCommand("create", input)),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
registerTool(
|
|
190
|
+
"write_deal",
|
|
191
|
+
{
|
|
192
|
+
description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. A file or HTML about one company belongs here as artifact.put; standalone knowledge not tied to one company belongs in wiki_save. Before artifact.put, read Deal files: reuse artifactId for a revision; a same-kind/title collision without an ID returns OCCAM_CONFLICT. Titles are candidates, not identity. Select the intended existing ID from the request/content, or explicitly generate a fresh UUID for a distinct same-name source. Send complete revised bytes, preserve applicable metadata, and verify the ID/version. Core hosts text/html in Command; do not mirror HTML to Drive or require a Drive link. Non-HTML source files, including original transcripts and recordings, go to Drive through artifact.put. Keep source-file bodies out of Input, Information, Page and metadata; save extracted facts and summaries with source references and preserve the user request. page.patch does not edit file bytes. All Page content fields are Agent-writable; author controls attribution, not permission. Before page.patch, use get_live_deal_page_schema for the exact fields being changed and write bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots. A Llama user's own judgment is human_subjective_view.people or human_subjective_view.business and must carry value.speaker, value.rawText quoted verbatim from origin.originalUserUtterance, and value.summary (the agent's restatement); it is rejected otherwise.",
|
|
193
|
+
// MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
|
|
194
|
+
// command preserves the real discriminated union instead of flattening four
|
|
195
|
+
// incompatible operations into false affordances.
|
|
196
|
+
inputSchema: { command: writeDealCommandSchema },
|
|
197
|
+
},
|
|
198
|
+
async ({ command }) => callApi(
|
|
199
|
+
"POST",
|
|
200
|
+
"/api/occam/deals/commands",
|
|
201
|
+
prepareDealCommand("write", command),
|
|
202
|
+
(result) => compactDealWriteResult(command, result),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Deal Memory is a separate sidecar domain. These do not add an Occam Deal
|
|
207
|
+
// business resource or widen the deliberately closed four-tool Deal surface.
|
|
208
|
+
registerTool(
|
|
209
|
+
"get_deal_memory",
|
|
210
|
+
{
|
|
211
|
+
description: "Read the complete canonical Markdown Deal Story and its opaque concurrency version. A returned placeholder is an existing Story whose version is required for a write; only 404 means no Story exists.",
|
|
212
|
+
inputSchema: { dealId: z.string().uuid() },
|
|
213
|
+
},
|
|
214
|
+
// @core-api-operation GET /api/deal-memory/{dealId}/story
|
|
215
|
+
async ({ dealId }) => callApi("GET", buildDealMemoryPath(dealId)),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
registerTool(
|
|
219
|
+
"update_deal_memory",
|
|
220
|
+
{
|
|
221
|
+
description: "Replace the complete Markdown Deal Story with one coherent current understanding; never append an update log, restate Live Deal Page/Deal Information fields, or write when understanding would not materially improve. Frontmatter must contain matching deal_id plus stable uuid and created timestamps; updated must strictly advance. Pass the version from get_deal_memory for every returned Story, including a placeholder; only 404 permits creation without expected_version.",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
dealId: z.string().uuid(),
|
|
224
|
+
markdown: z.string().min(1).max(1_048_576),
|
|
225
|
+
expected_version: z.string().min(1).optional(),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
// @core-api-operation PUT /api/deal-memory/{dealId}/story
|
|
229
|
+
async ({ dealId, markdown, expected_version }) => callApi(
|
|
230
|
+
"PUT",
|
|
231
|
+
buildDealMemoryPath(dealId),
|
|
232
|
+
{ markdown, ...(expected_version ? { expected_version } : {}) },
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
registerTool(
|
|
237
|
+
"agent_bootstrap",
|
|
238
|
+
{
|
|
239
|
+
description: "Mandatory before Llama work: fetch the authenticated two-part private Brain (Investment Framework V3 + Llama Command operating skill), a compact Live Deal Page schema index, runtime contract including Wiki publishing guidance, and visible Llama OS skills. Read exact Page fields separately with get_live_deal_page_schema.",
|
|
240
|
+
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
241
|
+
},
|
|
242
|
+
async ({ limit } = {}) => {
|
|
243
|
+
const params = new URLSearchParams({ clientVersion: version });
|
|
244
|
+
params.set("page_schema", "progressive");
|
|
245
|
+
if (limit) params.set("limit", String(limit));
|
|
246
|
+
try {
|
|
247
|
+
const result = await request("GET", `/api/agent/manifest?${params}`);
|
|
248
|
+
return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
registerTool(
|
|
256
|
+
"get_live_deal_page_schema",
|
|
257
|
+
{
|
|
258
|
+
description: "Read the Live Deal Page schema progressively. Call with no selector for the compact index; before page.patch, request only exact fields or one section. This is read-only context, not a fifth Deal action.",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
fields: z.array(z.string().min(1)).min(1).max(MAX_PAGE_SCHEMA_FIELDS).optional(),
|
|
261
|
+
section: z.string().min(1).optional(),
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
async ({ fields, section } = {}) => {
|
|
265
|
+
try {
|
|
266
|
+
// @core-api-operation GET /api/agent/page-schema
|
|
267
|
+
return await callApi("GET", buildPageSchemaPath({ fields, section }));
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return textResult(formatErrorForDisplay(error), true);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
registerTool(
|
|
275
|
+
"skills_search",
|
|
276
|
+
{
|
|
277
|
+
description: "Search authenticated runtime skills; read only the relevant result.",
|
|
278
|
+
inputSchema: { q: z.string().min(1), limit: z.number().int().min(1).max(100).optional() },
|
|
279
|
+
},
|
|
280
|
+
async ({ q, limit }) => {
|
|
281
|
+
const params = new URLSearchParams({ q });
|
|
282
|
+
if (limit) params.set("limit", String(limit));
|
|
283
|
+
return callApi("GET", `/api/agent/skills?${params}`);
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
registerTool(
|
|
288
|
+
"skills_read",
|
|
289
|
+
{
|
|
290
|
+
description: "Read one authenticated runtime skill by slug.",
|
|
291
|
+
inputSchema: { slug: z.string().min(1) },
|
|
292
|
+
},
|
|
293
|
+
async ({ slug }) => callApi("GET", `/api/agent/skills/${encodeURIComponent(slug)}`),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
registerTool(
|
|
297
|
+
"pref_list",
|
|
298
|
+
{
|
|
299
|
+
description: "List standing agent preferences.",
|
|
300
|
+
inputSchema: { status: z.enum(["active", "proposed", "retired", "all"]).optional() },
|
|
301
|
+
},
|
|
302
|
+
async ({ status } = {}) => {
|
|
303
|
+
const params = new URLSearchParams();
|
|
304
|
+
if (status) params.set("status", status);
|
|
305
|
+
return callApi("GET", `/api/agent/preferences${params.size ? `?${params}` : ""}`);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
registerTool(
|
|
310
|
+
"pref_add",
|
|
311
|
+
{
|
|
312
|
+
description: "Save a durable user or team preference; procedures belong in skills.",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
key: z.string().min(1),
|
|
315
|
+
content: z.string().min(1).max(280),
|
|
316
|
+
scope: z.enum(["user", "team"]).optional(),
|
|
317
|
+
evidence: z.string().optional(),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
async ({ key, content, scope, evidence }) =>
|
|
321
|
+
callApi("POST", "/api/agent/preferences", { key, content, scope, evidence }),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
registerTool(
|
|
325
|
+
"pref_set_status",
|
|
326
|
+
{
|
|
327
|
+
description: "Activate or retire a standing preference.",
|
|
328
|
+
inputSchema: { id: z.number().int().positive(), status: z.enum(["active", "retired"]) },
|
|
329
|
+
},
|
|
330
|
+
async ({ id, status }) => callApi("PATCH", `/api/agent/preferences/${id}`, { status }),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
registerTool(
|
|
334
|
+
"object_inspect",
|
|
335
|
+
{
|
|
336
|
+
description: "Explain a Command URL or object before guessing that it is broken.",
|
|
337
|
+
inputSchema: {
|
|
338
|
+
q: z.string().optional(),
|
|
339
|
+
type: z.string().optional(),
|
|
340
|
+
id: z.string().optional(),
|
|
341
|
+
lang: z.enum(["en", "zh"]).optional(),
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
async ({ q, type, id, lang } = {}) => {
|
|
345
|
+
const params = new URLSearchParams();
|
|
346
|
+
if (q) params.set("q", q);
|
|
347
|
+
if (type) params.set("type", type);
|
|
348
|
+
if (id) params.set("id", id);
|
|
349
|
+
if (lang) params.set("lang", lang);
|
|
350
|
+
return callApi("GET", `/api/agent/explain?${params}`);
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
registerTool(
|
|
355
|
+
"wiki_search",
|
|
356
|
+
{
|
|
357
|
+
description: "Search institutional knowledge in the internal Wiki.",
|
|
358
|
+
inputSchema: { q: z.string().min(1) },
|
|
359
|
+
},
|
|
360
|
+
async ({ q }) => callApi("GET", `/api/wiki/search?q=${encodeURIComponent(q)}`),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
registerTool(
|
|
364
|
+
"wiki_read",
|
|
365
|
+
{
|
|
366
|
+
description: "Read one Wiki article. format=text extracts its original upload or page body. attachment follows an ID listed in attachments. Continuations require offset and sha256.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
|
|
369
|
+
format: z.literal("text").optional(), attachment: z.string().optional(),
|
|
370
|
+
offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
|
|
371
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
// @core-api-operation GET /api/wiki/{slug}
|
|
375
|
+
async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
registerTool(
|
|
379
|
+
"wiki_save",
|
|
380
|
+
{
|
|
381
|
+
description: "Publish institutional knowledge when authorized. For knowledge not tied to one company; a file or HTML about one company is a Deal Artifact (write_deal artifact.put), unless the user names the Wiki. Follow agent_bootstrap contract.wiki_publishing. New pages default to complete self-contained HTML with embedded rendering assets and full report content/interactions; set content_type=html. Honor explicit Markdown requests. Read the same slug/lang before edits and preserve its format unless conversion is authorized. Verify the receipt, canonical page and interactions before reporting completion.",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
slug: z.string().min(1),
|
|
384
|
+
title: z.string().min(1),
|
|
385
|
+
content: z.string().describe("Complete page source. For HTML, include the full document and embedded rendering dependencies."),
|
|
386
|
+
sources: z.array(z.string()).min(1),
|
|
387
|
+
content_type: z.enum(["markdown", "html"]).optional().describe("Set html for HTML publications. Use markdown for an explicit Markdown request or an existing Markdown article."),
|
|
388
|
+
lang: z.enum(["en", "zh"]).optional().describe("Article language; Core defaults to en when omitted."),
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
async ({ slug, title, content, sources, content_type, lang }) =>
|
|
392
|
+
callApi("POST", "/api/wiki/save", { slug, title, content, sources, content_type, lang }),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
registerTool(
|
|
396
|
+
"wiki_delete",
|
|
397
|
+
{
|
|
398
|
+
description: "Soft-delete a Wiki article.",
|
|
399
|
+
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
400
|
+
},
|
|
401
|
+
async ({ slug, lang }) =>
|
|
402
|
+
callApi("DELETE", `/api/wiki/${encodeURIComponent(slug)}?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
registerTool(
|
|
406
|
+
"wiki_restore",
|
|
407
|
+
{
|
|
408
|
+
description: "Restore a soft-deleted Wiki article.",
|
|
409
|
+
inputSchema: { slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional() },
|
|
410
|
+
},
|
|
411
|
+
async ({ slug, lang }) =>
|
|
412
|
+
callApi("POST", `/api/wiki/${encodeURIComponent(slug)}/restore?lang=${lang === "zh" ? "zh" : "en"}`),
|
|
413
|
+
);
|
|
414
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "npm run test:auth && npm run test:agent-routing && npm run test:contract",
|
|
8
8
|
"test:agent-routing": "node scripts/verify-agent-routing.mjs",
|
|
9
|
-
"test:contract": "node --test scripts/feedback.test.mjs scripts/content-read.test.mjs scripts/build-manifest.test.mjs scripts/core-api-call-sites.test.mjs scripts/deal-actions-cli.test.mjs scripts/deal-actions.test.mjs scripts/deal-memory-actions-cli.test.mjs scripts/deal-memory-actions.test.mjs scripts/mcp-four-action-surface.test.mjs scripts/page-schema-cli.test.mjs scripts/page-schema.test.mjs scripts/server-compatibility.test.mjs scripts/verify-core-api-contract.test.mjs && node scripts/verify-core-api-contract.mjs",
|
|
9
|
+
"test:contract": "node --test scripts/wiki-publishing.test.mjs scripts/feedback.test.mjs scripts/content-read.test.mjs scripts/build-manifest.test.mjs scripts/core-api-call-sites.test.mjs scripts/deal-actions-cli.test.mjs scripts/deal-actions.test.mjs scripts/deal-memory-actions-cli.test.mjs scripts/deal-memory-actions.test.mjs scripts/mcp-four-action-surface.test.mjs scripts/page-schema-cli.test.mjs scripts/page-schema.test.mjs scripts/server-compatibility.test.mjs scripts/verify-core-api-contract.test.mjs && node scripts/verify-core-api-contract.mjs",
|
|
10
10
|
"verify:artifact": "node scripts/verify-release-artifact.mjs",
|
|
11
11
|
"verify:release": "npm test && npm run verify:artifact && node scripts/verify-tarball-clean.mjs",
|
|
12
12
|
"prepack": "node scripts/prepare-build-manifest.mjs",
|