@llamaventures/cli 1.19.0 → 1.22.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.
@@ -2,90 +2,341 @@
2
2
  "format": "llama.cli-core-api-operations.v1",
3
3
  "coreApiContract": "contracts/core-api.json",
4
4
  "requiredOperations": [
5
- { "method": "DELETE", "path": "/api/deals/{dealId}" },
6
- { "method": "DELETE", "path": "/api/deals/{dealId}/blocks/{blockId}" },
7
- { "method": "DELETE", "path": "/api/deals/{dealId}/collaborators/{userId}" },
8
- { "method": "DELETE", "path": "/api/deals/{dealId}/documents/{slug}" },
9
- { "method": "DELETE", "path": "/api/deals/{dealId}/documents/{slug}/html" },
10
- { "method": "DELETE", "path": "/api/deals/{dealId}/links/{linkId}" },
11
- { "method": "DELETE", "path": "/api/deals/{dealId}/memo" },
12
- { "method": "DELETE", "path": "/api/skill-corrections/{id}" },
13
- { "method": "DELETE", "path": "/api/wiki/{slug}" },
14
- { "method": "GET", "path": "/api/admin/agent-events" },
15
- { "method": "GET", "path": "/api/admin/auth-events" },
16
- { "method": "GET", "path": "/api/admin/deal-events" },
17
- { "method": "GET", "path": "/api/agent/activity" },
18
- { "method": "GET", "path": "/api/agent/briefing" },
19
- { "method": "GET", "path": "/api/agent/explain" },
20
- { "method": "GET", "path": "/api/agent/manifest" },
21
- { "method": "GET", "path": "/api/agent/skills" },
22
- { "method": "GET", "path": "/api/agent/skills/{slug}" },
23
- { "method": "GET", "path": "/api/deals" },
24
- { "method": "GET", "path": "/api/deals/{dealId}" },
25
- { "method": "GET", "path": "/api/deals/{dealId}/blocks" },
26
- { "method": "GET", "path": "/api/deals/{dealId}/blocks/{blockId}" },
27
- { "method": "GET", "path": "/api/deals/{dealId}/blocks/{blockId}/history" },
28
- { "method": "GET", "path": "/api/deals/{dealId}/collaborators" },
29
- { "method": "GET", "path": "/api/deals/{dealId}/command-center" },
30
- { "method": "GET", "path": "/api/deals/{dealId}/documents" },
31
- { "method": "GET", "path": "/api/deals/{dealId}/documents/{slug}/html" },
32
- { "method": "GET", "path": "/api/deals/{dealId}/documents/{slug}/html/history" },
33
- { "method": "GET", "path": "/api/deals/{dealId}/facts" },
34
- { "method": "GET", "path": "/api/deals/{dealId}/feed" },
35
- { "method": "GET", "path": "/api/deals/{dealId}/links" },
36
- { "method": "GET", "path": "/api/deals/{dealId}/memo" },
37
- { "method": "GET", "path": "/api/deals/{dealId}/timeline" },
38
- { "method": "GET", "path": "/api/deals/deleted" },
39
- { "method": "GET", "path": "/api/me" },
40
- { "method": "GET", "path": "/api/me/nominations" },
41
- { "method": "GET", "path": "/api/mentions" },
42
- { "method": "GET", "path": "/api/mentions/unread-count" },
43
- { "method": "GET", "path": "/api/oauth/authorize" },
44
- { "method": "GET", "path": "/api/partner/approvals" },
45
- { "method": "GET", "path": "/api/skill-corrections" },
46
- { "method": "GET", "path": "/api/users" },
47
- { "method": "GET", "path": "/api/wiki/{slug}" },
48
- { "method": "GET", "path": "/api/wiki/search" },
49
- { "method": "PATCH", "path": "/api/deals/{dealId}/blocks/{blockId}" },
50
- { "method": "PATCH", "path": "/api/deals/{dealId}/documents/{slug}" },
51
- { "method": "PATCH", "path": "/api/deals/{dealId}/facts/{factId}" },
52
- { "method": "POST", "path": "/api/agent/client-events" },
53
- { "method": "POST", "path": "/api/agent/eval-feedback" },
54
- { "method": "POST", "path": "/api/deals/{dealId}/agent-runs/{runId}/revert" },
55
- { "method": "POST", "path": "/api/deals/{dealId}/blocks/{blockId}/history" },
56
- { "method": "POST", "path": "/api/deals/{dealId}/blocks/{blockId}/restore" },
57
- { "method": "POST", "path": "/api/deals/{dealId}/collaborators" },
58
- { "method": "POST", "path": "/api/deals/{dealId}/collaborators/{userId}/restore" },
59
- { "method": "POST", "path": "/api/deals/{dealId}/documents" },
60
- { "method": "POST", "path": "/api/deals/{dealId}/documents/{slug}/html/restore/{version}" },
61
- { "method": "POST", "path": "/api/deals/{dealId}/enrich" },
62
- { "method": "POST", "path": "/api/deals/{dealId}/facts" },
63
- { "method": "POST", "path": "/api/deals/{dealId}/links" },
64
- { "method": "POST", "path": "/api/deals/{dealId}/links/{linkId}/restore" },
65
- { "method": "POST", "path": "/api/deals/{dealId}/memo" },
66
- { "method": "POST", "path": "/api/deals/{dealId}/posts" },
67
- { "method": "POST", "path": "/api/deals/{dealId}/propose-owner" },
68
- { "method": "POST", "path": "/api/deals/{dealId}/refresh-brief" },
69
- { "method": "POST", "path": "/api/deals/{dealId}/refresh-persona" },
70
- { "method": "POST", "path": "/api/deals/{dealId}/restore" },
71
- { "method": "POST", "path": "/api/deals/{dealId}/threads" },
72
- { "method": "POST", "path": "/api/deals/{dealId}/threads/{threadId}" },
73
- { "method": "POST", "path": "/api/deals/create" },
74
- { "method": "POST", "path": "/api/deals/update" },
75
- { "method": "POST", "path": "/api/external/chat" },
76
- { "method": "POST", "path": "/api/external/start-session" },
77
- { "method": "POST", "path": "/api/external/upload" },
78
- { "method": "POST", "path": "/api/mentions/{id}/resolve" },
79
- { "method": "POST", "path": "/api/nominations/{approvalId}" },
80
- { "method": "POST", "path": "/api/oauth/revoke" },
81
- { "method": "POST", "path": "/api/oauth/token" },
82
- { "method": "POST", "path": "/api/partner/approvals" },
83
- { "method": "POST", "path": "/api/skill-corrections" },
84
- { "method": "POST", "path": "/api/wiki/{slug}/restore" },
85
- { "method": "POST", "path": "/api/wiki/save" },
86
- { "method": "PUT", "path": "/api/deals/{dealId}/blocks" },
87
- { "method": "PUT", "path": "/api/deals/{dealId}/documents/{slug}/html" },
88
- { "method": "PUT", "path": "/api/deals/{dealId}/founders" },
89
- { "method": "PUT", "path": "/api/deals/{dealId}/memo" }
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/agent/activity"
20
+ },
21
+ {
22
+ "method": "GET",
23
+ "path": "/api/agent/briefing"
24
+ },
25
+ {
26
+ "method": "POST",
27
+ "path": "/api/agent/client-events"
28
+ },
29
+ {
30
+ "method": "GET",
31
+ "path": "/api/agent/explain"
32
+ },
33
+ {
34
+ "method": "GET",
35
+ "path": "/api/agent/manifest"
36
+ },
37
+ {
38
+ "method": "GET",
39
+ "path": "/api/agent/preferences"
40
+ },
41
+ {
42
+ "method": "POST",
43
+ "path": "/api/agent/preferences"
44
+ },
45
+ {
46
+ "method": "PATCH",
47
+ "path": "/api/agent/preferences/{id}"
48
+ },
49
+ {
50
+ "method": "GET",
51
+ "path": "/api/agent/skills"
52
+ },
53
+ {
54
+ "method": "GET",
55
+ "path": "/api/agent/skills/{slug}"
56
+ },
57
+ {
58
+ "method": "GET",
59
+ "path": "/api/deals"
60
+ },
61
+ {
62
+ "method": "POST",
63
+ "path": "/api/deals/create"
64
+ },
65
+ {
66
+ "method": "GET",
67
+ "path": "/api/deals/deleted"
68
+ },
69
+ {
70
+ "method": "POST",
71
+ "path": "/api/deals/update"
72
+ },
73
+ {
74
+ "method": "DELETE",
75
+ "path": "/api/deals/{dealId}"
76
+ },
77
+ {
78
+ "method": "GET",
79
+ "path": "/api/deals/{dealId}"
80
+ },
81
+ {
82
+ "method": "POST",
83
+ "path": "/api/deals/{dealId}/agent-runs/{runId}/revert"
84
+ },
85
+ {
86
+ "method": "GET",
87
+ "path": "/api/deals/{dealId}/blocks"
88
+ },
89
+ {
90
+ "method": "PUT",
91
+ "path": "/api/deals/{dealId}/blocks"
92
+ },
93
+ {
94
+ "method": "DELETE",
95
+ "path": "/api/deals/{dealId}/blocks/{blockId}"
96
+ },
97
+ {
98
+ "method": "GET",
99
+ "path": "/api/deals/{dealId}/blocks/{blockId}"
100
+ },
101
+ {
102
+ "method": "PATCH",
103
+ "path": "/api/deals/{dealId}/blocks/{blockId}"
104
+ },
105
+ {
106
+ "method": "GET",
107
+ "path": "/api/deals/{dealId}/blocks/{blockId}/history"
108
+ },
109
+ {
110
+ "method": "POST",
111
+ "path": "/api/deals/{dealId}/blocks/{blockId}/history"
112
+ },
113
+ {
114
+ "method": "POST",
115
+ "path": "/api/deals/{dealId}/blocks/{blockId}/restore"
116
+ },
117
+ {
118
+ "method": "GET",
119
+ "path": "/api/deals/{dealId}/collaborators"
120
+ },
121
+ {
122
+ "method": "POST",
123
+ "path": "/api/deals/{dealId}/collaborators"
124
+ },
125
+ {
126
+ "method": "DELETE",
127
+ "path": "/api/deals/{dealId}/collaborators/{userId}"
128
+ },
129
+ {
130
+ "method": "POST",
131
+ "path": "/api/deals/{dealId}/collaborators/{userId}/restore"
132
+ },
133
+ {
134
+ "method": "GET",
135
+ "path": "/api/deals/{dealId}/command-center"
136
+ },
137
+ {
138
+ "method": "GET",
139
+ "path": "/api/deals/{dealId}/documents"
140
+ },
141
+ {
142
+ "method": "POST",
143
+ "path": "/api/deals/{dealId}/documents"
144
+ },
145
+ {
146
+ "method": "DELETE",
147
+ "path": "/api/deals/{dealId}/documents/{slug}"
148
+ },
149
+ {
150
+ "method": "PATCH",
151
+ "path": "/api/deals/{dealId}/documents/{slug}"
152
+ },
153
+ {
154
+ "method": "DELETE",
155
+ "path": "/api/deals/{dealId}/documents/{slug}/html"
156
+ },
157
+ {
158
+ "method": "GET",
159
+ "path": "/api/deals/{dealId}/documents/{slug}/html"
160
+ },
161
+ {
162
+ "method": "PUT",
163
+ "path": "/api/deals/{dealId}/documents/{slug}/html"
164
+ },
165
+ {
166
+ "method": "GET",
167
+ "path": "/api/deals/{dealId}/documents/{slug}/html/history"
168
+ },
169
+ {
170
+ "method": "POST",
171
+ "path": "/api/deals/{dealId}/documents/{slug}/html/restore/{version}"
172
+ },
173
+ {
174
+ "method": "POST",
175
+ "path": "/api/deals/{dealId}/enrich"
176
+ },
177
+ {
178
+ "method": "GET",
179
+ "path": "/api/deals/{dealId}/facts"
180
+ },
181
+ {
182
+ "method": "POST",
183
+ "path": "/api/deals/{dealId}/facts"
184
+ },
185
+ {
186
+ "method": "PATCH",
187
+ "path": "/api/deals/{dealId}/facts/{factId}"
188
+ },
189
+ {
190
+ "method": "GET",
191
+ "path": "/api/deals/{dealId}/feed"
192
+ },
193
+ {
194
+ "method": "PUT",
195
+ "path": "/api/deals/{dealId}/founders"
196
+ },
197
+ {
198
+ "method": "POST",
199
+ "path": "/api/deals/{dealId}/ingest"
200
+ },
201
+ {
202
+ "method": "GET",
203
+ "path": "/api/deals/{dealId}/links"
204
+ },
205
+ {
206
+ "method": "POST",
207
+ "path": "/api/deals/{dealId}/links"
208
+ },
209
+ {
210
+ "method": "DELETE",
211
+ "path": "/api/deals/{dealId}/links/{linkId}"
212
+ },
213
+ {
214
+ "method": "POST",
215
+ "path": "/api/deals/{dealId}/links/{linkId}/restore"
216
+ },
217
+ {
218
+ "method": "DELETE",
219
+ "path": "/api/deals/{dealId}/memo"
220
+ },
221
+ {
222
+ "method": "GET",
223
+ "path": "/api/deals/{dealId}/memo"
224
+ },
225
+ {
226
+ "method": "POST",
227
+ "path": "/api/deals/{dealId}/memo"
228
+ },
229
+ {
230
+ "method": "PUT",
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": "POST",
263
+ "path": "/api/external/chat"
264
+ },
265
+ {
266
+ "method": "POST",
267
+ "path": "/api/external/start-session"
268
+ },
269
+ {
270
+ "method": "POST",
271
+ "path": "/api/external/upload"
272
+ },
273
+ {
274
+ "method": "GET",
275
+ "path": "/api/me"
276
+ },
277
+ {
278
+ "method": "GET",
279
+ "path": "/api/me/nominations"
280
+ },
281
+ {
282
+ "method": "GET",
283
+ "path": "/api/mentions"
284
+ },
285
+ {
286
+ "method": "GET",
287
+ "path": "/api/mentions/unread-count"
288
+ },
289
+ {
290
+ "method": "POST",
291
+ "path": "/api/mentions/{id}/resolve"
292
+ },
293
+ {
294
+ "method": "POST",
295
+ "path": "/api/nominations/{approvalId}"
296
+ },
297
+ {
298
+ "method": "GET",
299
+ "path": "/api/oauth/authorize"
300
+ },
301
+ {
302
+ "method": "POST",
303
+ "path": "/api/oauth/revoke"
304
+ },
305
+ {
306
+ "method": "POST",
307
+ "path": "/api/oauth/token"
308
+ },
309
+ {
310
+ "method": "GET",
311
+ "path": "/api/partner/approvals"
312
+ },
313
+ {
314
+ "method": "POST",
315
+ "path": "/api/partner/approvals"
316
+ },
317
+ {
318
+ "method": "GET",
319
+ "path": "/api/users"
320
+ },
321
+ {
322
+ "method": "POST",
323
+ "path": "/api/wiki/save"
324
+ },
325
+ {
326
+ "method": "GET",
327
+ "path": "/api/wiki/search"
328
+ },
329
+ {
330
+ "method": "DELETE",
331
+ "path": "/api/wiki/{slug}"
332
+ },
333
+ {
334
+ "method": "GET",
335
+ "path": "/api/wiki/{slug}"
336
+ },
337
+ {
338
+ "method": "POST",
339
+ "path": "/api/wiki/{slug}/restore"
340
+ }
90
341
  ]
