@llamaventures/cli 1.22.1 → 1.23.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.
- package/AGENT_BRIEFING.md +2 -0
- package/CHANGELOG.md +42 -0
- package/README.md +8 -1
- package/README.zh-CN.md +4 -1
- package/bin/llama-mcp.mjs +110 -5
- package/bin/llama.mjs +124 -14
- package/contracts/core-api.json +4 -4
- package/contracts/investment-workflow-v2.md +19 -0
- package/contracts/required-operations.json +12 -0
- package/lib/build-manifest.json +4 -4
- package/package.json +2 -2
package/AGENT_BRIEFING.md
CHANGED
|
@@ -66,6 +66,7 @@ A teammate says "I just met them and heard…" or pastes a chunk of notes. Your
|
|
|
66
66
|
2. **Split what they gave you into two kinds** — this is the whole data model:
|
|
67
67
|
- **A source packet with 2+ facts, or facts + a note → ingest once.** Build a JSON object with `source`, `facts`, and optional `note`, then run `llama deal ingest <dealId> --file <packet.json>`. This is the preferred agent path: one atomic, deduplicated, retry-safe commit. Reuse an explicit `idempotencyKey` when retrying the same material. The matching MCP tool is `deal_ingest` and uses the same packet shape.
|
|
68
68
|
- **Verifiable claims → facts.** `llama deal fact add <dealId> --category <cat> --claim "…" --source "<where it came from>" --source-url <url>`. A claim someone *relayed* ("their ARR is $3M", "raised from a16z") is a fact at **unverified** trust — it's hearsay until checked. Pass `--attested` ONLY if you actually verified it against a source yourself. In raw API terms, the fact text field is `claim` (`value` is only a compatibility alias), `source` is the human-readable provenance label, and `sourceUrl` is the canonical URL.
|
|
69
|
+
- **Trust and contest are separate axes.** A *contested* fact is excluded from what the Deal Agent treats as current, whatever its trust rung — so a fact can read as both `human-vouched` and suppressed. `fact verify --status confirmed` does **not** lift a contest; the response tells you when one is still standing. To lift one: `llama deal fact uncontest <dealId> <factId> --reason "…"`. The lifted contest is kept in the fact's history, not erased.
|
|
69
70
|
- **Their judgment / impression → a note.** `llama post <dealId> "…"`. "Founder seemed evasive", "I'd lean pass", "worth a second meeting" — opinion, not fact. Attributed, never "verified".
|
|
70
71
|
- A pasted blob → pull the verifiable claims out as facts, capture their take as a note.
|
|
71
72
|
3. **Read it back before you claim it's saved.** A generic tool call returning `{ok:true}` is NOT proof the content is where the user will look for it. `deal ingest` is the exception because its response is built from the rows and note block read back after commit: confirm `createdFacts`, `skippedFacts`, `note`, and `summary` in that receipt. For every other write, run `llama deal feed <dealId>` and confirm your fact/note actually appears. Never say "记好了 / saved" from a request acknowledgment alone — the #1 failure is an agent writing to the wrong surface (e.g. the brief, which is the Memo and does NOT appear in the feed) and reporting success anyway. If the authoritative ingest receipt or the feed read-back does not contain the material, fix it before reporting success.
|
|
@@ -217,6 +218,7 @@ llama deal create "Company" --description "..." --source-direction Outbound --st
|
|
|
217
218
|
llama deal create "Company" --description "..." --source-direction Outbound --status Outreached --deal-owner "owner@llamaventures.vc"
|
|
218
219
|
llama deal create "Company" --description "..." --source-direction Inbound --status Sourced --deal-owner "Exact Name"
|
|
219
220
|
llama deal update <dealId> <field> <value>
|
|
221
|
+
llama deal fact uncontest <dealId> <factId> --reason "..." # lift a wrong contest; verify does NOT
|
|
220
222
|
# writable: status theirStage stage notes dealOwner source sourceDirection description website
|
|
221
223
|
# location founders proposedAmount roundSize valuation sector subsector
|
|
222
224
|
# foundedYear leadInvestor investors (each write logs a deal_events row)
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,48 @@ this project adheres to [Semantic Versioning](https://semver.org).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.23.1] - 2026-08-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Add the canonical `llama workflow` command family and typed MCP tools for
|
|
13
|
+
reading and changing Investment Workflow V2 through one versioned API.
|
|
14
|
+
- Add audited workflow initialization for legacy migration and formal post-IC
|
|
15
|
+
execution status updates for Term Sheet, Verbal Commit, and Invested.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Reject direct Deal status writes and retired `stage_gates` / `stage4_gate`
|
|
19
|
+
mutations so CLI and MCP cannot bypass the server state machine.
|
|
20
|
+
|
|
21
|
+
## [1.23.0] - 2026-08-05
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `llama deal fact uncontest <dealId> <factId> --reason "..."` lifts a contest
|
|
25
|
+
that turned out to be wrong. Contesting a fact removes it from what the Deal
|
|
26
|
+
Agent treats as current, and until now nothing could put it back — so a true
|
|
27
|
+
claim contested on weak evidence stayed suppressed in every regenerated brief
|
|
28
|
+
and memo. The lifted contest is kept in the fact's history server-side, not
|
|
29
|
+
erased. Requires Llama Command >= 3.21.0.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- `--help` and `AGENT_BRIEFING.md` now state that **trust and contest are
|
|
33
|
+
separate axes**: `fact verify --status confirmed` does NOT lift a contest, and
|
|
34
|
+
a contested fact stays excluded from synthesis whatever its trust rung. The
|
|
35
|
+
server now returns a warning when a verify leaves a contest standing.
|
|
36
|
+
- `deal update` help says what `notes` is for — the one-line Summary headline at
|
|
37
|
+
the top of the deal page (~280 chars), not a place for meeting notes. It was
|
|
38
|
+
previously listed among 20 field names with no semantics, so the honest
|
|
39
|
+
reading was "notes" and long text landed in a headline slot.
|
|
40
|
+
|
|
41
|
+
### Note
|
|
42
|
+
This release also carries the unpublished 1.22.2 fix below.
|
|
43
|
+
|
|
44
|
+
## [1.22.2] - 2026-08-04
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- `llama claim` and `llama nominate` now require `--reason` and send it to
|
|
48
|
+
Llama Command's audited Owner-label endpoint. The CLI no longer emits a
|
|
49
|
+
request that Core must reject before recording the responsibility change.
|
|
50
|
+
|
|
9
51
|
## [1.22.1] - 2026-08-04
|
|
10
52
|
|
|
11
53
|
### Changed
|
package/README.md
CHANGED
|
@@ -100,7 +100,10 @@ llama activity updated-deals --since 7d # meaningful updates grouped by deal
|
|
|
100
100
|
llama deal create "Acme AI" --source alex --deal-owner owner@llamaventures.vc --source-direction Outbound --status Interested
|
|
101
101
|
llama deal ingest <dealId> --file packet.json # atomic multi-fact + optional Feed note; retry-safe
|
|
102
102
|
llama deal fact add <dealId> --category funding --claim "Raised a seed round" --source "deck p3" --source-url https://...
|
|
103
|
-
llama
|
|
103
|
+
llama workflow show <dealId>
|
|
104
|
+
llama workflow initialize <dealId> --reason "Migrate legacy workflow state without changing stage"
|
|
105
|
+
llama workflow proceed <dealId> --transition begin_preliminary --reason "Ready to begin research"
|
|
106
|
+
llama workflow execution-status <dealId> invested --reason "Wire confirmed"
|
|
104
107
|
llama post <dealId> "note body"
|
|
105
108
|
llama post <dealId> "@name please respond" --cue # only after explicit approval
|
|
106
109
|
llama brief add-text <dealId> --heading "..." --body "..."
|
|
@@ -115,6 +118,10 @@ Status vocabulary — `Interested`: tracked before any contact ·
|
|
|
115
118
|
signal exists. `sourceDirection` is separate: `Inbound` came to the firm,
|
|
116
119
|
`Outbound` we reached out first.
|
|
117
120
|
|
|
121
|
+
Deal stage is controlled only by Investment Workflow V2. Direct
|
|
122
|
+
`deal update ... status ...` and legacy `stage_gates` writes are rejected;
|
|
123
|
+
use `llama workflow show` followed by the matching formal workflow command.
|
|
124
|
+
|
|
118
125
|
For a deck, meeting note, email, or research packet, prefer `deal ingest` over a
|
|
119
126
|
loop of `deal fact add` calls. The JSON object accepts `source`, up to 50
|
|
120
127
|
`facts`, an optional `note`, and an optional `idempotencyKey`. The server commits
|
package/README.zh-CN.md
CHANGED
|
@@ -122,7 +122,10 @@ llama activity updated-deals --since 7d # 按 deal 聚合的实质更新
|
|
|
122
122
|
llama deal create "Acme AI" --source alex --deal-owner owner@llamaventures.vc --source-direction Outbound --status Interested
|
|
123
123
|
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
|
-
llama
|
|
125
|
+
llama workflow show <dealId>
|
|
126
|
+
llama workflow initialize <dealId> --reason "迁移旧状态,不改变当前阶段"
|
|
127
|
+
llama workflow proceed <dealId> --transition begin_preliminary --reason "开始初步研究"
|
|
128
|
+
llama workflow execution-status <dealId> invested --reason "已确认打款"
|
|
126
129
|
llama post <dealId> "备注内容"
|
|
127
130
|
llama post <dealId> "@name 请回复" --cue # 仅在用户明确授权后使用
|
|
128
131
|
llama brief add-text <dealId> --heading "..." --body "..."
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -74,6 +74,27 @@ function jsonResult(value, isError = false) {
|
|
|
74
74
|
return textResult(JSON.stringify(value, null, 2), isError);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
async function callWorkflow(dealId, type, fields) {
|
|
78
|
+
try {
|
|
79
|
+
const path = `/api/deals/${encodeURIComponent(dealId)}/workflow`;
|
|
80
|
+
// @core-api-operation GET /api/deals/{dealId}/workflow
|
|
81
|
+
const current = await request("GET", path);
|
|
82
|
+
const expectedRevision = current?.workflow?.revision;
|
|
83
|
+
if (!Number.isInteger(expectedRevision)) {
|
|
84
|
+
return textResult("Error: Investment Workflow V2 is not initialized for this deal.", true);
|
|
85
|
+
}
|
|
86
|
+
// @core-api-operation POST /api/deals/{dealId}/workflow
|
|
87
|
+
return callApi("POST", path, {
|
|
88
|
+
type,
|
|
89
|
+
requestId: `mcp:${type}:${randomUUID()}`,
|
|
90
|
+
expectedRevision,
|
|
91
|
+
...fields,
|
|
92
|
+
});
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return textResult(`Error: ${err?.message ?? String(err)}`, true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
function splitSources(value) {
|
|
78
99
|
if (Array.isArray(value)) return value.filter(Boolean);
|
|
79
100
|
if (!value || value === true) return undefined;
|
|
@@ -457,8 +478,7 @@ server.registerTool(
|
|
|
457
478
|
{
|
|
458
479
|
description:
|
|
459
480
|
"Create a new pipeline deal. Source defaults to the caller's user record. " +
|
|
460
|
-
"Owner
|
|
461
|
-
"until a partner approves it via /partner/approvals.",
|
|
481
|
+
"Owner is an audited responsibility label, not an operating-permission boundary.",
|
|
462
482
|
inputSchema: {
|
|
463
483
|
companyName: z.string(),
|
|
464
484
|
description: z.string().optional().describe("one-liner: what they do"),
|
|
@@ -495,7 +515,7 @@ server.registerTool(
|
|
|
495
515
|
"deal_update",
|
|
496
516
|
{
|
|
497
517
|
description:
|
|
498
|
-
"Update a single
|
|
518
|
+
"Update a single non-workflow field on a deal. Writable fields: theirStage, " +
|
|
499
519
|
"notes, stage, dealOwner, source, sourceDirection, description, website, location, founders, " +
|
|
500
520
|
"proposedAmount, roundSize, valuation, sector, subsector, foundedYear, leadInvestor, " +
|
|
501
521
|
"investors. Logs a field_change event in deal_events.",
|
|
@@ -505,8 +525,93 @@ server.registerTool(
|
|
|
505
525
|
value: z.union([z.string(), z.number(), z.null()]).describe("new value"),
|
|
506
526
|
},
|
|
507
527
|
},
|
|
508
|
-
async ({ dealId, field, value }) =>
|
|
509
|
-
|
|
528
|
+
async ({ dealId, field, value }) => {
|
|
529
|
+
if (field === "status") {
|
|
530
|
+
return textResult("Error: Direct status writes are retired. Use the workflow_* tools.", true);
|
|
531
|
+
}
|
|
532
|
+
return callApi("POST", "/api/deals/update", { dealId, field, value });
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
server.registerTool(
|
|
537
|
+
"workflow_show",
|
|
538
|
+
{
|
|
539
|
+
description: "Read the canonical Investment Workflow V2 state, revision, current transition, and blockers for a deal.",
|
|
540
|
+
inputSchema: { dealId: z.string() },
|
|
541
|
+
},
|
|
542
|
+
async ({ dealId }) => callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/workflow`),
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
server.registerTool(
|
|
546
|
+
"workflow_initialize",
|
|
547
|
+
{
|
|
548
|
+
description: "Persist the canonical Investment Workflow V2 bootstrap state for an unmigrated deal without changing its semantic stage. Intended for audited migration and recovery.",
|
|
549
|
+
inputSchema: { dealId: z.string(), reason: z.string().min(1) },
|
|
550
|
+
},
|
|
551
|
+
async ({ dealId, reason }) => callWorkflow(dealId, "initialize", { reason }),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
server.registerTool(
|
|
555
|
+
"workflow_request_partner_support",
|
|
556
|
+
{
|
|
557
|
+
description: "Submit a formal Partner Support request. Core refuses it until every current stage requirement is satisfied or waived.",
|
|
558
|
+
inputSchema: { dealId: z.string(), partnerId: z.string(), reason: z.string().min(1) },
|
|
559
|
+
},
|
|
560
|
+
async ({ dealId, partnerId, reason }) => callWorkflow(dealId, "request_partner_support", { partnerId, reason }),
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
server.registerTool(
|
|
564
|
+
"workflow_decide_partner_support",
|
|
565
|
+
{
|
|
566
|
+
description: "Record the named Partner's own Support, Need more, or Pass decision. Core verifies caller identity; never use on another person's behalf.",
|
|
567
|
+
inputSchema: { dealId: z.string(), decision: z.enum(["support", "need_more", "pass"]), reason: z.string().min(1) },
|
|
568
|
+
},
|
|
569
|
+
async ({ dealId, decision, reason }) => callWorkflow(dealId, "partner_support_decision", { decision, reason }),
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
server.registerTool(
|
|
573
|
+
"workflow_proceed",
|
|
574
|
+
{
|
|
575
|
+
description: "Apply the current formal workflow transition. Core re-evaluates every guard atomically.",
|
|
576
|
+
inputSchema: { dealId: z.string(), transitionKey: z.string(), reason: z.string().min(1) },
|
|
577
|
+
},
|
|
578
|
+
async ({ dealId, transitionKey, reason }) => callWorkflow(dealId, "proceed", { transitionKey, reason }),
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
server.registerTool(
|
|
582
|
+
"workflow_resolve_guard",
|
|
583
|
+
{
|
|
584
|
+
description: "Record an audited workflow guard resolution. Waivers require a concrete reason and remain visible in history.",
|
|
585
|
+
inputSchema: { dealId: z.string(), guardKey: z.string(), status: z.enum(["satisfied", "unsatisfied", "waived"]), reason: z.string().min(1) },
|
|
586
|
+
},
|
|
587
|
+
async ({ dealId, guardKey, status, reason }) => callWorkflow(dealId, "resolve_guard", { guardKey, status, reason }),
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
server.registerTool(
|
|
591
|
+
"workflow_control",
|
|
592
|
+
{
|
|
593
|
+
description: "Hold, resume, pass, restore, or return a deal through the canonical workflow control path.",
|
|
594
|
+
inputSchema: { dealId: z.string(), action: z.enum(["hold", "resume", "pass", "restore", "return"]), reason: z.string().min(1), holdDisposition: z.enum(["stalled", "future"]).optional() },
|
|
595
|
+
},
|
|
596
|
+
async ({ dealId, action, reason, holdDisposition }) => callWorkflow(dealId, "control", { action, reason, ...(holdDisposition ? { holdDisposition } : {}) }),
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
server.registerTool(
|
|
600
|
+
"workflow_vote",
|
|
601
|
+
{
|
|
602
|
+
description: "Cast the signed-in Partner's own Formal IC vote through Investment Workflow V2.",
|
|
603
|
+
inputSchema: { dealId: z.string(), vote: z.enum(["yes", "no"]), reason: z.string().min(1) },
|
|
604
|
+
},
|
|
605
|
+
async ({ dealId, vote, reason }) => callWorkflow(dealId, "cast_vote", { vote, reason }),
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
server.registerTool(
|
|
609
|
+
"workflow_update_execution_status",
|
|
610
|
+
{
|
|
611
|
+
description: "Update the canonical post-IC execution status. Available only after the formal IC decision.",
|
|
612
|
+
inputSchema: { dealId: z.string(), executionStatus: z.enum(["Term Sheet", "Verbal Commit", "Invested"]), reason: z.string().min(1) },
|
|
613
|
+
},
|
|
614
|
+
async ({ dealId, executionStatus, reason }) => callWorkflow(dealId, "update_execution_status", { executionStatus, reason }),
|
|
510
615
|
);
|
|
511
616
|
|
|
512
617
|
// ============================================================
|
package/bin/llama.mjs
CHANGED
|
@@ -391,12 +391,14 @@ Deals:
|
|
|
391
391
|
llama deal show <dealId>
|
|
392
392
|
llama deal feed <dealId> # every contribution (facts + notes), human-typed or assistant-drafted, newest first
|
|
393
393
|
llama deal update <dealId> <field> <value>
|
|
394
|
-
Writable fields:
|
|
394
|
+
Writable fields: theirStage, stage, notes, source, sourceDirection,
|
|
395
395
|
description, website, location, founders, founderInfo, proposedAmount,
|
|
396
396
|
roundSize, valuation, deckLink, folderUrl, sector, subsector,
|
|
397
397
|
foundedYear, leadInvestor, investors, agentActive.
|
|
398
|
+
'notes' is the ONE-LINE judgment shown as the Summary headline at the top of
|
|
399
|
+
the deal page (~280 chars). Meeting notes and narrative go in a comment
|
|
400
|
+
(llama post); verifiable claims go in facts (llama deal fact add).
|
|
398
401
|
e.g. llama deal update <dealId> website https://acme.ai
|
|
399
|
-
llama deal update <dealId> status Interested
|
|
400
402
|
llama deal update <dealId> sector "Developer Tools"
|
|
401
403
|
llama deal update <dealId> foundedYear 2024
|
|
402
404
|
llama deal update <dealId> leadInvestor "Acme Capital"
|
|
@@ -415,6 +417,19 @@ Deals:
|
|
|
415
417
|
[--limit 200] [--offset 0]
|
|
416
418
|
llama deal list [--owner ...] [--status ...] [...same flags as search]
|
|
417
419
|
|
|
420
|
+
Investment Workflow V2 (the only stage-write surface):
|
|
421
|
+
llama workflow show <dealId>
|
|
422
|
+
llama workflow initialize <dealId> --reason "..." # audited legacy migration/bootstrap
|
|
423
|
+
llama workflow request-support <dealId> --partner <userId> --reason "..."
|
|
424
|
+
llama workflow decide-support <dealId> support|need_more|pass --reason "..."
|
|
425
|
+
llama workflow proceed <dealId> --transition <key> --reason "..."
|
|
426
|
+
llama workflow waive <dealId> --guard <key> --reason "..."
|
|
427
|
+
llama workflow control <dealId> hold|resume|pass|restore|return --reason "..." [--disposition stalled|future]
|
|
428
|
+
llama workflow organize-ic <dealId> --note "..."
|
|
429
|
+
llama workflow vote <dealId> yes|no --reason "..."
|
|
430
|
+
llama workflow reassign-owner <dealId> --owner <userId> --reason "..."
|
|
431
|
+
llama workflow execution-status <dealId> term-sheet|verbal-commit|invested --reason "..."
|
|
432
|
+
|
|
418
433
|
Agent activity (read-only, cheap read model over append-only activity):
|
|
419
434
|
llama activity new-deals [--since 24h|7d|<ISO>] [--limit 50]
|
|
420
435
|
llama activity updated-deals [--since 24h|7d|<ISO>] [--limit 50] [--deal <uuid>]
|
|
@@ -446,12 +461,12 @@ Deal links (separate from brief link blocks — these live in deal_links):
|
|
|
446
461
|
llama deal link restore <dealId> <linkId>
|
|
447
462
|
|
|
448
463
|
Ownership:
|
|
449
|
-
llama claim <dealId>
|
|
450
|
-
llama nominate <dealId> --user <userId>
|
|
451
|
-
|
|
452
|
-
|
|
464
|
+
llama claim <dealId> --reason "..." # set yourself as Owner
|
|
465
|
+
llama nominate <dealId> --user <userId> --reason "..." # set another teammate as Owner
|
|
466
|
+
Owner is an audited responsibility label, not a permission boundary. Any
|
|
467
|
+
internal teammate can change it after Partner Support; reason is mandatory.
|
|
453
468
|
|
|
454
|
-
Approvals (
|
|
469
|
+
Approvals (Partner queue — contribution decisions):
|
|
455
470
|
llama approvals list
|
|
456
471
|
llama approvals decide <approvalId> approved|rejected [--note "..."]
|
|
457
472
|
|
|
@@ -503,6 +518,9 @@ Deal facts (AI-extracted or human-asserted, with verification):
|
|
|
503
518
|
llama deal fact list <dealId>
|
|
504
519
|
llama deal fact add <dealId> --category <cat> --claim "<text>" [--source "..."] [--source-url <url>] [--confidence high|medium|low] [--attested]
|
|
505
520
|
llama deal fact verify <dealId> <factId> --status confirmed|disputed [--corrected-value "..."]
|
|
521
|
+
llama deal fact uncontest <dealId> <factId> --reason "<why the contest was wrong>"
|
|
522
|
+
contest and trust are separate axes: 'verify --status confirmed' does NOT lift a contest,
|
|
523
|
+
and a contested fact stays excluded from what the Deal Agent treats as current.
|
|
506
524
|
|
|
507
525
|
Mentions / Inbox:
|
|
508
526
|
llama mentions # default: my unresolved cues
|
|
@@ -1440,6 +1458,65 @@ async function main() {
|
|
|
1440
1458
|
return;
|
|
1441
1459
|
}
|
|
1442
1460
|
|
|
1461
|
+
if (area === "workflow") {
|
|
1462
|
+
const dealId = rest[0];
|
|
1463
|
+
if (!dealId) throw new Error("Usage: llama workflow show|initialize|request-support|decide-support|proceed|waive|control|organize-ic|vote|reassign-owner|execution-status <dealId> [...flags]");
|
|
1464
|
+
if (action === "show") {
|
|
1465
|
+
print(await request("GET", `/api/deals/${encodeURIComponent(dealId)}/workflow`));
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const { flags, positional } = parseFlags(rest.slice(1));
|
|
1469
|
+
const current = await request("GET", `/api/deals/${encodeURIComponent(dealId)}/workflow`);
|
|
1470
|
+
const expectedRevision = current?.workflow?.revision;
|
|
1471
|
+
if (!Number.isInteger(expectedRevision)) {
|
|
1472
|
+
throw new Error("This deal has no initialized Investment Workflow V2 state. Open its workflow panel once, then retry.");
|
|
1473
|
+
}
|
|
1474
|
+
const base = {
|
|
1475
|
+
requestId: `cli:${action}:${randomUUID()}`,
|
|
1476
|
+
expectedRevision,
|
|
1477
|
+
};
|
|
1478
|
+
let command;
|
|
1479
|
+
if (action === "initialize") {
|
|
1480
|
+
if (!flags.reason) throw new Error('Usage: llama workflow initialize <dealId> --reason "..."');
|
|
1481
|
+
command = { ...base, type: "initialize", reason: String(flags.reason) };
|
|
1482
|
+
} else if (action === "request-support") {
|
|
1483
|
+
if (!flags.partner || !flags.reason) throw new Error('Usage: llama workflow request-support <dealId> --partner <userId> --reason "..."');
|
|
1484
|
+
command = { ...base, type: "request_partner_support", partnerId: String(flags.partner), reason: String(flags.reason) };
|
|
1485
|
+
} else if (action === "decide-support") {
|
|
1486
|
+
const decision = positional[0];
|
|
1487
|
+
if (!["support", "need_more", "pass"].includes(decision) || !flags.reason) throw new Error('Usage: llama workflow decide-support <dealId> support|need_more|pass --reason "..."');
|
|
1488
|
+
command = { ...base, type: "partner_support_decision", decision, reason: String(flags.reason) };
|
|
1489
|
+
} else if (action === "proceed") {
|
|
1490
|
+
if (!flags.transition || !flags.reason) throw new Error('Usage: llama workflow proceed <dealId> --transition <key> --reason "..."');
|
|
1491
|
+
command = { ...base, type: "proceed", transitionKey: String(flags.transition), reason: String(flags.reason) };
|
|
1492
|
+
} else if (action === "waive") {
|
|
1493
|
+
if (!flags.guard || !flags.reason) throw new Error('Usage: llama workflow waive <dealId> --guard <key> --reason "..."');
|
|
1494
|
+
command = { ...base, type: "resolve_guard", guardKey: String(flags.guard), status: "waived", reason: String(flags.reason) };
|
|
1495
|
+
} else if (action === "control") {
|
|
1496
|
+
const control = positional[0];
|
|
1497
|
+
if (!["hold", "resume", "pass", "restore", "return"].includes(control) || !flags.reason) throw new Error('Usage: llama workflow control <dealId> hold|resume|pass|restore|return --reason "..." [--disposition stalled|future]');
|
|
1498
|
+
command = { ...base, type: "control", action: control, reason: String(flags.reason), ...(flags.disposition ? { holdDisposition: String(flags.disposition) } : {}) };
|
|
1499
|
+
} else if (action === "organize-ic") {
|
|
1500
|
+
if (!flags.note) throw new Error('Usage: llama workflow organize-ic <dealId> --note "..."');
|
|
1501
|
+
command = { ...base, type: "organize_ic", note: String(flags.note) };
|
|
1502
|
+
} else if (action === "vote") {
|
|
1503
|
+
const vote = positional[0];
|
|
1504
|
+
if (!["yes", "no"].includes(vote) || !flags.reason) throw new Error('Usage: llama workflow vote <dealId> yes|no --reason "..."');
|
|
1505
|
+
command = { ...base, type: "cast_vote", vote, reason: String(flags.reason) };
|
|
1506
|
+
} else if (action === "reassign-owner") {
|
|
1507
|
+
if (!flags.owner || !flags.reason) throw new Error('Usage: llama workflow reassign-owner <dealId> --owner <userId> --reason "..."');
|
|
1508
|
+
command = { ...base, type: "reassign_owner", ownerId: String(flags.owner), ownerName: "resolved by server", reason: String(flags.reason) };
|
|
1509
|
+
} else if (action === "execution-status") {
|
|
1510
|
+
const status = { "term-sheet": "Term Sheet", "verbal-commit": "Verbal Commit", invested: "Invested" }[positional[0]];
|
|
1511
|
+
if (!status || !flags.reason) throw new Error('Usage: llama workflow execution-status <dealId> term-sheet|verbal-commit|invested --reason "..."');
|
|
1512
|
+
command = { ...base, type: "update_execution_status", executionStatus: status, reason: String(flags.reason) };
|
|
1513
|
+
} else {
|
|
1514
|
+
throw new Error(`Unknown workflow command "${action || ""}".`);
|
|
1515
|
+
}
|
|
1516
|
+
print(await request("POST", `/api/deals/${encodeURIComponent(dealId)}/workflow`, command));
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1443
1520
|
if (area === "deal" && action === "show") {
|
|
1444
1521
|
const dealId = rest[0];
|
|
1445
1522
|
if (!dealId) throw new Error("Usage: llama deal show <dealId>");
|
|
@@ -1458,6 +1535,9 @@ async function main() {
|
|
|
1458
1535
|
const [dealId, field, ...valueParts] = rest;
|
|
1459
1536
|
const value = valueParts.join(" ");
|
|
1460
1537
|
if (!dealId || !field) throw new Error("Usage: llama deal update <dealId> <field> <value>");
|
|
1538
|
+
if (field === "status") {
|
|
1539
|
+
throw new Error("Direct status writes are retired. Use `llama workflow show <dealId>` and a formal `llama workflow ...` command.");
|
|
1540
|
+
}
|
|
1461
1541
|
print(await request("POST", "/api/deals/update", { dealId, field, value }));
|
|
1462
1542
|
return;
|
|
1463
1543
|
}
|
|
@@ -1491,6 +1571,9 @@ async function main() {
|
|
|
1491
1571
|
const sub = rest[0];
|
|
1492
1572
|
const dealId = rest[1];
|
|
1493
1573
|
const key = rest[2];
|
|
1574
|
+
if (["stage_gates", "stage4_gate"].includes(key)) {
|
|
1575
|
+
throw new Error(`Legacy ${key} is retired. Use \`llama workflow ...\` commands.`);
|
|
1576
|
+
}
|
|
1494
1577
|
if (sub === "set") {
|
|
1495
1578
|
const raw = rest.slice(3).join(" ");
|
|
1496
1579
|
if (!dealId || !key || !raw) {
|
|
@@ -1826,7 +1909,29 @@ async function main() {
|
|
|
1826
1909
|
return;
|
|
1827
1910
|
}
|
|
1828
1911
|
|
|
1829
|
-
|
|
1912
|
+
// Lift a contest that turned out to be wrong. Contesting a claim removes
|
|
1913
|
+
// it from everything the Deal Agent treats as current; before this existed
|
|
1914
|
+
// there was no way back, so a TRUE claim contested on weak evidence stayed
|
|
1915
|
+
// suppressed in every regenerated brief and memo. `verify --status
|
|
1916
|
+
// confirmed` does NOT do this — trust and contest are separate axes.
|
|
1917
|
+
if (sub === "uncontest") {
|
|
1918
|
+
const factId = rest[2];
|
|
1919
|
+
const reason = flags.reason !== undefined && flags.reason !== true ? String(flags.reason).trim() : "";
|
|
1920
|
+
if (!factId || !reason) {
|
|
1921
|
+
throw new Error(
|
|
1922
|
+
`Usage: llama deal fact uncontest <dealId> <factId> --reason "<why the contest was wrong>"`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
// @core-api-operation POST /api/deals/{dealId}/facts/{factId}/uncontest
|
|
1926
|
+
print(await request(
|
|
1927
|
+
"POST",
|
|
1928
|
+
`/api/deals/${encodeURIComponent(dealId)}/facts/${encodeURIComponent(factId)}/uncontest`,
|
|
1929
|
+
{ reason }
|
|
1930
|
+
));
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
throw new Error(`Unknown fact sub-command "${sub}". Use list, add, verify, or uncontest.`);
|
|
1830
1935
|
}
|
|
1831
1936
|
|
|
1832
1937
|
// ----- Brief refresh: trigger stale-section re-eval watcher run -----
|
|
@@ -1890,12 +1995,16 @@ async function main() {
|
|
|
1890
1995
|
// ----- Ownership: self-claim -----
|
|
1891
1996
|
if (area === "claim") {
|
|
1892
1997
|
const dealId = action; // second positional
|
|
1893
|
-
|
|
1998
|
+
const { flags } = parseFlags(rest, ["reason"]);
|
|
1999
|
+
const reason = typeof flags.reason === "string" ? flags.reason.trim() : "";
|
|
2000
|
+
if (!dealId || !reason) {
|
|
2001
|
+
throw new Error('Usage: llama claim <dealId> --reason "<why this person should own it>"');
|
|
2002
|
+
}
|
|
1894
2003
|
const me = await request("GET", "/api/me");
|
|
1895
2004
|
print(await request(
|
|
1896
2005
|
"POST",
|
|
1897
2006
|
`/api/deals/${encodeURIComponent(dealId)}/propose-owner`,
|
|
1898
|
-
{ userId: me.id }
|
|
2007
|
+
{ userId: me.id, reason }
|
|
1899
2008
|
));
|
|
1900
2009
|
return;
|
|
1901
2010
|
}
|
|
@@ -1903,15 +2012,16 @@ async function main() {
|
|
|
1903
2012
|
// ----- Ownership: partner nominates someone else -----
|
|
1904
2013
|
if (area === "nominate") {
|
|
1905
2014
|
const dealId = action;
|
|
1906
|
-
const { flags } = parseFlags(rest);
|
|
2015
|
+
const { flags } = parseFlags(rest, ["user", "reason"]);
|
|
1907
2016
|
const userId = Number(flags.user);
|
|
1908
|
-
|
|
1909
|
-
|
|
2017
|
+
const reason = typeof flags.reason === "string" ? flags.reason.trim() : "";
|
|
2018
|
+
if (!dealId || !Number.isFinite(userId) || !reason) {
|
|
2019
|
+
throw new Error('Usage: llama nominate <dealId> --user <userId> --reason "<why>"');
|
|
1910
2020
|
}
|
|
1911
2021
|
print(await request(
|
|
1912
2022
|
"POST",
|
|
1913
2023
|
`/api/deals/${encodeURIComponent(dealId)}/propose-owner`,
|
|
1914
|
-
{ userId }
|
|
2024
|
+
{ userId, reason }
|
|
1915
2025
|
));
|
|
1916
2026
|
return;
|
|
1917
2027
|
}
|
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.23.0",
|
|
5
5
|
"openapiVersion": "3.0.3",
|
|
6
|
-
"sha256": "
|
|
7
|
-
"pathCount":
|
|
8
|
-
"operationCount":
|
|
6
|
+
"sha256": "b959a28605693097ba8d8d9345729070d54fcce08f004abcfe9b8c2fd2dabc6d",
|
|
7
|
+
"pathCount": 233,
|
|
8
|
+
"operationCount": 304
|
|
9
9
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Investment Workflow V2 client contract
|
|
2
|
+
|
|
3
|
+
CLI and MCP treat `GET/POST /api/deals/{dealId}/workflow` as the only workflow
|
|
4
|
+
read/write surface. A write must first read the current revision, submit one
|
|
5
|
+
typed command with a unique request id, and display the canonical response.
|
|
6
|
+
|
|
7
|
+
Never add a client shortcut that writes `deals.status`,
|
|
8
|
+
`extra.investment_workflow_v2` snapshots, `extra.stage_gates`, or
|
|
9
|
+
`extra.stage4_gate`. Partner
|
|
10
|
+
decisions are always made by the authenticated Partner; admin capability does
|
|
11
|
+
not imply permission to impersonate a decision maker.
|
|
12
|
+
|
|
13
|
+
`workflow initialize` is the audited bootstrap command for legacy migration.
|
|
14
|
+
It persists the state returned by `workflow show` without advancing a stage or
|
|
15
|
+
resolving a guard. Never translate legacy approvals into V2 approvals silently.
|
|
16
|
+
|
|
17
|
+
Post-IC compatibility status is also owned by V2. Use `workflow
|
|
18
|
+
execution-status` / `workflow_update_execution_status` for Term Sheet, Verbal
|
|
19
|
+
Commit, and Invested; never reopen direct `deal update ... status` writes.
|
|
@@ -78,6 +78,14 @@
|
|
|
78
78
|
"method": "GET",
|
|
79
79
|
"path": "/api/deals/{dealId}"
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"method": "GET",
|
|
83
|
+
"path": "/api/deals/{dealId}/workflow"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"method": "POST",
|
|
87
|
+
"path": "/api/deals/{dealId}/workflow"
|
|
88
|
+
},
|
|
81
89
|
{
|
|
82
90
|
"method": "POST",
|
|
83
91
|
"path": "/api/deals/{dealId}/agent-runs/{runId}/revert"
|
|
@@ -186,6 +194,10 @@
|
|
|
186
194
|
"method": "PATCH",
|
|
187
195
|
"path": "/api/deals/{dealId}/facts/{factId}"
|
|
188
196
|
},
|
|
197
|
+
{
|
|
198
|
+
"method": "POST",
|
|
199
|
+
"path": "/api/deals/{dealId}/facts/{factId}/uncontest"
|
|
200
|
+
},
|
|
189
201
|
{
|
|
190
202
|
"method": "GET",
|
|
191
203
|
"path": "/api/deals/{dealId}/feed"
|
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.23.1",
|
|
5
|
+
"sourceSha": "8d5824095c03137b224bdbff50bc6be144d37486",
|
|
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.23.0",
|
|
12
12
|
"openapiVersion": "3.0.3",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "b959a28605693097ba8d8d9345729070d54fcce08f004abcfe9b8c2fd2dabc6d"
|
|
14
14
|
}
|
|
15
15
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.1",
|
|
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/server-compatibility.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/investment-workflow-contract.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",
|