@llamaventures/cli 1.20.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.
- package/AGENT_BRIEFING.md +7 -4
- package/CHANGELOG.md +25 -1
- package/README.md +1 -0
- package/README.zh-CN.md +1 -0
- package/bin/llama-mcp.mjs +33 -14
- package/bin/llama.mjs +18 -11
- package/contracts/core-api.json +4 -4
- package/lib/build-manifest.json +4 -4
- package/lib/client.mjs +47 -2
- package/lib/server-compatibility.mjs +96 -0
- package/package.json +2 -2
package/AGENT_BRIEFING.md
CHANGED
|
@@ -8,6 +8,7 @@ You are not just an AI assistant. You're an **extension of a team member** — w
|
|
|
8
8
|
|
|
9
9
|
- **Your access scope is whatever your token allows.** Run `llama auth status` first; the response shows your role, identity, and active token source.
|
|
10
10
|
- **All your writes are logged.** `auth_events` and `deal_events` capture everything. Pipeline data can always be traced back to who/what changed it.
|
|
11
|
+
- **Never cue by implication.** A teammate cue creates an LC inbox item and may queue email under the user's identity. Cue a specific person only when the user explicitly authorized it in the current request. Referring to a teammate, ownership, responsibility, or writing their name is not permission.
|
|
11
12
|
- **Public surfaces stay clean.** Anything that leaves the workspace — public repos, npm packages, external artifacts, shared links — must not leak internal people, deals, private URLs, or workflow nuance.
|
|
12
13
|
- **Be direct, terse, action-oriented.** Save your words for the genuine judgment calls.
|
|
13
14
|
- **Critical when thinking, helpful when executing.** Push back on weak logic, then ship the work cleanly.
|
|
@@ -133,7 +134,7 @@ These three look similar but land in different surfaces. Don't infer from the co
|
|
|
133
134
|
|---|---|---|
|
|
134
135
|
| File a **source packet** with multiple facts and/or one note | `llama deal ingest <dealId> --file packet.json` | Facts + optional note → deal **Feed**, atomically and retry-safe |
|
|
135
136
|
| Record a **sourced, verifiable fact** | `llama deal fact add <dealId> --category <cat> --claim "…" --source "deck p3" --source-url <url>` | Facts → deal **Feed** (FACT card) + citable in the **Memo** |
|
|
136
|
-
| Leave a **comment / opinion / question / reaction** for the team | `llama post <dealId> "…"
|
|
137
|
+
| Leave a **comment / opinion / question / reaction** for the team | `llama post <dealId> "…"`; add `--cue` only after explicit permission | Posts → deal **Feed** (POST card); authorized cues create email + UI badge |
|
|
137
138
|
| Write **narrative that belongs in the IC memo** | `llama brief add-text <dealId> --heading "…" --body "…"` | Brief blocks → **Memo tab only — NOT in the Feed** |
|
|
138
139
|
|
|
139
140
|
⚠️ The trap: `brief add-text` is **not** visible in the Activity Feed. If the team should see it in the feed, use `llama post`. If it's a claim that needs a source + verification, use `llama deal fact add`. (It's `deal fact add`, not `fact-add`.)
|
|
@@ -149,7 +150,7 @@ The table below details the exact CLI for each destination.
|
|
|
149
150
|
| Insights, decisions, framework improvements | Wiki (markdown) | `llama wiki save <slug> --content "..."` (with attribution — see below) |
|
|
150
151
|
| **HTML wiki entry — standalone HTML page hosted at `/wiki/<slug>`** (sector landscape, market map, dashboard, hand-styled thesis page) | **Wiki (HTML)** | `llama wiki save <slug> --title "..." --file <path.html> --sources "..."`. Auto-detects content_type=html from extension. Public page is full-viewport sandboxed iframe takeover (no wiki chrome). Sources/status/title still required; appears in `wiki search` + backlinks. Use when the user says "deploy this HTML to wiki", "wiki 词条", "make this page a wiki entry". HTML must be self-contained (inline CSS/JS, image data URIs or external URLs) — asset bundles aren't supported on wiki yet. **Native comments + working in-page (#) anchor links are injected automatically** — readers discuss inline and the table of contents scrolls; you don't wire anything up (pages that already embed the comment widget are left as-is). |
|
|
151
152
|
| Large files (deck / PDF / transcript) | Drive deal folder | the deal's `folder_url` (from `llama deal show`) → upload via your filesystem / Drive tool |
|
|
152
|
-
| Cross-team
|
|
153
|
+
| Cross-team cues | Inbox + email | `llama post <dealId> "@<teammate> ..." --cue` — use `--cue` only after the user explicitly authorized that recipient |
|
|
153
154
|
|
|
154
155
|
### Attribution format (required for wiki writes)
|
|
155
156
|
|
|
@@ -167,8 +168,9 @@ Content. One block, one attribution. Don't mix fact and opinion in a single bloc
|
|
|
167
168
|
| Level | Type | Behaviour |
|
|
168
169
|
|---|---|---|
|
|
169
170
|
| **L0** | Reads (`search`, `show`, `list`) | Just do it. Don't announce. Integrate the result into your reply. |
|
|
170
|
-
| **L1** | Low-risk writes (timeline post, wiki append, add fact, add tag) | Do it, then tell the user **one line** afterward. |
|
|
171
|
+
| **L1** | Low-risk writes (cue-free timeline post, wiki append, add fact, add tag) | Do it, then tell the user **one line** afterward. |
|
|
171
172
|
| **L2** | Medium-risk writes (new deal, change stage, change owner, new wiki page) | Ask once: "Y/n — I'm about to do X". On yes, execute and report. Don't re-ask details. |
|
|
173
|
+
| **L2-cue** | Any assistant-authored write that resolves to a teammate cue | Require explicit permission for the resolved people and inbox/email channels. Retry the exact write with `--cue` only after yes. If the user already explicitly asked to cue them, do not ask twice. |
|
|
172
174
|
| **L3** | High-risk (delete deal, bulk change, overwrite someone else's wiki, force-push, regulatory-relevant) | Detailed explanation + explicit confirmation. Provide a dry-run / undo path when possible. |
|
|
173
175
|
|
|
174
176
|
When in doubt, lean to a higher level (more confirmation), not lower.
|
|
@@ -290,7 +292,8 @@ llama wiki restore <slug> [--lang en|zh]
|
|
|
290
292
|
|
|
291
293
|
# Timeline + posts
|
|
292
294
|
llama timeline <dealId>
|
|
293
|
-
llama post <dealId> "message"
|
|
295
|
+
llama post <dealId> "message" # cue-free note
|
|
296
|
+
llama post <dealId> "@name respond" --cue # only after explicit user approval
|
|
294
297
|
|
|
295
298
|
# Mentions inbox
|
|
296
299
|
llama mentions
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.22.0] - 2026-07-28
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Consume Llama Command's server-owned CLI/MCP compatibility headers on every
|
|
13
|
+
shared API request. Recommended upgrades are surfaced on stderr at most once
|
|
14
|
+
per day, including for non-TTY coding agents, while structured stdout remains
|
|
15
|
+
unchanged.
|
|
16
|
+
- Surface hard `CLI_VERSION_UNSUPPORTED` responses with a stable error code,
|
|
17
|
+
exact upgrade command, and retry instruction.
|
|
18
|
+
|
|
19
|
+
## [1.21.0] - 2026-07-24
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Add explicit `--cue` (CLI) and `cueAuthorized` (MCP) authorization for
|
|
23
|
+
teammate cues in posts and brief writes. Cue-free writes remain unchanged;
|
|
24
|
+
agents must obtain permission for the resolved recipients before setting it.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Agent onboarding now treats teammate cues as approval-required outbound
|
|
28
|
+
actions rather than ordinary low-risk timeline posts. Structured
|
|
29
|
+
`MENTION_APPROVAL_REQUIRED` errors retain recipient and channel context.
|
|
30
|
+
|
|
9
31
|
## [1.20.0] - 2026-07-22
|
|
10
32
|
|
|
11
33
|
### Added
|
|
@@ -544,7 +566,9 @@ assistant-drafted (🤖) and enforces the AI trust ceiling.
|
|
|
544
566
|
|
|
545
567
|
---
|
|
546
568
|
|
|
547
|
-
[Unreleased]: https://github.com/Llama-Ventures/llama-cli/compare/v1.
|
|
569
|
+
[Unreleased]: https://github.com/Llama-Ventures/llama-cli/compare/v1.22.0...HEAD
|
|
570
|
+
[1.22.0]: https://github.com/Llama-Ventures/llama-cli/compare/v1.21.0...v1.22.0
|
|
571
|
+
[1.21.0]: https://github.com/Llama-Ventures/llama-cli/compare/v1.20.0...v1.21.0
|
|
548
572
|
[1.18.1]: https://github.com/Llama-Ventures/llama-cli/compare/v1.18.0...v1.18.1
|
|
549
573
|
[1.18.0]: https://github.com/Llama-Ventures/llama-cli/compare/v1.17.3...v1.18.0
|
|
550
574
|
[1.17.3]: https://github.com/Llama-Ventures/llama-cli/compare/v1.17.2...v1.17.3
|
package/README.md
CHANGED
|
@@ -102,6 +102,7 @@ llama deal ingest <dealId> --file packet.json # atomic multi-fact + optional Fe
|
|
|
102
102
|
llama deal fact add <dealId> --category funding --claim "Raised a seed round" --source "deck p3" --source-url https://...
|
|
103
103
|
llama deal update <dealId> status Diligence
|
|
104
104
|
llama post <dealId> "note body"
|
|
105
|
+
llama post <dealId> "@name please respond" --cue # only after explicit approval
|
|
105
106
|
llama brief add-text <dealId> --heading "..." --body "..."
|
|
106
107
|
llama wiki search "<query>"
|
|
107
108
|
llama wiki save <slug> --title "..." --content "..."
|
package/README.zh-CN.md
CHANGED
|
@@ -124,6 +124,7 @@ llama deal ingest <dealId> --file packet.json # 多条 facts + 可选 Feed note
|
|
|
124
124
|
llama deal fact add <dealId> --category funding --claim "Raised a seed round" --source "deck p3" --source-url https://...
|
|
125
125
|
llama deal update <dealId> status Diligence
|
|
126
126
|
llama post <dealId> "备注内容"
|
|
127
|
+
llama post <dealId> "@name 请回复" --cue # 仅在用户明确授权后使用
|
|
127
128
|
llama brief add-text <dealId> --heading "..." --body "..."
|
|
128
129
|
llama wiki search "<查询词>"
|
|
129
130
|
llama wiki save <slug> --title "..." --content "..."
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -161,7 +161,7 @@ async function runDealAgentTool({ dealId, message, title = "MCP agent run" }) {
|
|
|
161
161
|
// full-array PUTs (no POST), so we GET current blocks, prepend the new
|
|
162
162
|
// one (matches UI default since 2026-05-03), and PUT the merged array.
|
|
163
163
|
// Server stamps identity meta on PUT; we don't send any.
|
|
164
|
-
async function addBriefBlock(dealId, block) {
|
|
164
|
+
async function addBriefBlock(dealId, block, cueAuthorized = false) {
|
|
165
165
|
try {
|
|
166
166
|
const id = globalThis.crypto.randomUUID();
|
|
167
167
|
const cur = await request("GET", `/api/deals/${encodeURIComponent(dealId)}/blocks`);
|
|
@@ -169,7 +169,7 @@ async function addBriefBlock(dealId, block) {
|
|
|
169
169
|
const result = await request(
|
|
170
170
|
"PUT",
|
|
171
171
|
`/api/deals/${encodeURIComponent(dealId)}/blocks`,
|
|
172
|
-
{ blocks: [{ id, ...block }, ...existing] }
|
|
172
|
+
{ blocks: [{ id, ...block }, ...existing], cue_authorized: cueAuthorized === true }
|
|
173
173
|
);
|
|
174
174
|
const text = JSON.stringify(
|
|
175
175
|
{ ok: result?.ok ?? true, id, count: result?.count ?? existing.length + 1 },
|
|
@@ -681,10 +681,13 @@ server.registerTool(
|
|
|
681
681
|
dealId: z.string(),
|
|
682
682
|
heading: z.string().optional().describe("optional block heading"),
|
|
683
683
|
body: z.string().describe("markdown body"),
|
|
684
|
+
cueAuthorized: z.boolean().optional().describe(
|
|
685
|
+
"true only after the user explicitly approved every resolved cue recipient",
|
|
686
|
+
),
|
|
684
687
|
},
|
|
685
688
|
},
|
|
686
|
-
async ({ dealId, heading, body }) =>
|
|
687
|
-
addBriefBlock(dealId, { type: "text", heading: heading ?? "", body })
|
|
689
|
+
async ({ dealId, heading, body, cueAuthorized }) =>
|
|
690
|
+
addBriefBlock(dealId, { type: "text", heading: heading ?? "", body }, cueAuthorized)
|
|
688
691
|
);
|
|
689
692
|
|
|
690
693
|
server.registerTool(
|
|
@@ -696,10 +699,13 @@ server.registerTool(
|
|
|
696
699
|
dealId: z.string(),
|
|
697
700
|
url: z.string(),
|
|
698
701
|
label: z.string().optional().describe("optional human-readable label"),
|
|
702
|
+
cueAuthorized: z.boolean().optional().describe(
|
|
703
|
+
"true only after the user explicitly approved every resolved cue recipient",
|
|
704
|
+
),
|
|
699
705
|
},
|
|
700
706
|
},
|
|
701
|
-
async ({ dealId, url, label }) =>
|
|
702
|
-
addBriefBlock(dealId, { type: "link", url, label: label ?? "" })
|
|
707
|
+
async ({ dealId, url, label, cueAuthorized }) =>
|
|
708
|
+
addBriefBlock(dealId, { type: "link", url, label: label ?? "" }, cueAuthorized)
|
|
703
709
|
);
|
|
704
710
|
|
|
705
711
|
server.registerTool(
|
|
@@ -712,10 +718,13 @@ server.registerTool(
|
|
|
712
718
|
tone: z.string().describe("insight | warning | info | success"),
|
|
713
719
|
heading: z.string().optional(),
|
|
714
720
|
body: z.string(),
|
|
721
|
+
cueAuthorized: z.boolean().optional().describe(
|
|
722
|
+
"true only after the user explicitly approved every resolved cue recipient",
|
|
723
|
+
),
|
|
715
724
|
},
|
|
716
725
|
},
|
|
717
|
-
async ({ dealId, tone, heading, body }) =>
|
|
718
|
-
addBriefBlock(dealId, { type: "callout", tone, heading: heading ?? "", body })
|
|
726
|
+
async ({ dealId, tone, heading, body, cueAuthorized }) =>
|
|
727
|
+
addBriefBlock(dealId, { type: "callout", tone, heading: heading ?? "", body }, cueAuthorized)
|
|
719
728
|
);
|
|
720
729
|
|
|
721
730
|
server.registerTool(
|
|
@@ -738,9 +747,12 @@ server.registerTool(
|
|
|
738
747
|
locked: z.boolean().optional(),
|
|
739
748
|
hidden: z.boolean().optional(),
|
|
740
749
|
sourceSection: z.string().optional(),
|
|
750
|
+
cueAuthorized: z.boolean().optional().describe(
|
|
751
|
+
"true only after the user explicitly approved every resolved cue recipient",
|
|
752
|
+
),
|
|
741
753
|
},
|
|
742
754
|
},
|
|
743
|
-
async ({ dealId, blockId, heading, body, url, label, description, tone, locked, hidden, sourceSection }) => {
|
|
755
|
+
async ({ dealId, blockId, heading, body, url, label, description, tone, locked, hidden, sourceSection, cueAuthorized }) => {
|
|
744
756
|
const patch = {};
|
|
745
757
|
for (const [k, v] of Object.entries({ heading, body, url, label, description, tone })) {
|
|
746
758
|
if (v !== undefined) patch[k] = v;
|
|
@@ -750,6 +762,7 @@ server.registerTool(
|
|
|
750
762
|
if (hidden !== undefined) meta.hidden = hidden;
|
|
751
763
|
if (sourceSection !== undefined) meta.sourceSection = sourceSection;
|
|
752
764
|
if (Object.keys(meta).length > 0) patch.meta = meta;
|
|
765
|
+
if (cueAuthorized === true) patch.cue_authorized = true;
|
|
753
766
|
return callApi(
|
|
754
767
|
"PATCH",
|
|
755
768
|
`/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`,
|
|
@@ -988,16 +1001,22 @@ server.registerTool(
|
|
|
988
1001
|
"post",
|
|
989
1002
|
{
|
|
990
1003
|
description:
|
|
991
|
-
"Post a message to a deal's timeline.
|
|
992
|
-
"
|
|
993
|
-
"
|
|
1004
|
+
"Post a message to a deal's timeline. Cue-free posts are autonomous. " +
|
|
1005
|
+
"Explicit or implicit teammate cues create inbox/email notifications and require " +
|
|
1006
|
+
"cueAuthorized=true only after explicit user permission.",
|
|
994
1007
|
inputSchema: {
|
|
995
1008
|
dealId: z.string(),
|
|
996
1009
|
message: z.string(),
|
|
1010
|
+
cueAuthorized: z.boolean().optional().describe(
|
|
1011
|
+
"true only after the user explicitly approved cueing every resolved recipient",
|
|
1012
|
+
),
|
|
997
1013
|
},
|
|
998
1014
|
},
|
|
999
|
-
async ({ dealId, message }) =>
|
|
1000
|
-
callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/posts`, {
|
|
1015
|
+
async ({ dealId, message, cueAuthorized }) =>
|
|
1016
|
+
callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/posts`, {
|
|
1017
|
+
message,
|
|
1018
|
+
cue_authorized: cueAuthorized === true,
|
|
1019
|
+
})
|
|
1001
1020
|
);
|
|
1002
1021
|
|
|
1003
1022
|
// ============================================================
|
package/bin/llama.mjs
CHANGED
|
@@ -457,16 +457,17 @@ Approvals (partner queue — self-claim approvals):
|
|
|
457
457
|
|
|
458
458
|
Timeline / Posts:
|
|
459
459
|
llama timeline <dealId> # full unified feed
|
|
460
|
-
llama post <dealId> "message body" [--link url] [--link-name "name"]
|
|
460
|
+
llama post <dealId> "message body" [--link url] [--link-name "name"] [--cue]
|
|
461
|
+
# --cue only after explicit user approval
|
|
461
462
|
|
|
462
463
|
Brief blocks:
|
|
463
464
|
llama brief blocks <dealId> # list current block array
|
|
464
465
|
llama brief block <dealId> <blockId> # fetch one block's body (manifest in 'llama deal show')
|
|
465
|
-
llama brief add-text <dealId> --heading "..." --body "..."
|
|
466
|
-
llama brief add-link <dealId> --url "..." --label "..." [--description "..."]
|
|
467
|
-
llama brief add-embed <dealId> --url "..." [--label "..."]
|
|
468
|
-
llama brief add-callout <dealId> --tone insight|info|warning|success --heading "..." --body "..."
|
|
469
|
-
llama brief edit <dealId> <blockId> [--heading ...] [--body ...] [--url ...] [--label ...] [--tone ...]
|
|
466
|
+
llama brief add-text <dealId> --heading "..." --body "..." [--cue]
|
|
467
|
+
llama brief add-link <dealId> --url "..." --label "..." [--description "..."] [--cue]
|
|
468
|
+
llama brief add-embed <dealId> --url "..." [--label "..."] [--cue]
|
|
469
|
+
llama brief add-callout <dealId> --tone insight|info|warning|success --heading "..." --body "..." [--cue]
|
|
470
|
+
llama brief edit <dealId> <blockId> [--heading ...] [--body ...] [--url ...] [--label ...] [--tone ...] [--cue]
|
|
470
471
|
[--source-section <key>] [--lock|--unlock] [--hide|--unhide]
|
|
471
472
|
llama brief delete <dealId> <blockId>
|
|
472
473
|
llama brief history <dealId> <blockId> [--limit 50] # prior versions of this block (newest first)
|
|
@@ -1934,7 +1935,7 @@ async function main() {
|
|
|
1934
1935
|
const { flags, positional } = parseFlags(rest);
|
|
1935
1936
|
const body = positional[0];
|
|
1936
1937
|
if (!dealId || !body) {
|
|
1937
|
-
throw new Error(`Usage: llama post <dealId> "message body" [--link url] [--link-name "name"]`);
|
|
1938
|
+
throw new Error(`Usage: llama post <dealId> "message body" [--link url] [--link-name "name"] [--cue]`);
|
|
1938
1939
|
}
|
|
1939
1940
|
const attachments = flags.link
|
|
1940
1941
|
? [{ url: String(flags.link), name: flags["link-name"] ? String(flags["link-name"]) : String(flags.link) }]
|
|
@@ -1942,7 +1943,7 @@ async function main() {
|
|
|
1942
1943
|
print(await request(
|
|
1943
1944
|
"POST",
|
|
1944
1945
|
`/api/deals/${encodeURIComponent(dealId)}/posts`,
|
|
1945
|
-
{ body, attachments }
|
|
1946
|
+
{ body, attachments, cue_authorized: flags.cue === true }
|
|
1946
1947
|
));
|
|
1947
1948
|
return;
|
|
1948
1949
|
}
|
|
@@ -2168,7 +2169,10 @@ Routing — is this the right command?
|
|
|
2168
2169
|
if (!cur) cur = await request("GET", `/api/deals/${encodeURIComponent(dealId)}/blocks`);
|
|
2169
2170
|
const existing = cur.blocks ?? [];
|
|
2170
2171
|
const next = position === "top" ? [block, ...existing] : [...existing, block];
|
|
2171
|
-
print(await request("PUT", `/api/deals/${encodeURIComponent(dealId)}/blocks`, {
|
|
2172
|
+
print(await request("PUT", `/api/deals/${encodeURIComponent(dealId)}/blocks`, {
|
|
2173
|
+
blocks: next,
|
|
2174
|
+
cue_authorized: flags.cue === true,
|
|
2175
|
+
}));
|
|
2172
2176
|
console.log(`Created block ${id}`);
|
|
2173
2177
|
return;
|
|
2174
2178
|
}
|
|
@@ -2178,7 +2182,7 @@ Routing — is this the right command?
|
|
|
2178
2182
|
const dealId = rest[0];
|
|
2179
2183
|
const blockId = rest[1];
|
|
2180
2184
|
if (!dealId || !blockId) {
|
|
2181
|
-
throw new Error("Usage: llama brief edit <dealId> <blockId> [--heading ...] [--body ...] [--url ...] [--label ...] [--description ...] [--tone ...] [--source-section ...] [--lock|--unlock] [--hide|--unhide]");
|
|
2185
|
+
throw new Error("Usage: llama brief edit <dealId> <blockId> [--heading ...] [--body ...] [--url ...] [--label ...] [--description ...] [--tone ...] [--source-section ...] [--lock|--unlock] [--hide|--unhide] [--cue]");
|
|
2182
2186
|
}
|
|
2183
2187
|
const patch = {};
|
|
2184
2188
|
for (const k of ["heading", "body", "url", "label", "description", "tone"]) {
|
|
@@ -2198,8 +2202,11 @@ Routing — is this the right command?
|
|
|
2198
2202
|
metaPatch.sourceSection = String(flags["source-section"]);
|
|
2199
2203
|
}
|
|
2200
2204
|
if (Object.keys(metaPatch).length > 0) patch.meta = metaPatch;
|
|
2205
|
+
if (flags.cue === true) patch.cue_authorized = true;
|
|
2201
2206
|
|
|
2202
|
-
if (Object.keys(patch).length === 0
|
|
2207
|
+
if (Object.keys(patch).length === 0 || (Object.keys(patch).length === 1 && patch.cue_authorized === true)) {
|
|
2208
|
+
throw new Error("at least one field flag required");
|
|
2209
|
+
}
|
|
2203
2210
|
print(await request("PATCH", `/api/deals/${encodeURIComponent(dealId)}/blocks/${encodeURIComponent(blockId)}`, patch));
|
|
2204
2211
|
return;
|
|
2205
2212
|
}
|
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": "3.
|
|
4
|
+
"apiVersion": "3.5.0",
|
|
5
5
|
"openapiVersion": "3.0.3",
|
|
6
|
-
"sha256": "
|
|
7
|
-
"pathCount":
|
|
8
|
-
"operationCount":
|
|
6
|
+
"sha256": "31c7d23ee06bd4282aa2451698604cc30df4792d4a0e9674593a809b4eb55767",
|
|
7
|
+
"pathCount": 216,
|
|
8
|
+
"operationCount": 281
|
|
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": "1.
|
|
5
|
-
"sourceSha": "
|
|
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": "3.
|
|
11
|
+
"apiVersion": "3.5.0",
|
|
12
12
|
"openapiVersion": "3.0.3",
|
|
13
|
-
"sha256": "
|
|
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
|
|
|
@@ -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
|
|
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
|
|
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.
|
|
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",
|