@llamaventures/cli 1.26.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "format": "llama.core-api-contract.v1",
3
3
  "name": "llama-core-api",
4
- "apiVersion": "3.33.0",
4
+ "apiVersion": "5.0.0",
5
5
  "openapiVersion": "3.0.3",
6
- "sha256": "d673c2f38f63be2cd92415d9c384b40d115165ca4ba11bfb93ddaf4036226eed",
7
- "pathCount": 238,
8
- "operationCount": 312
6
+ "sha256": "c48ed580f9930b06445f62b94959a2ca4bf9780e59473f17588e1e6554914014",
7
+ "pathCount": 110,
8
+ "operationCount": 136
9
9
  }
@@ -2,353 +2,32 @@
2
2
  "format": "llama.cli-core-api-operations.v1",
3
3
  "coreApiContract": "contracts/core-api.json",
4
4
  "requiredOperations": [
5
- {
6
- "method": "GET",
7
- "path": "/api/admin/agent-events"
8
- },
9
- {
10
- "method": "GET",
11
- "path": "/api/admin/auth-events"
12
- },
13
- {
14
- "method": "GET",
15
- "path": "/api/admin/deal-events"
16
- },
17
- {
18
- "method": "GET",
19
- "path": "/api/admin/workflow-audit"
20
- },
21
- {
22
- "method": "POST",
23
- "path": "/api/admin/workflow-remediation"
24
- },
25
- {
26
- "method": "GET",
27
- "path": "/api/agent/activity"
28
- },
29
- {
30
- "method": "GET",
31
- "path": "/api/agent/briefing"
32
- },
33
- {
34
- "method": "POST",
35
- "path": "/api/agent/client-events"
36
- },
37
- {
38
- "method": "GET",
39
- "path": "/api/agent/explain"
40
- },
41
- {
42
- "method": "GET",
43
- "path": "/api/agent/manifest"
44
- },
45
- {
46
- "method": "GET",
47
- "path": "/api/agent/preferences"
48
- },
49
- {
50
- "method": "POST",
51
- "path": "/api/agent/preferences"
52
- },
53
- {
54
- "method": "PATCH",
55
- "path": "/api/agent/preferences/{id}"
56
- },
57
- {
58
- "method": "GET",
59
- "path": "/api/agent/skills"
60
- },
61
- {
62
- "method": "GET",
63
- "path": "/api/agent/skills/{slug}"
64
- },
65
- {
66
- "method": "GET",
67
- "path": "/api/deals"
68
- },
69
- {
70
- "method": "POST",
71
- "path": "/api/deals/create"
72
- },
73
- {
74
- "method": "GET",
75
- "path": "/api/deals/deleted"
76
- },
77
- {
78
- "method": "POST",
79
- "path": "/api/deals/update"
80
- },
81
- {
82
- "method": "DELETE",
83
- "path": "/api/deals/{dealId}"
84
- },
85
- {
86
- "method": "GET",
87
- "path": "/api/deals/{dealId}"
88
- },
89
- {
90
- "method": "POST",
91
- "path": "/api/deals/{dealId}/agent-runs/{runId}/revert"
92
- },
93
- {
94
- "method": "GET",
95
- "path": "/api/deals/{dealId}/blocks"
96
- },
97
- {
98
- "method": "PUT",
99
- "path": "/api/deals/{dealId}/blocks"
100
- },
101
- {
102
- "method": "DELETE",
103
- "path": "/api/deals/{dealId}/blocks/{blockId}"
104
- },
105
- {
106
- "method": "GET",
107
- "path": "/api/deals/{dealId}/blocks/{blockId}"
108
- },
109
- {
110
- "method": "PATCH",
111
- "path": "/api/deals/{dealId}/blocks/{blockId}"
112
- },
113
- {
114
- "method": "GET",
115
- "path": "/api/deals/{dealId}/blocks/{blockId}/history"
116
- },
117
- {
118
- "method": "POST",
119
- "path": "/api/deals/{dealId}/blocks/{blockId}/history"
120
- },
121
- {
122
- "method": "POST",
123
- "path": "/api/deals/{dealId}/blocks/{blockId}/restore"
124
- },
125
- {
126
- "method": "GET",
127
- "path": "/api/deals/{dealId}/collaborators"
128
- },
129
- {
130
- "method": "POST",
131
- "path": "/api/deals/{dealId}/collaborators"
132
- },
133
- {
134
- "method": "DELETE",
135
- "path": "/api/deals/{dealId}/collaborators/{userId}"
136
- },
137
- {
138
- "method": "POST",
139
- "path": "/api/deals/{dealId}/collaborators/{userId}/restore"
140
- },
141
- {
142
- "method": "GET",
143
- "path": "/api/deals/{dealId}/command-center"
144
- },
145
- {
146
- "method": "GET",
147
- "path": "/api/deals/{dealId}/documents"
148
- },
149
- {
150
- "method": "POST",
151
- "path": "/api/deals/{dealId}/documents"
152
- },
153
- {
154
- "method": "DELETE",
155
- "path": "/api/deals/{dealId}/documents/{slug}"
156
- },
157
- {
158
- "method": "PATCH",
159
- "path": "/api/deals/{dealId}/documents/{slug}"
160
- },
161
- {
162
- "method": "DELETE",
163
- "path": "/api/deals/{dealId}/documents/{slug}/html"
164
- },
165
- {
166
- "method": "GET",
167
- "path": "/api/deals/{dealId}/documents/{slug}/html"
168
- },
169
- {
170
- "method": "PUT",
171
- "path": "/api/deals/{dealId}/documents/{slug}/html"
172
- },
173
- {
174
- "method": "GET",
175
- "path": "/api/deals/{dealId}/documents/{slug}/html/history"
176
- },
177
- {
178
- "method": "POST",
179
- "path": "/api/deals/{dealId}/documents/{slug}/html/restore/{version}"
180
- },
181
- {
182
- "method": "POST",
183
- "path": "/api/deals/{dealId}/enrich"
184
- },
185
- {
186
- "method": "GET",
187
- "path": "/api/deals/{dealId}/facts"
188
- },
189
- {
190
- "method": "POST",
191
- "path": "/api/deals/{dealId}/facts"
192
- },
193
- {
194
- "method": "PATCH",
195
- "path": "/api/deals/{dealId}/facts/{factId}"
196
- },
197
- {
198
- "method": "POST",
199
- "path": "/api/deals/{dealId}/facts/{factId}/uncontest"
200
- },
201
- {
202
- "method": "GET",
203
- "path": "/api/deals/{dealId}/feed"
204
- },
205
- {
206
- "method": "PUT",
207
- "path": "/api/deals/{dealId}/founders"
208
- },
209
- {
210
- "method": "POST",
211
- "path": "/api/deals/{dealId}/ingest"
212
- },
213
- {
214
- "method": "GET",
215
- "path": "/api/deals/{dealId}/links"
216
- },
217
- {
218
- "method": "POST",
219
- "path": "/api/deals/{dealId}/links"
220
- },
221
- {
222
- "method": "DELETE",
223
- "path": "/api/deals/{dealId}/links/{linkId}"
224
- },
225
- {
226
- "method": "POST",
227
- "path": "/api/deals/{dealId}/links/{linkId}/restore"
228
- },
229
- {
230
- "method": "GET",
231
- "path": "/api/deals/{dealId}/memo"
232
- },
233
- {
234
- "method": "POST",
235
- "path": "/api/deals/{dealId}/posts"
236
- },
237
- {
238
- "method": "POST",
239
- "path": "/api/deals/{dealId}/propose-owner"
240
- },
241
- {
242
- "method": "POST",
243
- "path": "/api/deals/{dealId}/refresh-brief"
244
- },
245
- {
246
- "method": "POST",
247
- "path": "/api/deals/{dealId}/restore"
248
- },
249
- {
250
- "method": "POST",
251
- "path": "/api/deals/{dealId}/threads"
252
- },
253
- {
254
- "method": "POST",
255
- "path": "/api/deals/{dealId}/threads/{threadId}"
256
- },
257
- {
258
- "method": "GET",
259
- "path": "/api/deals/{dealId}/timeline"
260
- },
261
- {
262
- "method": "GET",
263
- "path": "/api/deals/{dealId}/workflow"
264
- },
265
- {
266
- "method": "POST",
267
- "path": "/api/deals/{dealId}/workflow"
268
- },
269
- {
270
- "method": "POST",
271
- "path": "/api/external/chat"
272
- },
273
- {
274
- "method": "POST",
275
- "path": "/api/external/start-session"
276
- },
277
- {
278
- "method": "POST",
279
- "path": "/api/external/upload"
280
- },
281
- {
282
- "method": "GET",
283
- "path": "/api/me"
284
- },
285
- {
286
- "method": "GET",
287
- "path": "/api/me/nominations"
288
- },
289
- {
290
- "method": "GET",
291
- "path": "/api/mentions"
292
- },
293
- {
294
- "method": "GET",
295
- "path": "/api/mentions/unread-count"
296
- },
297
- {
298
- "method": "POST",
299
- "path": "/api/mentions/{id}/resolve"
300
- },
301
- {
302
- "method": "POST",
303
- "path": "/api/nominations/{approvalId}"
304
- },
305
- {
306
- "method": "GET",
307
- "path": "/api/oauth/authorize"
308
- },
309
- {
310
- "method": "POST",
311
- "path": "/api/oauth/revoke"
312
- },
313
- {
314
- "method": "POST",
315
- "path": "/api/oauth/token"
316
- },
317
- {
318
- "method": "GET",
319
- "path": "/api/partner/approvals"
320
- },
321
- {
322
- "method": "POST",
323
- "path": "/api/partner/approvals"
324
- },
325
- {
326
- "method": "GET",
327
- "path": "/api/users"
328
- },
329
- {
330
- "method": "POST",
331
- "path": "/api/wiki/save"
332
- },
333
- {
334
- "method": "GET",
335
- "path": "/api/wiki/search"
336
- },
337
- {
338
- "method": "DELETE",
339
- "path": "/api/wiki/{slug}"
340
- },
341
- {
342
- "method": "GET",
343
- "path": "/api/wiki/{slug}"
344
- },
345
- {
346
- "method": "POST",
347
- "path": "/api/wiki/{slug}/file"
348
- },
349
- {
350
- "method": "POST",
351
- "path": "/api/wiki/{slug}/restore"
352
- }
5
+ { "method": "DELETE", "path": "/api/wiki/{slug}" },
6
+ { "method": "GET", "path": "/api/admin/agent-events" },
7
+ { "method": "GET", "path": "/api/admin/auth-events" },
8
+ { "method": "GET", "path": "/api/admin/deal-events" },
9
+ { "method": "GET", "path": "/api/agent/briefing" },
10
+ { "method": "GET", "path": "/api/agent/explain" },
11
+ { "method": "GET", "path": "/api/agent/manifest" },
12
+ { "method": "GET", "path": "/api/agent/preferences" },
13
+ { "method": "GET", "path": "/api/agent/skills" },
14
+ { "method": "GET", "path": "/api/agent/skills/{slug}" },
15
+ { "method": "GET", "path": "/api/me" },
16
+ { "method": "GET", "path": "/api/oauth/authorize" },
17
+ { "method": "GET", "path": "/api/occam/deals" },
18
+ { "method": "GET", "path": "/api/occam/deals/{dealId}" },
19
+ { "method": "GET", "path": "/api/wiki/{slug}" },
20
+ { "method": "GET", "path": "/api/wiki/search" },
21
+ { "method": "PATCH", "path": "/api/agent/preferences/{id}" },
22
+ { "method": "POST", "path": "/api/agent/client-events" },
23
+ { "method": "POST", "path": "/api/agent/preferences" },
24
+ { "method": "POST", "path": "/api/external/chat" },
25
+ { "method": "POST", "path": "/api/external/start-session" },
26
+ { "method": "POST", "path": "/api/external/upload" },
27
+ { "method": "POST", "path": "/api/oauth/revoke" },
28
+ { "method": "POST", "path": "/api/oauth/token" },
29
+ { "method": "POST", "path": "/api/occam/deals/commands" },
30
+ { "method": "POST", "path": "/api/wiki/{slug}/restore" },
31
+ { "method": "POST", "path": "/api/wiki/save" }
353
32
  ]