91
342
  }
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "format": "llama.cli-build.v1",
3
3
  "packageName": "@llamaventures/cli",
4
- "packageVersion": "1.19.0",
5
- "sourceSha": "5f615ca16d607fc0da09887952afdf42301a43f8",
4
+ "packageVersion": "1.22.0",
5
+ "sourceSha": "3e3ede496804de031650e23fc29e1a6763c41daf",
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": "1.0.1",
11
+ "apiVersion": "3.5.0",
12
12
  "openapiVersion": "3.0.3",
13
- "sha256": "95ce3639aeee15b0fed09830116838f2a11fd2e40e4f35bf80d785664ecf8d96"
13
+ "sha256": "31c7d23ee06bd4282aa2451698604cc30df4792d4a0e9674593a809b4eb55767"
14
14
  }
15
15
  }
package/lib/client.mjs CHANGED
@@ -15,6 +15,7 @@ import { execFile as _execFile } from "child_process";
15
15
  import { promisify } from "util";
16
16
  import { createHash, randomUUID } from "crypto";
17
17
  import { getBuildInfo } from "./build-info.mjs";
18
+ import { maybeNudgeServerCompatibility } from "./server-compatibility.mjs";
18
19
 
19
20
  const execFile = promisify(_execFile);
20
21
 
