@llamaventures/cli 2.3.0 → 2.4.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/CHANGELOG.md +8 -0
- package/README.md +14 -0
- package/README.zh-CN.md +11 -0
- package/bin/llama-mcp.mjs +26 -7
- package/bin/llama.mjs +24 -4
- package/contracts/core-api.json +2 -2
- package/contracts/required-operations.json +1 -0
- package/lib/build-manifest.json +4 -4
- package/lib/client.mjs +1 -0
- package/lib/content-read.mjs +49 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.4.0] - 2026-09-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Read Deal artifact text and original bytes through Core with version/hash provenance.
|
|
14
|
+
- Read Wiki uploads and supported page references; paginate text with a pinned source hash.
|
|
15
|
+
- Verify source downloads and refuse to overwrite local files.
|
|
16
|
+
|
|
9
17
|
## [2.3.0] - 2026-09-04
|
|
10
18
|
|
|
11
19
|
### Changed
|
package/README.md
CHANGED
|
@@ -214,3 +214,17 @@ npm run verify:release
|
|
|
214
214
|
|
|
215
215
|
Release artifacts are source-SHA certified. Publishing to npm and changing the
|
|
216
216
|
production server's minimum version are separate, explicit release operations.
|
|
217
|
+
|
|
218
|
+
### Read original source content
|
|
219
|
+
|
|
220
|
+
Use `llama deal read <dealId> --artifact <artifactId>` to read a listed file's text,
|
|
221
|
+
source hash and page/paragraph anchors. Use `llama wiki read <slug> --format text`
|
|
222
|
+
for a Wiki page or its original uploaded document. `--attachment <referenceId>`
|
|
223
|
+
follows a supported reference listed by the Wiki read. Long reads return
|
|
224
|
+
`nextOffset`; continue with `--offset N --sha256 HASH`. `--output <file>` downloads
|
|
225
|
+
and hash-verifies the original bytes without overwriting a local file.
|
|
226
|
+
|
|
227
|
+
These options require a Core version with source-content reads. Scanned PDFs
|
|
228
|
+
may require OCR; no-text, missing files, unsupported formats and denied storage
|
|
229
|
+
are explicit outcomes, not evidence that a memo has been read. MCP exposes the
|
|
230
|
+
same text reads inside `read_deal` and `wiki_read`.
|
package/README.zh-CN.md
CHANGED
|
@@ -204,3 +204,14 @@ npm run verify:release
|
|
|
204
204
|
|
|
205
205
|
发布包会绑定准确 source SHA。npm publish 与生产服务端强制最低版本是两个
|
|
206
206
|
独立、需要明确授权的 release 动作。
|
|
207
|
+
|
|
208
|
+
### 读取原始材料
|
|
209
|
+
|
|
210
|
+
`llama deal read <dealId> --artifact <artifactId>` 读取文件正文、原文件哈希与页码/段落位置。
|
|
211
|
+
`llama wiki read <slug> --format text` 读取 Wiki 正文或上传的原文件;
|
|
212
|
+
`--attachment <referenceId>` 读取页面返回的受支持附件引用。
|
|
213
|
+
长文用 `--offset <nextOffset> --sha256 <source.sha256>` 继续,文件变化时会拒绝拼接。
|
|
214
|
+
`--output <文件路径>` 下载并核验原始字节,不覆盖已有文件。
|
|
215
|
+
|
|
216
|
+
需要服务端支持正文读取。扫描 PDF 不自动 OCR;无文字、缺失文件、不支持的格式、
|
|
217
|
+
权限不足都会明确报告。MCP 在原有 `read_deal` 和 `wiki_read` 中提供相同正文读取能力。
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { buildArtifactReadPath, buildWikiReadPath } from "../lib/content-read.mjs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -123,14 +124,27 @@ server.registerTool(
|
|
|
123
124
|
server.registerTool(
|
|
124
125
|
"read_deal",
|
|
125
126
|
{
|
|
126
|
-
description: "Read one Deal. Live Page is current human-visible state; Information is memory and never updates Page automatically. Page field semantics come from agent_bootstrap, not every Deal read.",
|
|
127
|
+
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.",
|
|
127
128
|
inputSchema: {
|
|
128
129
|
dealId: z.string().min(1),
|
|
129
130
|
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
131
|
+
artifactId: z.string().uuid().optional(),
|
|
132
|
+
version: z.number().int().min(1).optional(),
|
|
133
|
+
offset: z.number().int().min(0).optional(),
|
|
134
|
+
limit: z.number().int().min(1).max(50000).optional(),
|
|
135
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
130
136
|
},
|
|
131
137
|
},
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
async ({ dealId, detail, artifactId, ...options }) => {
|
|
139
|
+
if (artifactId) {
|
|
140
|
+
if (detail) throw new Error("Use artifactId or detail, not both");
|
|
141
|
+
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
142
|
+
return callApi("GET", buildArtifactReadPath(dealId, artifactId, options));
|
|
143
|
+
}
|
|
144
|
+
if (Object.values(options).some(value => value !== undefined)) throw new Error("Content options require artifactId");
|
|
145
|
+
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
146
|
+
return callApi("GET", buildDealReadPath(dealId, detail || "overview"));
|
|
147
|
+
},
|
|
134
148
|
);
|
|
135
149
|
|
|
136
150
|
server.registerTool(
|
|
@@ -344,11 +358,16 @@ server.registerTool(
|
|
|
344
358
|
server.registerTool(
|
|
345
359
|
"wiki_read",
|
|
346
360
|
{
|
|
347
|
-
description: "Read one Wiki article
|
|
348
|
-
inputSchema: {
|
|
361
|
+
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.",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
slug: z.string().min(1), lang: z.enum(["en", "zh"]).optional(),
|
|
364
|
+
format: z.literal("text").optional(), attachment: z.string().optional(),
|
|
365
|
+
offset: z.number().int().min(0).optional(), limit: z.number().int().min(1).max(50000).optional(),
|
|
366
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
367
|
+
},
|
|
349
368
|
},
|
|
350
|
-
|
|
351
|
-
|
|
369
|
+
// @core-api-operation GET /api/wiki/{slug}
|
|
370
|
+
async ({ slug, ...options }) => callApi("GET", buildWikiReadPath(slug, options)),
|
|
352
371
|
);
|
|
353
372
|
|
|
354
373
|
server.registerTool(
|
package/bin/llama.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { buildArtifactReadPath, buildWikiReadPath, saveContentSource } from "../lib/content-read.mjs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { readFile } from "node:fs/promises";
|
|
5
6
|
import readline from "node:readline";
|
|
@@ -70,6 +71,8 @@ Separate preserved domains:
|
|
|
70
71
|
llama skills list|search|show
|
|
71
72
|
llama pref list|add|approve|retire
|
|
72
73
|
llama explain <url-or-object>
|
|
74
|
+
llama deal read <dealId> --artifact <artifactId> [--version N] [--offset N --sha256 HASH] [--limit N] [--output <file>]
|
|
75
|
+
llama wiki read <slug> --format text [--attachment <referenceId>] [--offset N --sha256 HASH] [--output <file>]
|
|
73
76
|
llama wiki search|read|save|delete|restore
|
|
74
77
|
llama admin auth-events|deal-events|agent-events
|
|
75
78
|
llama pitch start|say|upload|status|finalize|end
|
|
@@ -84,6 +87,15 @@ Exactly four actions:
|
|
|
84
87
|
llama deal create --json <file|->
|
|
85
88
|
llama deal write --json <file|->
|
|
86
89
|
|
|
90
|
+
Read original source content:
|
|
91
|
+
llama deal read <dealId> --artifact <artifactId> [--version N] [--offset N --sha256 HASH] [--limit N] [--output <file>]
|
|
92
|
+
llama wiki read <slug> --format text [--attachment <referenceId>] [--offset N --sha256 HASH] [--output <file>]
|
|
93
|
+
|
|
94
|
+
Artifact reads return source hashes, text and page/paragraph anchors. Continue with
|
|
95
|
+
--offset <nextOffset> --sha256 <source.sha256>. --output saves verified original
|
|
96
|
+
bytes without overwriting existing files. Wiki --format text reads the original
|
|
97
|
+
upload when present; --attachment follows a reference listed by the page.
|
|
98
|
+
|
|
87
99
|
Before any Page write:
|
|
88
100
|
llama agent bootstrap
|
|
89
101
|
llama page-schema read <field> [field...]
|
|
@@ -461,7 +473,7 @@ async function handleWiki(action, rest) {
|
|
|
461
473
|
if (action === "read") {
|
|
462
474
|
if (!slug) throw new Error("Usage: llama wiki read <slug> [--lang en|zh]");
|
|
463
475
|
// @core-api-operation GET /api/wiki/{slug}
|
|
464
|
-
print(await request("GET",
|
|
476
|
+
print(await saveContentSource(await request("GET", buildWikiReadPath(slug, flags)), flags.output));
|
|
465
477
|
return;
|
|
466
478
|
}
|
|
467
479
|
if (action === "save") {
|
|
@@ -657,9 +669,17 @@ async function main() {
|
|
|
657
669
|
}
|
|
658
670
|
if (area === "deal" && action === "read") {
|
|
659
671
|
const dealId = rest[0];
|
|
660
|
-
const { flags } = parseFlags(rest.slice(1), ["detail"]);
|
|
661
|
-
|
|
662
|
-
|
|
672
|
+
const { flags } = parseFlags(rest.slice(1), ["detail", "artifact", "version", "format", "offset", "limit", "sha256", "output"]);
|
|
673
|
+
if (flags.artifact !== undefined) {
|
|
674
|
+
if (flags.detail !== undefined) throw new Error("Use --artifact or --detail, not both");
|
|
675
|
+
// @core-api-operation GET /api/occam/deals/{dealId}/artifacts/{artifactId}
|
|
676
|
+
const result = await request("GET", buildArtifactReadPath(dealId, flags.artifact, flags));
|
|
677
|
+
print(await saveContentSource(result, flags.output));
|
|
678
|
+
} else {
|
|
679
|
+
if (["version", "format", "offset", "limit", "sha256", "output"].some(key => flags[key] !== undefined)) throw new Error("Content options require --artifact");
|
|
680
|
+
// @core-api-operation GET /api/occam/deals/{dealId}
|
|
681
|
+
print(await request("GET", buildDealReadPath(dealId, flags.detail === true ? "overview" : flags.detail || "overview")));
|
|
682
|
+
}
|
|
663
683
|
return;
|
|
664
684
|
}
|
|
665
685
|
if (area === "deal" && ["create", "write"].includes(action)) {
|
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.8.0",
|
|
5
5
|
"openapiVersion": "3.0.3",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "ec68e92362c9f60eee94b81d73fb7cff2868c80d7c17c24a9e3122cc2743e7ad",
|
|
7
7
|
"pathCount": 113,
|
|
8
8
|
"operationCount": 140
|
|
9
9
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
{ "method": "GET", "path": "/api/oauth/authorize" },
|
|
19
19
|
{ "method": "GET", "path": "/api/occam/deals" },
|
|
20
20
|
{ "method": "GET", "path": "/api/occam/deals/{dealId}" },
|
|
21
|
+
{ "method": "GET", "path": "/api/occam/deals/{dealId}/artifacts/{artifactId}" },
|
|
21
22
|
{ "method": "GET", "path": "/api/wiki/{slug}" },
|
|
22
23
|
{ "method": "GET", "path": "/api/wiki/search" },
|
|
23
24
|
{ "method": "PATCH", "path": "/api/agent/preferences/{id}" },
|
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.4.0",
|
|
5
|
+
"sourceSha": "5f2e08c216a111bc568e7dee925aa198674c6255",
|
|
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.8.0",
|
|
12
12
|
"openapiVersion": "3.0.3",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "ec68e92362c9f60eee94b81d73fb7cff2868c80d7c17c24a9e3122cc2743e7ad"
|
|
14
14
|
}
|
|
15
15
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -291,6 +291,7 @@ function inferCommand(method, endpoint) {
|
|
|
291
291
|
if (pathname === "/api/wiki/search") return "wiki.search";
|
|
292
292
|
if (pathname === "/api/wiki/save") return "wiki.save";
|
|
293
293
|
if (/^\/api\/wiki\/[^/]+$/.test(pathname)) return verb === "GET" ? "wiki.read" : "wiki.write";
|
|
294
|
+
if (/^\/api\/occam\/deals\/[^/]+\/artifacts\/[^/]+$/.test(pathname)) return "deal.read";
|
|
294
295
|
if (pathname === "/api/occam/deals") return "deal.search";
|
|
295
296
|
if (pathname === "/api/occam/deals/commands") return "deal.write";
|
|
296
297
|
if (/^\/api\/occam\/deals\/[^/]+$/.test(pathname)) return "deal.read";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
function parameters(options) {
|
|
5
|
+
const params = new URLSearchParams();
|
|
6
|
+
const format = options.output ? "source" : options.format || "text";
|
|
7
|
+
if (!["text", "source"].includes(format)) throw new Error("--format must be text or source");
|
|
8
|
+
params.set("format", format);
|
|
9
|
+
for (const [key, min, max] of [["version", 1, Number.MAX_SAFE_INTEGER], ["offset", 0, 2000000], ["limit", 1, 50000]]) {
|
|
10
|
+
if (options[key] === undefined) continue;
|
|
11
|
+
if (!/^\d+$/.test(String(options[key])) || !Number.isSafeInteger(Number(options[key])) || Number(options[key]) < min || Number(options[key]) > max) throw new Error(`Invalid --${key}`);
|
|
12
|
+
params.set(key, String(options[key]));
|
|
13
|
+
}
|
|
14
|
+
if (options.sha256 !== undefined) {
|
|
15
|
+
if (!/^[a-f0-9]{64}$/.test(options.sha256)) throw new Error("Invalid --sha256");
|
|
16
|
+
params.set("sha256", options.sha256);
|
|
17
|
+
}
|
|
18
|
+
if (Number(options.offset) > 0 && !options.sha256) throw new Error("Continuation reads require --sha256 from the first response");
|
|
19
|
+
if (options.output !== undefined && (typeof options.output !== "string" || !options.output)) throw new Error("--output requires a file path");
|
|
20
|
+
return params;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildArtifactReadPath(dealId, artifactId, options = {}) {
|
|
24
|
+
if (typeof dealId !== "string" || !dealId || typeof artifactId !== "string" || !artifactId) throw new Error("Usage: llama deal read <dealId> --artifact <artifactId>");
|
|
25
|
+
return `/api/occam/deals/${encodeURIComponent(dealId)}/artifacts/${encodeURIComponent(artifactId)}?${parameters(options)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildWikiReadPath(slug, options = {}) {
|
|
29
|
+
if (typeof slug !== "string" || !slug) throw new Error("Usage: llama wiki read <slug>");
|
|
30
|
+
const params = options.format || options.output || options.attachment ? parameters(options) : new URLSearchParams();
|
|
31
|
+
params.set("lang", options.lang === "zh" ? "zh" : "en");
|
|
32
|
+
if (options.attachment !== undefined) {
|
|
33
|
+
if (typeof options.attachment !== "string" || !options.attachment) throw new Error("--attachment requires a reference ID from the wiki read");
|
|
34
|
+
params.set("attachment", options.attachment);
|
|
35
|
+
}
|
|
36
|
+
if (options.version !== undefined) throw new Error("Wiki reads pin --sha256, not --version");
|
|
37
|
+
if (!options.format && !options.output && !options.attachment && [options.offset, options.limit, options.sha256].some(x => x !== undefined)) throw new Error("Use --format text with pagination options");
|
|
38
|
+
return `/api/wiki/${encodeURIComponent(slug)}?${params}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function saveContentSource(result, output) {
|
|
42
|
+
if (!output) return result;
|
|
43
|
+
if (result?.format !== "source" || typeof result.contentBase64 !== "string") throw new Error("Server did not return original file bytes");
|
|
44
|
+
const bytes = Buffer.from(result.contentBase64, "base64");
|
|
45
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
46
|
+
if (sha256 !== result.source?.sha256 || bytes.length !== result.source?.byteSize) throw new Error("Downloaded file hash or size does not match the source");
|
|
47
|
+
await writeFile(output, bytes, { flag: "wx", mode: 0o600 });
|
|
48
|
+
return { saved: output, source: result.source, verified: true };
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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:agent-routing && npm run test:contract",
|
|
8
8
|
"test:agent-routing": "node scripts/verify-agent-routing.mjs",
|
|
9
|
-
"test:contract": "node --test 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/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",
|