354
33
  }
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": "llama.cli-build.v1",
3
3
  "packageName": "@llamaventures/cli",
4
- "packageVersion": "1.26.0",
5
- "sourceSha": "c330b8222f075478e50a96e160b647b09f535e3d",
4
+ "packageVersion": "2.0.1",
5
+ "sourceSha": "ac71158a0bd630d602012e5750be8c1babb93f2d",
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": "3.33.0",
11
+ "apiVersion": "5.0.0",
12
12
  "openapiVersion": "3.0.3",
13
- "sha256": "d673c2f38f63be2cd92415d9c384b40d115165ca4ba11bfb93ddaf4036226eed"
13
+ "sha256": "c48ed580f9930b06445f62b94959a2ca4bf9780e59473f17588e1e6554914014"
14
14
  }
15
15
  }
package/lib/client.mjs CHANGED
@@ -59,11 +59,11 @@ export function getPackageVersion() {
59
59
  return packageVersionCache;
60
60
  }
61
61
 
62
- // Canonical entrypoint. `llama-command.onrender.com` also serves the API
63
- // but its NextAuth callback URL doesn't match, so browser login (needed
64
- // to mint a token at /settings/tokens) fails there with a server-config
65
- // error. Always default to canonical — override with $LLAMA_API_URL only
66
- // if testing.
62
+ // Canonical entrypoint. The hosting provider's assigned hostname also
63
+ // serves the API, but its NextAuth callback URL doesn't match, so browser
64
+ // login (needed to mint a token at /settings/tokens) fails there with a
65
+ // server-config error. Always default to canonical — override with
66
+ // $LLAMA_API_URL only if testing.
67
67
  export const DEFAULT_BASE_URL = "https://command.llamaventures.vc";