@@ -198,7 +199,6 @@ function rememberAgentEvent(event) {
198
199
  ...readAgentSession(),
199
200
  sessionId: event.sessionId || currentAgentSessionId(),
200
201
  lastEventId: event.eventId,
201
- lastCandidateId: event.candidateId ?? null,
202
202
  lastCommand: event.command ?? null,
203
203
  lastQuery: event.query ?? null,
204
204
  lastSurface: event.surface ?? null,
@@ -293,6 +293,7 @@ function inferCommand(method, endpoint) {
293
293
  if (pathname === "/api/deals") return verb === "GET" ? "deal.search" : "deal.write";
294
294
  if (pathname === "/api/deals/create") return "deal.create";
295
295
  if (pathname === "/api/deals/update") return "deal.update";
296
+ if (/^\/api\/deals\/[^/]+\/ingest$/.test(pathname)) return "deal.ingest";
296
297
  if (/^\/api\/deals\/[^/]+\/threads\/[^/]+$/.test(pathname)) return "deal.agent.run";
297
298
  if (/^\/api\/deals\/[^/]+\/threads$/.test(pathname)) return "deal.thread.create";
298
299
  if (/^\/api\/deals\/[^/]+\/facts/.test(pathname)) return verb === "GET" ? "deal.fact.list" : "deal.fact.write";
@@ -523,6 +524,48 @@ export async function requestSse(method, endpoint, body, opts = {}) {
523
524
  return requestSseWithRetry(method, endpoint, body, opts, /* allowRetry */ true);
524
525
  }
525
526
 
527
+ function apiResponseError(data, status) {
528
+ const message =
529
+ typeof data === "object" && data?.error ? data.error : `HTTP ${status}`;
530
+ if (data?.code === "CLI_VERSION_UNSUPPORTED") {
531
+ const recovery = data?.recovery?.command || "npm i -g @llamaventures/cli@latest";
532
+ const error = new Error(
533
+ `Error[CLI_VERSION_UNSUPPORTED]: ${message}\n` +
534
+ `Recovery: ${recovery}\n` +
535
+ "Retry the original Llama command after upgrading.",
536
+ );
537
+ error.code = data.code;
538
+ error.details = data;
539
+ return error;
540
+ }
541
+ if (data?.code !== "MENTION_APPROVAL_REQUIRED") {
542
+ const error = new Error(message);
543
+ if (typeof data?.code === "string") error.code = data.code;
544
+ error.details = data;
545
+ return error;
546
+ }
547
+ const recipients = Array.isArray(data?.cue?.recipients)
548
+ ? data.cue.recipients
549
+ .map((recipient) => {
550
+ const name = recipient?.name || `user ${recipient?.user_id ?? "unknown"}`;
551
+ return recipient?.intent ? `${name} (${recipient.intent})` : name;
552
+ })
553
+ .join(", ")
554
+ : "unknown";
555
+ const channels = Array.isArray(data?.cue?.channels)
556
+ ? data.cue.channels.join(" + ")
557
+ : "inbox + email";
558
+ const error = new Error(
559
+ `Error[MENTION_APPROVAL_REQUIRED]: ${message}\n` +
560
+ `Recipients: ${recipients}\n` +
561
+ `Channels: ${channels}\n` +
562
+ "Ask the user for explicit permission, then retry the exact write with --cue (CLI) or cueAuthorized=true (MCP).",
563
+ );
564
+ error.code = data.code;
565
+ error.details = data;
566
+ return error;
567
+ }
568
+
526
569
  async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
527
570
  const authHeaders = await getAuthHeaders();
528
571
  if (Object.keys(authHeaders).length === 0) throw noAuthError();
@@ -538,6 +581,7 @@ async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
538
581
  },
539
582
  body: body === undefined ? undefined : JSON.stringify(body),
540
583
  });
