@llamaventures/cli 2.0.0 → 2.0.2
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 +51 -1
- package/CHANGELOG.md +28 -0
- package/README.md +15 -0
- package/README.zh-CN.md +12 -0
- package/bin/llama-mcp.mjs +4 -4
- package/bin/llama.mjs +14 -25
- package/contracts/core-api.json +4 -4
- package/lib/build-manifest.json +4 -4
- package/lib/client.mjs +0 -10
- package/lib/deal-actions.mjs +91 -0
- package/package.json +1 -1
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
|
|
@@ -137,7 +174,12 @@ Update the human-facing state only when you know the intended field:
|
|
|
137
174
|
{
|
|
138
175
|
"operation": "page.patch",
|
|
139
176
|
"dealId": "<uuid>",
|
|
140
|
-
"patch": {
|
|
177
|
+
"patch": {
|
|
178
|
+
"location": {
|
|
179
|
+
"en": "San Francisco, CA",
|
|
180
|
+
"zh": "美国加州旧金山"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
141
183
|
"origin": {
|
|
142
184
|
"kind": "user",
|
|
143
185
|
"originalUserUtterance": "The company is in SF, CA."
|
|
@@ -145,6 +187,14 @@ Update the human-facing state only when you know the intended field:
|
|
|
145
187
|
}
|
|
146
188
|
```
|
|
147
189
|
|
|
190
|
+
Human-visible Page prose is bilingual at write time. Store one field value as
|
|
191
|
+
`{"en":"natural English","zh":"自然中文"}`; do not create separate English
|
|
192
|
+
and Chinese Pages. Language-neutral company names, enums, URLs, numbers, dates,
|
|
193
|
+
person names, and source IDs remain scalar. A localized object missing either
|
|
194
|
+
language is invalid. New scalar Page prose is also invalid; historical scalar
|
|
195
|
+
prose is read-compatible only. Information and raw Input retain their original
|
|
196
|
+
language and provenance; do not manufacture a second source quote.
|
|
197
|
+
|
|
148
198
|
Artifacts use `artifact.put`. Treat them as immutable source material. Read the
|
|
149
199
|
artifact, then decide whether its content deserves Information or a Page patch.
|
|
150
200
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.2] - 2026-08-31
|
|
4
|
+
|
|
5
|
+
- Correct the authenticated runtime model: Brain has exactly two private
|
|
6
|
+
components, Investment Framework V3 and the Llama Command operating skill.
|
|
7
|
+
- Load the complete Live Deal Page field prompts beside Brain as a separate
|
|
8
|
+
current CLI/MCP tool contract.
|
|
9
|
+
- Keep all Brain and Page prompt bytes server-owned and out of the public npm
|
|
10
|
+
artifact.
|
|
11
|
+
|
|
12
|
+
## [2.0.1] - 2026-08-30
|
|
13
|
+
|
|
14
|
+
- Ship the four-action Occam Deal surface from the audited cleanup commit.
|
|
15
|
+
- Pin the CLI to the final 4.0.0 Core contract after removing the retired
|
|
16
|
+
Secretary analysis API and cron.
|
|
17
|
+
- Remove legacy Deal API examples from contract-verifier fixtures so the
|
|
18
|
+
package source and publish artifact contain no retired Deal action surface.
|
|
19
|
+
- Enforce bilingual values for newly Agent-authored Page prose while retaining
|
|
20
|
+
read compatibility for historical scalar content.
|
|
21
|
+
- Pin the release candidate to the final local Core API 5.0 contract.
|
|
22
|
+
|
|
3
23
|
## [2.0.0] - 2026-08-30
|
|
4
24
|
|
|
5
25
|
- Replace the split Deal command surface with exactly four actions: `search`,
|
|
@@ -19,6 +39,14 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
19
39
|
|
|
20
40
|
## [Unreleased]
|
|
21
41
|
|
|
42
|
+
### Changed
|
|
43
|
+
- Make the authenticated live bootstrap mandatory before Deal work and clarify
|
|
44
|
+
that it loads the server-owned two-part private Brain plus the separate Live
|
|
45
|
+
Deal Page field contract. CLI remains only the tool/transport layer.
|
|
46
|
+
- Teach CLI and MCP Agents that Information never updates Page automatically,
|
|
47
|
+
every Page content field is Agent-writable, and field authorship controls
|
|
48
|
+
attribution rather than permission.
|
|
49
|
+
|
|
22
50
|
## [1.26.0] - 2026-08-11
|
|
23
51
|
|
|
24
52
|
### Added
|
package/README.md
CHANGED
|
@@ -86,6 +86,14 @@ the initial Live Page and Information, and appends Events atomically.
|
|
|
86
86
|
- `page.patch` — update the human-visible Live Deal Page.
|
|
87
87
|
- `artifact.put` — add an immutable memo/HTML/source artifact.
|
|
88
88
|
|
|
89
|
+
When Page prose is human-visible, write one bilingual field value as
|
|
90
|
+
`{"en":"natural English","zh":"自然中文"}`. The Web language switch selects
|
|
91
|
+
the reader's version from the same Page revision. Keep language-neutral names,
|
|
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
|
|
95
|
+
scalar prose remains readable.
|
|
96
|
+
|
|
89
97
|
Raw user input example:
|
|
90
98
|
|
|
91
99
|
```json
|
|
@@ -150,6 +158,13 @@ llama skills search "<task>"
|
|
|
150
158
|
llama skills show <slug>
|
|
151
159
|
```
|
|
152
160
|
|
|
161
|
+
Agents should run `llama agent bootstrap` before Deal work. The command loads
|
|
162
|
+
the authenticated, server-owned private Brain: Investment Framework V3 for
|
|
163
|
+
thinking plus the Llama Command operating skill for working. It also loads the
|
|
164
|
+
separate current Live Deal Page field contract for writing. None of that private
|
|
165
|
+
content is shipped in this public package; CLI is only the tool and authenticated
|
|
166
|
+
transport.
|
|
167
|
+
|
|
153
168
|
The live server briefing is authoritative. The bundled
|
|
154
169
|
`AGENT_BRIEFING.md` is an offline fallback with the same four-action contract.
|
|
155
170
|
|
package/README.zh-CN.md
CHANGED
|
@@ -84,6 +84,12 @@ Core 会补上 `operation: deal.create`,创建或复用 Drive 文件夹,并
|
|
|
84
84
|
- `page.patch`:更新人类直接看到的 Live Deal Page;
|
|
85
85
|
- `artifact.put`:添加不可变的 memo、HTML 或源文件。
|
|
86
86
|
|
|
87
|
+
给人看的 Page 文字必须在写入时同时提供自然的中英文:
|
|
88
|
+
`{"en":"natural English","zh":"自然中文"}`。Web 的语言开关从同一个
|
|
89
|
+
Page revision 选择对应内容,不创建中文页和英文页两套真相。公司名、枚举、URL、数字、日期、
|
|
90
|
+
人名和 source ID 等语言无关值继续使用 scalar;Information 和原始 Input 保留原语言与 provenance。
|
|
91
|
+
CLI 会在请求到达 Core 前拒绝新的 scalar Page 文案或缺少任一语言的 pair;历史 scalar 文案仍可读取。
|
|
92
|
+
|
|
87
93
|
原始用户输入示例:
|
|
88
94
|
|
|
89
95
|
```json
|
|
@@ -145,6 +151,12 @@ llama skills search "<任务>"
|
|
|
145
151
|
llama skills show <slug>
|
|
146
152
|
```
|
|
147
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
|
+
|
|
148
160
|
服务端实时 briefing 是权威合同;包内 `AGENT_BRIEFING.md` 是相同四动作
|
|
149
161
|
合同的离线兜底。
|
|
150
162
|
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -70,7 +70,7 @@ server.registerTool(
|
|
|
70
70
|
server.registerTool(
|
|
71
71
|
"read_deal",
|
|
72
72
|
{
|
|
73
|
-
description: "Read one Deal. Live Page
|
|
73
|
+
description: "Read one Deal. Live Page and its current private field contract are always returned; Information is memory and never updates Page automatically.",
|
|
74
74
|
inputSchema: {
|
|
75
75
|
dealId: z.string().min(1),
|
|
76
76
|
detail: z.enum(["overview", "memory", "files", "conversation", "history", "all"]).optional(),
|
|
@@ -99,7 +99,7 @@ server.registerTool(
|
|
|
99
99
|
server.registerTool(
|
|
100
100
|
"write_deal",
|
|
101
101
|
{
|
|
102
|
-
description: "The only Deal mutation tool: input.submit, information.put, page.patch, or artifact.put.",
|
|
102
|
+
description: "The only Deal mutation tool: input.submit, information.put, page.patch, or artifact.put. All Page content fields are Agent-writable; author controls attribution, not permission. Use the read/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.",
|
|
103
103
|
inputSchema: {
|
|
104
104
|
operation: z.enum(["input.submit", "information.put", "page.patch", "artifact.put"]),
|
|
105
105
|
dealId: z.string().uuid(),
|
|
@@ -152,7 +152,7 @@ server.registerTool(
|
|
|
152
152
|
server.registerTool(
|
|
153
153
|
"agent_bootstrap",
|
|
154
154
|
{
|
|
155
|
-
description: "
|
|
155
|
+
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
156
|
inputSchema: { limit: z.number().int().min(1).max(100).optional() },
|
|
157
157
|
},
|
|
158
158
|
async ({ limit } = {}) => {
|
|
@@ -361,7 +361,7 @@ server.registerTool(
|
|
|
361
361
|
server.registerPrompt(
|
|
362
362
|
"agent_briefing",
|
|
363
363
|
{
|
|
364
|
-
description: "
|
|
364
|
+
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
365
|
},
|
|
366
366
|
async () => {
|
|
367
367
|
const headers = await getAuthHeaders();
|
package/bin/llama.mjs
CHANGED
|
@@ -69,6 +69,16 @@ Exactly four actions:
|
|
|
69
69
|
llama deal create --json <file|->
|
|
70
70
|
llama deal write --json <file|->
|
|
71
71
|
|
|
72
|
+
Before any Page write:
|
|
73
|
+
llama agent bootstrap
|
|
74
|
+
|
|
75
|
+
The authenticated bootstrap contains the two-part private Llama Brain:
|
|
76
|
+
Investment Framework V3 plus the Llama Command operating skill. It also
|
|
77
|
+
contains the separate current 61-field Page contract. CLI is the tool, not
|
|
78
|
+
Brain. Information never updates Page automatically. page.patch is JSON
|
|
79
|
+
Merge Patch: objects merge, null deletes, and arrays replace whole arrays, so
|
|
80
|
+
read-modify-write complete Page arrays and preserve sibling slots.
|
|
81
|
+
|
|
72
82
|
create JSON:
|
|
73
83
|
{"companyName":"Acme","page":{},"information":[],"origin":{"kind":"user","originalUserUtterance":"..."}}
|
|
74
84
|
|
|
@@ -79,21 +89,6 @@ Core owns Chat Records, append-only Deal Events, Drive provisioning, audit,
|
|
|
79
89
|
and idempotency. User-originated work must preserve exact wording in
|
|
80
90
|
origin.originalUserUtterance or reference origin.originatingChatRecordId.`;
|
|
81
91
|
|
|
82
|
-
const RETIRED_DEAL_AREAS = new Set([
|
|
83
|
-
"activity",
|
|
84
|
-
"approvals",
|
|
85
|
-
"brief",
|
|
86
|
-
"claim",
|
|
87
|
-
"html",
|
|
88
|
-
"memo",
|
|
89
|
-
"mentions",
|
|
90
|
-
"nominate",
|
|
91
|
-
"nominations",
|
|
92
|
-
"post",
|
|
93
|
-
"timeline",
|
|
94
|
-
"workflow",
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
92
|
function parseFlags(args, allowed = null) {
|
|
98
93
|
const flags = {};
|
|
99
94
|
const positional = [];
|
|
@@ -123,17 +118,11 @@ function usage(area) {
|
|
|
123
118
|
console.log(area === "deal" ? HELP_DEAL : HELP_ROOT);
|
|
124
119
|
}
|
|
125
120
|
|
|
126
|
-
function
|
|
121
|
+
function assertDealAction(area, action) {
|
|
127
122
|
if (area === "deal" && !DEAL_ACTIONS.includes(action)) {
|
|
128
123
|
throw new Error(
|
|
129
|
-
`
|
|
130
|
-
"Use
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
if (RETIRED_DEAL_AREAS.has(area)) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`DEAL_COMMAND_RETIRED: \`llama ${area}\` belonged to the split legacy Deal model. ` +
|
|
136
|
-
"Use `llama deal read` or `llama deal write --json <file|->`.",
|
|
124
|
+
`Unknown Deal action: ${action || "<missing>"}. ` +
|
|
125
|
+
"Use search, read, create --json, or write --json.",
|
|
137
126
|
);
|
|
138
127
|
}
|
|
139
128
|
}
|
|
@@ -426,7 +415,7 @@ async function main() {
|
|
|
426
415
|
return;
|
|
427
416
|
}
|
|
428
417
|
|
|
429
|
-
|
|
418
|
+
assertDealAction(area, action);
|
|
430
419
|
|
|
431
420
|
if (await handleAuth(area, action, rest)) return;
|
|
432
421
|
|
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": "
|
|
4
|
+
"apiVersion": "5.0.0",
|
|
5
5
|
"openapiVersion": "3.0.3",
|
|
6
|
-
"sha256": "
|
|
7
|
-
"pathCount":
|
|
8
|
-
"operationCount":
|
|
6
|
+
"sha256": "c48ed580f9930b06445f62b94959a2ca4bf9780e59473f17588e1e6554914014",
|
|
7
|
+
"pathCount": 110,
|
|
8
|
+
"operationCount": 136
|
|
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.0.
|
|
5
|
-
"sourceSha": "
|
|
4
|
+
"packageVersion": "2.0.2",
|
|
5
|
+
"sourceSha": "dc52c43363b050072c70f45144909d93b4f42028",
|
|
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": "
|
|
11
|
+
"apiVersion": "5.0.0",
|
|
12
12
|
"openapiVersion": "3.0.3",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "c48ed580f9930b06445f62b94959a2ca4bf9780e59473f17588e1e6554914014"
|
|
14
14
|
}
|
|
15
15
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -294,16 +294,6 @@ function inferCommand(method, endpoint) {
|
|
|
294
294
|
if (pathname === "/api/occam/deals") return "deal.search";
|
|
295
295
|
if (pathname === "/api/occam/deals/commands") return "deal.write";
|
|
296
296
|
if (/^\/api\/occam\/deals\/[^/]+$/.test(pathname)) return "deal.read";
|
|
297
|
-
if (pathname === "/api/deals") return verb === "GET" ? "deal.search" : "deal.write";
|
|
298
|
-
if (pathname === "/api/deals/create") return "deal.create";
|
|
299
|
-
if (pathname === "/api/deals/update") return "deal.update";
|
|
300
|
-
if (/^\/api\/deals\/[^/]+\/ingest$/.test(pathname)) return "deal.ingest";
|
|
301
|
-
if (/^\/api\/deals\/[^/]+\/threads\/[^/]+$/.test(pathname)) return "deal.agent.run";
|
|
302
|
-
if (/^\/api\/deals\/[^/]+\/threads$/.test(pathname)) return "deal.thread.create";
|
|
303
|
-
if (/^\/api\/deals\/[^/]+\/facts/.test(pathname)) return verb === "GET" ? "deal.fact.list" : "deal.fact.write";
|
|
304
|
-
if (/^\/api\/deals\/[^/]+\/posts$/.test(pathname)) return "deal.post";
|
|
305
|
-
if (/^\/api\/deals\/[^/]+\/blocks/.test(pathname)) return verb === "GET" ? "brief.blocks" : "brief.write";
|
|
306
|
-
if (/^\/api\/deals\/[^/]+$/.test(pathname)) return verb === "GET" ? "deal.show" : "deal.write";
|
|
307
297
|
if (pathname === "/api/me") return "auth.status";
|
|
308
298
|
if (pathname.startsWith("/api/agent/skills")) return "skills.read";
|
|
309
299
|
if (pathname === "/api/agent/manifest") return "agent.bootstrap";
|
package/lib/deal-actions.mjs
CHANGED
|
@@ -45,6 +45,89 @@ export function validateCommandOrigin(command) {
|
|
|
45
45
|
return command;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function validateLocalizedTextPairs(value, path = "page") {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
value.forEach((item, index) => validateLocalizedTextPairs(item, `${path}[${index}]`));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!value || typeof value !== "object") return;
|
|
54
|
+
const hasEnglish = Object.prototype.hasOwnProperty.call(value, "en");
|
|
55
|
+
const hasChinese = Object.prototype.hasOwnProperty.call(value, "zh");
|
|
56
|
+
if (hasEnglish || hasChinese) {
|
|
57
|
+
if (
|
|
58
|
+
Object.keys(value).some((key) => key !== "en" && key !== "zh") ||
|
|
59
|
+
typeof value.en !== "string" ||
|
|
60
|
+
!value.en.trim() ||
|
|
61
|
+
typeof value.zh !== "string" ||
|
|
62
|
+
!value.zh.trim()
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`${path} localized text must contain only non-empty en and zh values`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const [key, child] of Object.entries(value)) {
|
|
71
|
+
validateLocalizedTextPairs(child, `${path}.${key}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
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
|
+
|
|
48
131
|
export async function readJsonInput(path, stdin = process.stdin) {
|
|
49
132
|
if (!path || path === true) throw new Error("--json <file|-> is required");
|
|
50
133
|
const text = path === "-"
|
|
@@ -98,5 +181,13 @@ export function prepareDealCommand(action, input) {
|
|
|
98
181
|
if (action === "write" && !DEAL_WRITE_OPERATIONS.includes(command.operation)) {
|
|
99
182
|
throw new Error(`write JSON operation must be ${DEAL_WRITE_OPERATIONS.join(", ")}`);
|
|
100
183
|
}
|
|
184
|
+
if (action === "create") {
|
|
185
|
+
validateLocalizedTextPairs(command.page ?? {});
|
|
186
|
+
validateAgentAuthoredPageProse(command.page ?? {});
|
|
187
|
+
}
|
|
188
|
+
if (action === "write" && command.operation === "page.patch") {
|
|
189
|
+
validateLocalizedTextPairs(command.patch ?? {});
|
|
190
|
+
validateAgentAuthoredPageProse(command.patch ?? {});
|
|
191
|
+
}
|
|
101
192
|
return ensureIdempotencyKey(validateCommandOrigin(command));
|
|
102
193
|
}
|