68
68
 
69
69
  // Canonical token location (single line, mode 0600). Aligns with the
@@ -212,6 +212,7 @@ function agentClientHeaders(command) {
212
212
  return {
213
213
  "X-Llama-Client": runtimeClient,
214
214
  "X-Llama-Client-Version": getPackageVersion(),
215
+ "X-Llama-Client-Capabilities": "core.read.v1,occam.deal.v1",
215
216
  "X-Llama-Client-Source-Sha": build.sourceSha,
216
217
  "X-Llama-API-Contract-Version": build.coreApiContract.apiVersion,
217
218
  "X-Llama-API-Contract-Digest": build.coreApiContract.sha256,
@@ -290,16 +291,9 @@ function inferCommand(method, endpoint) {
290
291
  if (pathname === "/api/wiki/search") return "wiki.search";
291
292
  if (pathname === "/api/wiki/save") return "wiki.save";
292
293
  if (/^\/api\/wiki\/[^/]+$/.test(pathname)) return verb === "GET" ? "wiki.read" : "wiki.write";
293
- if (pathname === "/api/deals") return verb === "GET" ? "deal.search" : "deal.write";
294
- if (pathname === "/api/deals/create") return "deal.create";
295
- if (pathname === "/api/deals/update") return "deal.update";
296
- if (/^\/api\/deals\/[^/]+\/ingest$/.test(pathname)) return "deal.ingest";
297
- if (/^\/api\/deals\/[^/]+\/threads\/[^/]+$/.test(pathname)) return "deal.agent.run";
298
- if (/^\/api\/deals\/[^/]+\/threads$/.test(pathname)) return "deal.thread.create";
299
- if (/^\/api\/deals\/[^/]+\/facts/.test(pathname)) return verb === "GET" ? "deal.fact.list" : "deal.fact.write";
300
- if (/^\/api\/deals\/[^/]+\/posts$/.test(pathname)) return "deal.post";
301
- if (/^\/api\/deals\/[^/]+\/blocks/.test(pathname)) return verb === "GET" ? "brief.blocks" : "brief.write";
302
- if (/^\/api\/deals\/[^/]+$/.test(pathname)) return verb === "GET" ? "deal.show" : "deal.write";
294
+ if (pathname === "/api/occam/deals") return "deal.search";
295
+ if (pathname === "/api/occam/deals/commands") return "deal.write";
296
+ if (/^\/api\/occam\/deals\/[^/]+$/.test(pathname)) return "deal.read";
303
297
  if (pathname === "/api/me") return "auth.status";
304
298
  if (pathname.startsWith("/api/agent/skills")) return "skills.read";
305
299
  if (pathname === "/api/agent/manifest") return "agent.bootstrap";
@@ -536,12 +530,14 @@ function apiResponseError(data, status) {
536
530
  );
537
531
  error.code = data.code;
538
532
  error.details = data;
533
+ error.status = status;
539
534
  return error;
540
535
  }
541
536
  if (data?.code !== "MENTION_APPROVAL_REQUIRED") {
542
537
  const error = new Error(message);
543
538
  if (typeof data?.code === "string") error.code = data.code;
544
539
  error.details = data;
540
+ error.status = status;
545
541
  return error;
546
542
  }
547
543
  const recipients = Array.isArray(data?.cue?.recipients)
@@ -563,6 +559,7 @@ function apiResponseError(data, status) {
563
559
  );
564
560
  error.code = data.code;
565
561
  error.details = data;
562
+ error.status = status;
566
563
  return error;
567
564
  }
568
565
 
@@ -0,0 +1,193 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ export const DEAL_ACTIONS = Object.freeze(["search", "read", "create", "write"]);
5
+ export const DEAL_WRITE_OPERATIONS = Object.freeze([
6
+ "input.submit",
7
+ "information.put",
8
+ "page.patch",
9
+ "artifact.put",
10
+ ]);
11
+
12
+ function canonicalJson(value) {
13
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
14
+ if (value && typeof value === "object") {
15
+ return `{${Object.keys(value)
16
+ .sort()
17
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
18
+ .join(",")}}`;
19
+ }
20
+ return JSON.stringify(value);
21
+ }
22
+
23
+ export function ensureIdempotencyKey(command) {
24
+ if (typeof command?.idempotencyKey === "string" && command.idempotencyKey.trim()) {
25
+ return command;
26
+ }
27
+ const digest = createHash("sha256").update(canonicalJson(command)).digest("hex").slice(0, 40);
28
+ return { ...command, idempotencyKey: `cli:${digest}` };
29
+ }
30
+
31
+ export function validateCommandOrigin(command) {
32
+ const origin = command?.origin;
33
+ if (!origin || !["user", "agent", "system"].includes(origin.kind)) {
34
+ throw new Error("Mutation JSON must include origin.kind: user, agent, or system");
35
+ }
36
+ if (
37
+ origin.kind === "user" &&
38
+ !(typeof origin.originalUserUtterance === "string" && origin.originalUserUtterance.trim()) &&
39
+ !(typeof origin.originatingChatRecordId === "string" && origin.originatingChatRecordId.trim())
40
+ ) {
41
+ throw new Error(
42
+ "User-originated mutation JSON must preserve origin.originalUserUtterance or origin.originatingChatRecordId",
43
+ );
44
+ }
45
+ return command;
46
+ }
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
+
131
+ export async function readJsonInput(path, stdin = process.stdin) {
132
+ if (!path || path === true) throw new Error("--json <file|-> is required");
133
+ const text = path === "-"
134
+ ? await new Promise((resolve, reject) => {
135
+ let value = "";
136
+ stdin.setEncoding("utf8");
137
+ stdin.on("data", (chunk) => { value += chunk; });
138
+ stdin.on("end", () => resolve(value));
139
+ stdin.on("error", reject);
140
+ })
141
+ : await readFile(path, "utf8");
142
+ try {
143
+ return JSON.parse(text);
144
+ } catch (error) {
145
+ throw new Error(`Invalid JSON from ${path}: ${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+ }
148
+
149
+ export function buildDealSearchPath(query, flags = {}) {
150
+ const params = new URLSearchParams();
151
+ if (query) params.set("q", query);
152
+ if (flags.state && flags.state !== true) params.set("state", String(flags.state));
153
+ if (flags.limit && flags.limit !== true) params.set("limit", String(flags.limit));
154
+ return `/api/occam/deals${params.size ? `?${params}` : ""}`;
155
+ }
156
+
157
+ export function buildDealReadPath(dealId, detail = "overview") {
158
+ if (!dealId || dealId.startsWith("--")) {
159
+ throw new Error("Usage: llama deal read <dealId> [--detail overview|memory|files|conversation|history|all]");
160
+ }
161
+ const expansion = {
162
+ overview: "",
163
+ memory: "information",
164
+ files: "artifacts",
165
+ conversation: "chat",
166
+ history: "feed",
167
+ all: "information,artifacts,chat,feed",
168
+ }[detail];
169
+ if (expansion === undefined) throw new Error(`Unknown detail: ${detail}`);
170
+ return `/api/occam/deals/${encodeURIComponent(dealId)}${expansion ? `?expand=${expansion}` : ""}`;
171
+ }
172
+
173
+ export function prepareDealCommand(action, input) {
174
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
175
+ throw new Error("Mutation JSON must be an object");
176
+ }
177
+ const command = action === "create" ? { operation: "deal.create", ...input } : input;
178
+ if (action === "create" && command.operation !== "deal.create") {
179
+ throw new Error("create JSON must describe one deal.create intent");
180
+ }
181
+ if (action === "write" && !DEAL_WRITE_OPERATIONS.includes(command.operation)) {
182
+ throw new Error(`write JSON operation must be ${DEAL_WRITE_OPERATIONS.join(", ")}`);
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
+ }
192
+ return ensureIdempotencyKey(validateCommandOrigin(command));
193
+ }