584
+ maybeNudgeServerCompatibility(res.headers);
541
585
 
542
586
  // 401 + we sent an OAuth Bearer + this is the first attempt → try a
543
587
  // forced refresh once. Covers two cases: (a) clock skew between client
@@ -582,7 +626,7 @@ async function requestWithRetry(method, endpoint, body, opts, allowRetry) {
582
626
  data: null,
583
627
  errorMessage: message,
584
628
  });
585
- throw new Error(message);
629
+ throw apiResponseError(data, res.status);
586
630
  }
587
631
  await recordClientTelemetry({
588
632
  authHeaders,
@@ -613,6 +657,7 @@ async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
613
657
  },
614
658
  body: body === undefined ? undefined : JSON.stringify(body),
615
659
  });
660
+ maybeNudgeServerCompatibility(res.headers);
616
661
 
617
662
  if (res.status === 401 && allowRetry && (await bearerCameFromOAuth())) {
618
663
  let refreshed = null;
@@ -651,7 +696,7 @@ async function requestSseWithRetry(method, endpoint, body, opts, allowRetry) {
651
696
  data: null,
652
697
  errorMessage: message,
653
698
  });
654
- throw new Error(message);
699
+ throw apiResponseError(data, res.status);
655
700
  }
656
701
 
657
702
  const reader = res.body?.getReader();
