@llamaventures/cli 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT_BRIEFING.md CHANGED
@@ -4,6 +4,28 @@ This package is the authenticated tool boundary for Llama Command. For Deal
4
4
  work, the interface is intentionally small: search, read, create, and write.
5
5
  Do not bypass it with SQL, Google APIs, or retired HTTP routes.
6
6
 
7
+ ## Mandatory live bootstrap
8
+
9
+ Before any Llama workflow, run:
10
+
11
+ ```bash
12
+ llama agent bootstrap
13
+ ```
14
+
15
+ The authenticated response contains the current private Llama Brain with
16
+ exactly two components: the approved Investment Framework V3 Markdown (how to
17
+ think) and the Llama Command operating skill (how to work). Beside Brain it
18
+ contains the separate current Live Deal Page field contract (what every visible
19
+ slot means and how to verify a good write). The public CLI intentionally bundles
20
+ none of this private content; it only fetches it after authentication.
21
+
22
+ CLI is the Agent's tool, not Brain. Do not substitute a private skill wrapper
23
+ for either canonical Brain component.
24
+
25
+ Do not assume the Page is only `description` plus `notes`, and do not infer its
26
+ schema from a previous session. If bootstrap fails, repair authentication or
27
+ upgrade the CLI before writing; do not guess the fields.
28
+
7
29
  ## The Deal model
8
30
 
9
31
  Each company has five business resources:
@@ -17,6 +39,21 @@ Each company has five business resources:
17
39
  Archive, trash, status, founder, opinion, fact, memo section, and artifact kind
18
40
  are data or labels inside these resources. They are not separate tools.
19
41
 
42
+ Information and Page are deliberately different. Information is traceable
43
+ working memory; it never becomes Page automatically. Page is the bounded,
44
+ human-visible current projection, so patch the exact semantic fields described
45
+ by the live contract instead of hiding structured analysis in `notes`.
46
+
47
+ Every Page content field is Agent-writable. A field's `author` or
48
+ `semanticOwner` describes attribution, not permission. Preserve a real human
49
+ judgment with its author; otherwise label the value as an Agent proposal / 待人确认.
50
+
51
+ `page.patch` uses JSON Merge Patch: objects merge, `null` deletes, and arrays
52
+ replace the entire array. Before changing one `whatTheyDo`, `peopleSteps`,
53
+ `foundersJson`, or `industryMaps` item, read the current Page, preserve every
54
+ sibling item, and submit the complete updated array. The live field contract
55
+ includes the exact payload path and write mode for all 61 semantic fields.
56
+
20
57
  ## Exactly four Deal actions
21
58
 
22
59
  ```bash
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.3] - 2026-08-31
4
+
5
+ - Show Core validation codes and field paths instead of collapsing malformed
6
+ Deal writes into `Invalid Occam command`.
7
+ - Publish `write_deal` as four operation-specific MCP command shapes while
8
+ retaining one mutation tool.
9
+ - Add copyable write examples to `llama help deal` and omit the duplicate
10
+ rendered briefing from structured bootstrap output.
11
+ - Let Core's private Page contract classify prose and enums instead of keeping
12
+ a second, drifting public-CLI field whitelist.
13
+ - Return a compact `page.patch` revision receipt with exact read-back fields
14
+ instead of echoing the full Live Deal Page after every write.
15
+ - Turn retired top-level commands such as `llama brief`, `fact`, `post`,
16
+ `timeline`, `html`, `memo`, and `pipeline` into self-correcting errors that
17
+ name the CLI 2 replacement and live contract entrypoint.
18
+
19
+ ## [2.0.2] - 2026-08-31
20
+
21
+ - Correct the authenticated runtime model: Brain has exactly two private
22
+ components, Investment Framework V3 and the Llama Command operating skill.
23
+ - Load the complete Live Deal Page field prompts beside Brain as a separate
24
+ current CLI/MCP tool contract.
25
+ - Keep all Brain and Page prompt bytes server-owned and out of the public npm
26
+ artifact.
27
+
3
28
  ## [2.0.1] - 2026-08-30
4
29
 
5
30
  - Ship the four-action Occam Deal surface from the audited cleanup commit.
@@ -30,6 +55,14 @@ this project adheres to [Semantic Versioning](https://semver.org).
30
55
 
31
56
  ## [Unreleased]
32
57
 
58
+ ### Changed
59
+ - Make the authenticated live bootstrap mandatory before Deal work and clarify
60
+ that it loads the server-owned two-part private Brain plus the separate Live
61
+ Deal Page field contract. CLI remains only the tool/transport layer.
62
+ - Teach CLI and MCP Agents that Information never updates Page automatically,
63
+ every Page content field is Agent-writable, and field authorship controls
64
+ attribution rather than permission.
65
+
33
66
  ## [1.26.0] - 2026-08-11
34
67
 
35
68
  ### Added
package/README.md CHANGED
@@ -90,8 +90,9 @@ When Page prose is human-visible, write one bilingual field value as
90
90
  `{"en":"natural English","zh":"自然中文"}`. The Web language switch selects
91
91
  the reader's version from the same Page revision. Keep language-neutral names,
92
92
  enums, URLs, numbers, dates, and source IDs scalar. Information and raw Input
93
- stay in their original language with provenance. The CLI rejects new scalar
94
- Page prose and incomplete language pairs before they reach Core; historical
93
+ stay in their original language with provenance. Core rejects new scalar Page
94
+ prose according to its private Page contract; the public CLI catches malformed
95
+ half-localized pairs without maintaining a second field whitelist. Historical
95
96
  scalar prose remains readable.
96
97
 
97
98
  Raw user input example:
@@ -158,6 +159,13 @@ llama skills search "<task>"
158
159
  llama skills show <slug>
159
160
  ```