@@ -0,0 +1,96 @@
1
+ // Consume the compatibility policy returned by Llama Command.
2
+ //
3
+ // Unlike the npm-registry nudge, this path is intentionally NOT TTY-gated:
4
+ // coding agents and MCP hosts need to see the server's recommendation too.
5
+ // It remains safe for scripts because it writes only to stderr and shares the
6
+ // existing once-per-day update stamp with lib/version-check.mjs.
7
+
8
+ import fs from "fs";
9
+ import os from "os";
10
+ import path from "path";
11
+ import { createRequire } from "module";
12
+
13
+ const DEFAULT_STAMP_FILE = path.join(os.homedir(), ".llama", ".update-check");
14
+ const THROTTLE_MS = 24 * 60 * 60 * 1000;
15
+
16
+ function installedVersion() {
17
+ try {
18
+ const requireFromHere = createRequire(import.meta.url);
19
+ return String(requireFromHere("../package.json").version || "unknown");
20
+ } catch {
21
+ return "unknown";
22
+ }
23
+ }
24
+
25
+ function readHeader(headers, name) {
26
+ if (typeof headers?.get === "function") return headers.get(name);
27
+ if (!headers || typeof headers !== "object") return null;
28
+ const match = Object.entries(headers).find(
29
+ ([key]) => key.toLowerCase() === name.toLowerCase(),
30
+ );
31
+ return match ? String(match[1]) : null;
32
+ }
33
+
34
+ export function parseServerCompatibility(headers) {
35
+ const status = readHeader(headers, "x-llama-cli-status");
36
+ if (!["ok", "stale", "unsupported", "unknown"].includes(status)) return null;
37
+ return {
38
+ status,
39
+ recommendedVersion: readHeader(headers, "x-llama-cli-recommended-version"),
40
+ minimumVersion: readHeader(headers, "x-llama-cli-minimum-version"),
41
+ upgradeCommand:
42
+ readHeader(headers, "x-llama-cli-upgrade-command") ||
43
+ "npm i -g @llamaventures/cli@latest",
44
+ };
45
+ }
46
+
47
+ function checkedRecently(stampFile, now) {
48
+ try {
49
+ const last = Number.parseInt(fs.readFileSync(stampFile, "utf8").trim(), 10);
50
+ return Number.isFinite(last) && now - last < THROTTLE_MS;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function touchStamp(stampFile, now) {
57
+ try {
58
+ fs.mkdirSync(path.dirname(stampFile), { recursive: true, mode: 0o700 });
59
+ fs.writeFileSync(stampFile, `${now}\n`, { mode: 0o600 });
60
+ } catch {
61
+ // Best-effort only. Compatibility hints must never break a command.
62
+ }
63
+ }
64
+
65
+ export function formatServerCompatibilityNudge(policy, currentVersion = installedVersion()) {
66
+ if (policy?.status !== "stale" || !policy.recommendedVersion) return null;
67
+ return (
68
+ `warning[CLI_UPDATE_RECOMMENDED]: Llama Command recommends ` +
69
+ `@llamaventures/cli ${policy.recommendedVersion}; this client is ${currentVersion}. ` +
70
+ `Upgrade: ${policy.upgradeCommand}`
71
+ );
72
+ }
73
+
74
+ export function maybeNudgeServerCompatibility(headers, options = {}) {
75
+ try {
76
+ const env = options.env ?? process.env;
77
+ if (env.LLAMA_NO_UPDATE_CHECK) return false;
78
+ const policy = parseServerCompatibility(headers);
79
+ const nudge = formatServerCompatibilityNudge(
80
+ policy,
81
+ options.currentVersion ?? installedVersion(),
82
+ );
83
+ if (!nudge) return false;
84
+ const now = options.now ?? Date.now();
85
+ const stampFile = options.stampFile ?? DEFAULT_STAMP_FILE;
86
+ if (checkedRecently(stampFile, now)) return false;
87
+ touchStamp(stampFile, now);
88
+ const write = options.write ?? ((line) => process.stderr.write(`${line}\n`));
89
+ write(nudge);
90
+ return true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ export const SERVER_COMPATIBILITY_STAMP_FILE = DEFAULT_STAMP_FILE;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@llamaventures/cli",
3
- "version": "1.19.0",
3
+ "version": "1.22.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/verify-core-api-contract.test.mjs && node scripts/verify-core-api-contract.mjs",
9
+ "test:contract": "node --test scripts/build-manifest.test.mjs scripts/core-api-call-sites.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",