160
161
 
162
+ Agents should run `llama agent bootstrap` before Deal work. The command loads
163
+ the authenticated, server-owned private Brain: Investment Framework V3 for
164
+ thinking plus the Llama Command operating skill for working. It also loads the
165
+ separate current Live Deal Page field contract for writing. None of that private
166
+ content is shipped in this public package; CLI is only the tool and authenticated
167
+ transport.
168
+
161
169
  The live server briefing is authoritative. The bundled
162
170
  `AGENT_BRIEFING.md` is an offline fallback with the same four-action contract.
163
171
 
package/README.zh-CN.md CHANGED
@@ -151,6 +151,12 @@ llama skills search "<任务>"
151
151
  llama skills show <slug>
152
152
  ```
153
153
 
154
+ Agent 在处理 Deal 前应先运行 `llama agent bootstrap`。该命令会从鉴权后的
155
+ Llama Command 服务端加载两部分私有 Brain:负责投资思考的 Investment
156
+ Framework V3,以及负责如何在 Command 工作的 Llama Command operating skill;
157
+ 同时加载独立的、实时的 Live Deal Page 字段契约。所有私有内容都不会进入公开
158
+ npm 包;CLI 只是 Agent 的工具和鉴权运输层。
159
+
154
160
  服务端实时 briefing 是权威合同;包内 `AGENT_BRIEFING.md` 是相同四动作
155
161
  合同的离线兜底。
156
162
 
package/bin/llama-mcp.mjs CHANGED
@@ -5,10 +5,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import { z } from "zod";
7
7
  import {
8
+ formatErrorForDisplay,
8
9
  getAuthHeaders,
9
10
  readBriefing,
10
11
  request,
11
12
  setClientRuntime,
13
+ structuredBootstrapManifest,
12
14
  } from "../lib/client.mjs";
13
15
  import {
14
16
  clearExternalSession,
@@ -20,6 +22,7 @@ import {
20
22
  import {
21
23
  buildDealReadPath,
22
24
  buildDealSearchPath,
25
+ compactDealWriteResult,
23
26
  prepareDealCommand,
24
27
  } from "../lib/deal-actions.mjs";
25
28
 
@@ -36,12 +39,12 @@ function textResult(text, isError = false) {
36
39
  };
37
40
  }
38
41
 
39
- async function callApi(method, path, body) {
42
+ async function callApi(method, path, body, transform = (value) => value) {
40
43
  try {
41
- const result = await request(method, path, body);
44
+ const result = transform(await request(method, path, body));
42
45
  return textResult(typeof result === "string" ? result : JSON.stringify(result, null, 2));
43
46
  } catch (error) {
44
- return textResult(`Error: ${error?.message ?? String(error)}`, true);
47
+ return textResult(formatErrorForDisplay(error), true);
45
48
  }
46
49
  }
47
50
 
@@ -51,6 +54,54 @@ const originSchema = z.object({
51
54
  originatingChatRecordId: z.string().uuid().optional(),
52
55
  });
53
56
 
57
+ const writeDealCommandSchema = z.discriminatedUnion("operation", [
58
+ z.object({
59
+ operation: z.literal("input.submit"),
60
+ dealId: z.string().uuid(),
61
+ format: z.string().min(1).max(120).optional(),
62
+ content: z.any(),
63
+ source: z.record(z.string(), z.any()).optional(),
64
+ origin: originSchema,
65
+ idempotencyKey: z.string().min(1).max(240).optional(),
66
+ }).strict(),
67
+ z.object({
68
+ operation: z.literal("information.put"),
69
+ dealId: z.string().uuid(),
70
+ informationId: z.string().uuid().optional(),
71
+ type: z.string().min(1).max(120),
72
+ labels: z.array(z.string()).optional(),
73
+ subject: z.record(z.string(), z.any()).optional(),
74
+ value: z.any(),
75
+ expectedVersion: z.number().int().positive().optional(),
76
+ origin: originSchema,
77
+ idempotencyKey: z.string().min(1).max(240).optional(),
78
+ }).strict(),
79
+ z.object({
80
+ operation: z.literal("page.patch"),
81
+ dealId: z.string().uuid(),
82
+ patch: z.record(z.string(), z.any()),
83
+ expectedRevision: z.number().int().nonnegative().optional(),
84
+ origin: originSchema,
85
+ idempotencyKey: z.string().min(1).max(240).optional(),
86
+ }).strict(),
87
+ z.object({
88
+ operation: z.literal("artifact.put"),
89
+ dealId: z.string().uuid(),
90
+ artifactId: z.string().uuid().optional(),
91
+ kind: z.string().min(1).max(120),
92
+ title: z.string().min(1).max(500),
93
+ mimeType: z.string().min(1).max(240),
94
+ contentBase64: z.string().min(1).optional(),
95
+ storageKey: z.string().min(1).optional(),
96
+ storageUrl: z.string().url().optional(),
97
+ byteSize: z.number().int().nonnegative().optional(),
98
+ sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
99
+ metadata: z.record(z.string(), z.any()).optional(),
100
+ origin: originSchema,
101
+ idempotencyKey: z.string().min(1).max(240).optional(),
102
+ }).strict(),
103
+ ]);
104
+
54
105
  // Deal is deliberately a closed four-tool surface. Do not add split resource
55
106
  // tools here: one more tool is one more branch every agent must reason about.
56
107
  server.registerTool(
@@ -70,7 +121,7 @@ server.registerTool(
70
121
  server.registerTool(
71
122
  "read_deal",
72
123
  {
73
- description: "Read one Deal. Live Page is always returned; expand only the resource needed.",
124
+ 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.",
74
125
  inputSchema: {
75
126
  dealId: z.string().min(1),
76
127
  detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
@@ -99,36 +150,18 @@ server.registerTool(
99
150
  server.registerTool(
100
151
  "write_deal",
101
152
  {
102
- description: "The only Deal mutation tool: input.submit, information.put, page.patch, or artifact.put. Human-visible Page prose uses one value with both en and zh.",
103
- inputSchema: {
104
- operation: z.enum(["input.submit", "information.put", "page.patch", "artifact.put"]),
105
- dealId: z.string().uuid(),
106
- patch: z.record(z.string(), z.any()).optional(),
107
- expectedRevision: z.number().int().nonnegative().optional(),
108
- informationId: z.string().uuid().optional(),
109
- type: z.string().min(1).max(120).optional(),
110
- labels: z.array(z.string()).optional(),
111
- subject: z.record(z.string(), z.any()).optional(),
112
- value: z.any().optional(),
113
- expectedVersion: z.number().int().positive().optional(),
114
- format: z.string().min(1).max(120).optional(),
115
- content: z.any().optional(),
116
- source: z.record(z.string(), z.any()).optional(),
117
- artifactId: z.string().uuid().optional(),
118
- kind: z.string().min(1).max(120).optional(),
119
- title: z.string().min(1).max(500).optional(),
120
- mimeType: z.string().min(1).max(240).optional(),
121
- contentBase64: z.string().min(1).optional(),
122
- storageKey: z.string().min(1).optional(),
123
- storageUrl: z.string().url().optional(),
124
- byteSize: z.number().int().nonnegative().optional(),
125
- sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
126
- metadata: z.record(z.string(), z.any()).optional(),
127
- origin: originSchema,
128
- idempotencyKey: z.string().min(1).max(240).optional(),
129
- },
153
+ description: "The only Deal mutation tool. Pass one command: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Use the bootstrap field contract and bilingual Page prose. page.patch is JSON Merge Patch: arrays replace whole arrays, so read-modify-write and preserve sibling slots.",
154
+ // MCP SDK 1.30 only publishes object schemas at the tool root. Nesting one
155
+ // command preserves the real discriminated union instead of flattening four
156
+ // incompatible operations into false affordances.
157
+ inputSchema: { command: writeDealCommandSchema },
130
158
  },
131
- async (input) => callApi("POST", "/api/occam/deals/commands", prepareDealCommand("write", input)),
159
+ async ({ command }) => callApi(
160
+ "POST",
161
+ "/api/occam/deals/commands",
162
+ prepareDealCommand("write", command),
163
+ (result) => compactDealWriteResult(command, result),
164
+ ),
132
165
  );
133
166
 
134
167
  server.registerTool(
@@ -152,13 +185,18 @@ server.registerTool(
152
185
  server.registerTool(
153
186
  "agent_bootstrap",
154
187
  {
155
- description: "Fetch the authenticated runtime contract and visible Llama OS skills.",
188
+ description: "Mandatory before Llama work: fetch the authenticated two-part private Brain (Investment Framework V3 + Llama Command operating skill), the separate current Live Deal Page field contract, runtime contract, and visible Llama OS skills.",
156
189
  inputSchema: { limit: z.number().int().min(1).max(100).optional() },
157
190
  },
158
191
  async ({ limit } = {}) => {
159
192
  const params = new URLSearchParams({ clientVersion: PKG_VERSION });
160
193
  if (limit) params.set("limit", String(limit));
161
- return callApi("GET", `/api/agent/manifest?${params}`);
194
+ try {
195
+ const result = await request("GET", `/api/agent/manifest?${params}`);
196
+ return textResult(JSON.stringify(structuredBootstrapManifest(result), null, 2));
197
+ } catch (error) {
198
+ return textResult(formatErrorForDisplay(error), true);
199
+ }
162
200
  },
163
201
  );
164
202
 
@@ -361,7 +399,7 @@ server.registerTool(
361
399
  server.registerPrompt(
362
400
  "agent_briefing",
363
401
  {
364
- description: "Fetch the authenticated current agent contract; bundled text is only an offline fallback.",
402
+ description: "Mandatory before Llama work: fetch the authenticated two-part private Brain, the separate current Live Deal Page field contract, and runtime contract; bundled text is only an offline fallback.",
365
403
  },
366
404
  async () => {
367
405
  const headers = await getAuthHeaders();
package/bin/llama.mjs CHANGED
@@ -4,6 +4,7 @@ import { createRequire } from "node:module";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import readline from "node:readline";
6
6
  import {
7
+ formatErrorForDisplay,
7
8
  getAuthHeaders,
8
9
  getBaseUrl,
9
10
  getToken,
@@ -12,6 +13,7 @@ import {
12
13
  readCanonicalToken,
13
14
  readLegacyConfig,
14
15
  request,
16
+ structuredBootstrapManifest,
15
17
  tryGcloudIdentityToken,
16
18
  writeCanonicalToken,
17
19
  writeLegacyConfig,
@@ -33,6 +35,7 @@ import {
33
35
  DEAL_ACTIONS,
34
36
  buildDealReadPath,
35
37
  buildDealSearchPath,
38
+ compactDealWriteResult,
36
39
  prepareDealCommand,
37
40
  readJsonInput,
38
41
  } from "../lib/deal-actions.mjs";
@@ -69,11 +72,27 @@ Exactly four actions:
69
72
  llama deal create --json <file|->
70
73
  llama deal write --json <file|->
71
74
 
75
+ Before any Page write:
76
+ llama agent bootstrap
77
+
78
+ The authenticated bootstrap contains the two-part private Llama Brain:
79
+ Investment Framework V3 plus the Llama Command operating skill. It also
80
+ contains the separate current 61-field Page contract. CLI is the tool, not
81
+ Brain. Information never updates Page automatically. page.patch is JSON
82
+ Merge Patch: objects merge, null deletes, and arrays replace whole arrays, so
83
+ read-modify-write complete Page arrays and preserve sibling slots.
84
+
72
85
  create JSON:
73
86
  {"companyName":"Acme","page":{},"information":[],"origin":{"kind":"user","originalUserUtterance":"..."}}
74
87
 
75
- write operation:
76
- input.submit | information.put | page.patch | artifact.put
88
+ write JSON (CLI derives idempotencyKey when omitted):
89
+ input.submit: {"operation":"input.submit","dealId":"<uuid>","content":"<original input>","format":"text","source":{},"origin":{"kind":"user","originalUserUtterance":"..."}}
90
+ information.put:{"operation":"information.put","dealId":"<uuid>","type":"founder_claim","labels":["founder-stated"],"subject":{"topic":"traction"},"value":{"content":"...","source":{"kind":"first_meeting_transcript"}},"origin":{"kind":"user","originalUserUtterance":"..."}}
91
+ page.patch: {"operation":"page.patch","dealId":"<uuid>","patch":{"description":{"en":"...","zh":"..."}},"origin":{"kind":"agent"}}
92
+ artifact.put: {"operation":"artifact.put","dealId":"<uuid>","kind":"deck","title":"deck.pdf","mimeType":"application/pdf","storageKey":"<drive-file-id>","storageUrl":"https://drive.google.com/...","byteSize":123,"sha256":"<64 hex>","origin":{"kind":"user","originalUserUtterance":"..."}}
93
+
94
+ For Information, origin records who caused the write and preserves user words;
95
+ value.source records where the evidence came from.
77
96
 
78
97
  Core owns Chat Records, append-only Deal Events, Drive provisioning, audit,
79
98
  and idempotency. User-originated work must preserve exact wording in
@@ -117,6 +136,28 @@ function assertDealAction(area, action) {
117
136
  }
118
137
  }
119
138
 
139
+ const RETIRED_TOP_LEVEL_COMMANDS = Object.freeze({
140
+ brief: "llama deal write --json (operation: page.patch)",
141
+ fact: "llama deal write --json (operation: information.put)",
142
+ post: "llama deal write --json (operation: input.submit)",
143
+ timeline: "llama deal read <dealId> --detail history",
144
+ html: "llama deal write --json (operation: artifact.put)",
145
+ memo: "llama deal write --json (operation: artifact.put)",
146
+ pipeline: "llama deal search|read|create|write",
147
+ });
148
+
149
+ function unknownCommandError(area, action) {
150
+ const replacement = RETIRED_TOP_LEVEL_COMMANDS[area];
151
+ if (replacement) {
152
+ return new Error(
153
+ `\`llama ${area}\` was retired in CLI 2.0.\n` +
154
+ ` → Use: ${replacement}\n` +
155
+ " → Contract: llama agent bootstrap",
156
+ );
157
+ }
158
+ return new Error(`Unknown command: llama ${[area, action].filter(Boolean).join(" ")}`);
159
+ }
160
+
120
161
  function onboardingNoAuth() {
121
162
  return `Llama team onboarding requires credentials.
122
163
 
@@ -431,7 +472,7 @@ async function main() {
431
472
  const params = new URLSearchParams({ clientVersion: PKG_VERSION });
432
473
  if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
433
474
  const manifest = await request("GET", `/api/agent/manifest?${params}`);
434
- if (flags.json) print(manifest);
475
+ if (flags.json) print(structuredBootstrapManifest(manifest));
435
476
  else process.stdout.write(`${manifest.briefing || JSON.stringify(manifest, null, 2)}\n`);
436
477
  return;
437
478
  }
@@ -529,7 +570,9 @@ async function main() {
529
570
  if (area === "deal" && ["create", "write"].includes(action)) {
530
571
  const { flags } = parseFlags(rest, ["json"]);
531
572
  const input = await readJsonInput(flags.json);
532
- print(await request("POST", "/api/occam/deals/commands", prepareDealCommand(action, input)));
573
+ const command = prepareDealCommand(action, input);
574
+ const response = await request("POST", "/api/occam/deals/commands", command);
575
+ print(compactDealWriteResult(command, response));
533
576
  return;
534
577
  }
535
578
 
@@ -555,12 +598,12 @@ async function main() {
555
598
  return;
556
599
  }
557
600
 
558
- throw new Error(`Unknown command: llama ${[area, action].filter(Boolean).join(" ")}`);
601
+ throw unknownCommandError(area, action);
559
602
  }
560
603
 
561
604
  main()
562
605
  .then(() => maybeNudgeUpdate())
563
606
  .catch((error) => {
564
- console.error(`Error: ${error.message}`);
607
+ console.error(formatErrorForDisplay(error));
565
608
  process.exit(1);
566
609
  });
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "format": "llama.cli-build.v1",
3
3
  "packageName": "@llamaventures/cli",
4
- "packageVersion": "2.0.1",
5
- "sourceSha": "ac71158a0bd630d602012e5750be8c1babb93f2d",
4
+ "packageVersion": "2.0.3",
5
+ "sourceSha": "dc1618ab65b6cb2e73b107e7d56315ce2076744e",
6
6
  "sourceKind": "github",
7
7
  "sourceDirty": false,
8
8
  "coreApiContract": {
package/lib/client.mjs CHANGED
@@ -534,7 +534,21 @@ function apiResponseError(data, status) {
534
534
  return error;
535
535
  }
536
536
  if (data?.code !== "MENTION_APPROVAL_REQUIRED") {
537
- const error = new Error(message);
537
+ const issues = Array.isArray(data?.issues)
538
+ ? data.issues.map((issue) => {
539
+ const path = Array.isArray(issue?.path) && issue.path.length
540
+ ? issue.path.reduce(
541
+ (result, part) => typeof part === "number"
542
+ ? `${result}[${part}]`
543
+ : result ? `${result}.${part}` : String(part),
544
+ "",
545
+ )
546
+ : "$";
547
+ const detail = issue?.message || issue?.code || "Invalid value";
548
+ return `- ${path}: ${detail}`;
549
+ })
550
+ : [];
551
+ const error = new Error([message, ...issues].join("\n"));
538
552
  if (typeof data?.code === "string") error.code = data.code;
539
553
  error.details = data;
540
554
  error.status = status;
@@ -563,6 +577,21 @@ function apiResponseError(data, status) {
563
577
  return error;
564
578
  }
565
579
 
580
+ export function formatErrorForDisplay(error) {
581
+ const message = error?.message ?? String(error);
582
+ if (/^Error(?:\[[^\]]+\])?:/u.test(message)) return message;
583
+ const code = typeof error?.code === "string" ? `[${error.code}]` : "";
584
+ return `Error${code}: ${message}`;
585
+ }
586
+
587
+ export function structuredBootstrapManifest(manifest) {
588
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
589
+ return manifest;
590
+ }
591
+ const { briefing: _renderedDuplicate, ...structured } = manifest;
592
+ return structured;
593
+ }
594
+
566
595
  async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
567
596
  const authHeaders = await getAuthHeaders();
568
597
  if (Object.keys(authHeaders).length === 0) throw noAuthError();
@@ -72,62 +72,6 @@ function validateLocalizedTextPairs(value, path = "page") {
72
72
  }
73
73
  }
74
74
 
75
- const LANGUAGE_NEUTRAL_PAGE_KEYS = new Set([
76
- "companyName", "companyKey", "state", "pipelineStatus", "stage",
77
- "theirStage", "ownerUserId", "sourceUserId", "website", "dealOwner",
78
- "source", "sourceDirection", "proposedAmount", "roundSize", "valuation",
79
- "founders", "deckLink", "location", "foundedYear", "leadInvestor",
80
- "dateAdded", "tab", "sourcePerson", "createdByName", "createdByLabel",
81
- "createdVia", "id", "name", "key", "slug", "url", "href", "email",
82
- "linkedin", "handle", "status", "kind", "type", "tier", "confidence",
83
- "currency", "unit", "format", "mimeType", "sha256", "timestamp",
84
- "date", "year",
85
- ]);
86
- const LANGUAGE_NEUTRAL_PAGE_CONTAINERS = new Set([
87
- "manualTags", "investors", "founderEmails", "labels", "tags",
88
- ]);
89
- const OPAQUE_PAGE_CONTAINERS = new Set([
90
- "migration", "extra", "fieldProvenance",
91
- ]);
92
-
93
- function pathKeys(path) {
94
- return path.match(/[^.[\]]+/g)?.filter((part) => !/^\d+$/u.test(part)) ?? [];
95
- }
96
-
97
- function isLanguageNeutralPagePath(path) {
98
- const keys = pathKeys(path);
99
- if (keys.some((key) => OPAQUE_PAGE_CONTAINERS.has(key))) return true;
100
- if (keys.some((key) => LANGUAGE_NEUTRAL_PAGE_CONTAINERS.has(key))) return true;
101
- const key = keys.at(-1) ?? "";
102
- if (LANGUAGE_NEUTRAL_PAGE_KEYS.has(key)) return true;
103
- return /(?:Id|Ids|URL|Url|URI|Uri|Link|Email|Emails|At|Date|Year|Key|Slug|Hash)$/u.test(key);
104
- }
105
-
106
- function validateAgentAuthoredPageProse(value, path = "page") {
107
- if (typeof value === "string") {
108
- if (!isLanguageNeutralPagePath(path)) {
109
- throw new Error(
110
- `${path} agent-authored prose must be one value with non-empty en and zh`,
111
- );
112
- }
113
- return;
114
- }
115
- if (Array.isArray(value)) {
116
- value.forEach((item, index) => validateAgentAuthoredPageProse(item, `${path}[${index}]`));
117
- return;
118
- }
119
- if (!value || typeof value !== "object") return;
120
- if (
121
- Object.prototype.hasOwnProperty.call(value, "en") ||
122
- Object.prototype.hasOwnProperty.call(value, "zh")
123
- ) {
124
- return;
125
- }
126
- for (const [key, child] of Object.entries(value)) {
127
- validateAgentAuthoredPageProse(child, `${path}.${key}`);
128
- }
129
- }
130
-
131
75
  export async function readJsonInput(path, stdin = process.stdin) {
132
76
  if (!path || path === true) throw new Error("--json <file|-> is required");
133
77
  const text = path === "-"
@@ -170,6 +114,32 @@ export function buildDealReadPath(dealId, detail = "overview") {
170
114
  return `/api/occam/deals/${encodeURIComponent(dealId)}${expansion ? `?expand=${expansion}` : ""}`;
171
115
  }
172
116
 
117
+ /**
118
+ * page.patch previously echoed the entire Live Deal Page after every write.
119
+ * Keep the authoritative revision receipt and tell the Agent exactly what to
120
+ * read back, without paying another full-page context cost before verification.
121
+ */
122
+ export function compactDealWriteResult(command, response) {
123
+ if (command?.operation !== "page.patch" || !response?.result?.page) return response;
124
+ const page = response.result.page;
125
+ return {
126
+ ...response,
127
+ result: {
128
+ idempotent: Boolean(response.result.idempotent),
129
+ page: {
130
+ id: page.id,
131
+ revision: page.revision,
132
+ updatedAt: page.updatedAt,
133
+ },
134
+ verify: {
135
+ dealId: command.dealId,
136
+ fields: Object.keys(command.patch ?? {}),
137
+ command: `llama deal read ${command.dealId}`,
138
+ },
139
+ },
140
+ };
141
+ }
142
+
173
143
  export function prepareDealCommand(action, input) {
174
144
  if (!input || typeof input !== "object" || Array.isArray(input)) {
175
145
  throw new Error("Mutation JSON must be an object");
@@ -183,11 +153,9 @@ export function prepareDealCommand(action, input) {
183
153
  }
184
154
  if (action === "create") {
185
155
  validateLocalizedTextPairs(command.page ?? {});
186
- validateAgentAuthoredPageProse(command.page ?? {});
187
156
  }
188
157
  if (action === "write" && command.operation === "page.patch") {
189
158
  validateLocalizedTextPairs(command.patch ?? {});
190
- validateAgentAuthoredPageProse(command.patch ?? {});
191
159
  }
192
160
  return ensureIdempotencyKey(validateCommandOrigin(command));
193
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
5
5
  "type": "module",
6
6
  "scripts": {