@llamaventures/cli 1.3.0 → 1.4.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 +0 -1
- package/README.md +2 -3
- package/bin/llama-mcp.mjs +95 -9
- package/bin/llama.mjs +203 -5
- package/lib/external.mjs +5 -8
- package/package.json +2 -2
package/AGENT_BRIEFING.md
CHANGED
|
@@ -139,7 +139,6 @@ Tools available:
|
|
|
139
139
|
- `timeline` / `post`
|
|
140
140
|
- `mentions_list`
|
|
141
141
|
- `pitch_start` / `pitch_send_message` / `pitch_upload_file` / `pitch_status` / `pitch_finalize` — public intake (no Llama token needed; for founders / EAs / external agents)
|
|
142
|
-
- `llama_api` — escape hatch for any endpoint not yet wrapped (path must start `/api/`)
|
|
143
142
|
|
|
144
143
|
You can also fetch this exact briefing as an MCP prompt named `agent_briefing`.
|
|
145
144
|
|
package/README.md
CHANGED
|
@@ -342,9 +342,8 @@ llama pitch upload ./deck.pdf
|
|
|
342
342
|
llama pitch # interactive REPL
|
|
343
343
|
```
|
|
344
344
|
|
|
345
|
-
Server-enforced
|
|
346
|
-
|
|
347
|
-
1 M tokens/session.
|
|
345
|
+
Server-enforced rate limits apply (per-IP, per-email, per-session). If you
|
|
346
|
+
hit a limit, the CLI surfaces the server's response message.
|
|
348
347
|
|
|
349
348
|
This is genuine **agent-to-agent**: your AI helps you tell the story, our
|
|
350
349
|
intake agent extracts the structured fields and produces the verdict.
|
package/bin/llama-mcp.mjs
CHANGED
|
@@ -364,9 +364,8 @@ server.registerTool(
|
|
|
364
364
|
// founder's agent talks to ours, structured intake gets captured, and a
|
|
365
365
|
// 12-dimension verdict is returned.
|
|
366
366
|
//
|
|
367
|
-
// Anti-abuse
|
|
368
|
-
//
|
|
369
|
-
// surface those rejections as text back to the agent.
|
|
367
|
+
// Anti-abuse rate limits are server-enforced. The MCP tools surface
|
|
368
|
+
// any server-side rejections as text back to the agent.
|
|
370
369
|
|
|
371
370
|
function asTextResult(text, isError = false) {
|
|
372
371
|
return {
|
|
@@ -383,8 +382,8 @@ server.registerTool(
|
|
|
383
382
|
"when a founder (the user) wants to pitch their company to Llama. " +
|
|
384
383
|
"Requires their name + email. Returns a session_id; the conversation " +
|
|
385
384
|
"is then maintained via pitch_send_message until the agent finalizes. " +
|
|
386
|
-
"
|
|
387
|
-
"
|
|
385
|
+
"Server-enforced rate limits apply (per-IP, per-email, per-session). " +
|
|
386
|
+
"No Llama Command token needed.",
|
|
388
387
|
inputSchema: {
|
|
389
388
|
name: z.string().describe("the founder's full name (max 100 chars)"),
|
|
390
389
|
email: z.string().describe("the founder's email (deliverable, not a disposable domain)"),
|
|
@@ -447,8 +446,9 @@ server.registerTool(
|
|
|
447
446
|
description:
|
|
448
447
|
"Attach a file (deck, one-pager, deck PDF, screenshot, etc.) to the " +
|
|
449
448
|
"active pitch session. Server allows pdf / pptx / ppt / docx / doc / " +
|
|
450
|
-
"xlsx / xls / png / jpg / webp / heic / heif / txt / md,
|
|
451
|
-
"
|
|
449
|
+
"xlsx / xls / png / jpg / webp / heic / heif / txt / md, with " +
|
|
450
|
+
"server-enforced size and per-session count limits. " +
|
|
451
|
+
"Returns a drive_file_id; the intake agent will " +
|
|
452
452
|
"pick the file up via list_uploaded_files / read_uploaded_file on its " +
|
|
453
453
|
"next turn (so call pitch_send_message with a one-line note like " +
|
|
454
454
|
"'I just uploaded our pitch deck' so the agent knows to look).",
|
|
@@ -493,7 +493,7 @@ server.registerTool(
|
|
|
493
493
|
"server-side intake agent to finalize — the agent decides that on its " +
|
|
494
494
|
"own once the pitch is sufficient. Use this for cleanup after a session " +
|
|
495
495
|
"ends, or to abandon a session early. The server-side session will " +
|
|
496
|
-
"naturally expire after
|
|
496
|
+
"naturally expire after the server's idle timeout.",
|
|
497
497
|
inputSchema: {},
|
|
498
498
|
},
|
|
499
499
|
async () => {
|
|
@@ -505,7 +505,7 @@ server.registerTool(
|
|
|
505
505
|
{
|
|
506
506
|
cleared: before.active,
|
|
507
507
|
previous_session: before.active ? before : null,
|
|
508
|
-
note: "Local pitch session state cleared. Server-side session may still be active
|
|
508
|
+
note: "Local pitch session state cleared. Server-side session may still be active until its idle timeout.",
|
|
509
509
|
},
|
|
510
510
|
null,
|
|
511
511
|
2
|
|
@@ -517,6 +517,92 @@ server.registerTool(
|
|
|
517
517
|
}
|
|
518
518
|
);
|
|
519
519
|
|
|
520
|
+
// ============================================================
|
|
521
|
+
// Memo — long-form HTML investment memo (the Memo tab in the UI)
|
|
522
|
+
// ============================================================
|
|
523
|
+
|
|
524
|
+
server.registerTool(
|
|
525
|
+
"memo_show",
|
|
526
|
+
{
|
|
527
|
+
description:
|
|
528
|
+
"Fetch the current memo for a deal. Returns the envelope: memo " +
|
|
529
|
+
"(html, version, source, updated_by, updated_at), mode " +
|
|
530
|
+
"('composed' = server-generated, 'override' = hand-written), and " +
|
|
531
|
+
"inflight (if a server-side regeneration is in progress). html " +
|
|
532
|
+
"can be 50-100KB — be deliberate about including it in your reply.",
|
|
533
|
+
inputSchema: {
|
|
534
|
+
dealId: z.string().describe("deal uuid"),
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
async ({ dealId }) =>
|
|
538
|
+
callApi("GET", `/api/deals/${encodeURIComponent(dealId)}/memo`)
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
server.registerTool(
|
|
542
|
+
"memo_regenerate",
|
|
543
|
+
{
|
|
544
|
+
description:
|
|
545
|
+
"Trigger server-side regeneration of the deal memo. Synchronous: " +
|
|
546
|
+
"returns the final result (version, model, duration_ms, degraded) " +
|
|
547
|
+
"once the composer finishes. Typical duration 2-3 minutes. Use " +
|
|
548
|
+
"tier='opus' for high-stakes deals (higher cost, deeper analysis).",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
dealId: z.string().describe("deal uuid"),
|
|
551
|
+
tier: z
|
|
552
|
+
.enum(["sonnet", "opus"])
|
|
553
|
+
.optional()
|
|
554
|
+
.describe("LLM tier (default: sonnet)"),
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
async ({ dealId, tier }) =>
|
|
558
|
+
callApi("POST", `/api/deals/${encodeURIComponent(dealId)}/memo`, {
|
|
559
|
+
action: "regenerate",
|
|
560
|
+
stream: false,
|
|
561
|
+
model: tier ?? "sonnet",
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
server.registerTool(
|
|
566
|
+
"memo_save",
|
|
567
|
+
{
|
|
568
|
+
description:
|
|
569
|
+
"Save hand-written HTML as a manual override for a deal's memo. " +
|
|
570
|
+
"Manual overrides take precedence over auto-composed memos on " +
|
|
571
|
+
"read. Pass the full HTML document including <!DOCTYPE html>, " +
|
|
572
|
+
"<style>, and <body> — it's rendered as-is in a sandboxed iframe.",
|
|
573
|
+
inputSchema: {
|
|
574
|
+
dealId: z.string().describe("deal uuid"),
|
|
575
|
+
html: z
|
|
576
|
+
.string()
|
|
577
|
+
.describe("full HTML document"),
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
async ({ dealId, html }) =>
|
|
581
|
+
callApi("PUT", `/api/deals/${encodeURIComponent(dealId)}/memo`, { html })
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
server.registerTool(
|
|
585
|
+
"memo_reset",
|
|
586
|
+
{
|
|
587
|
+
description:
|
|
588
|
+
"Reset memo state. Default drops only the manual override row " +
|
|
589
|
+
"(next read falls back to the auto-composed version, if any). " +
|
|
590
|
+
"Pass scope='all' to drop every version for the deal — destructive, " +
|
|
591
|
+
"use sparingly.",
|
|
592
|
+
inputSchema: {
|
|
593
|
+
dealId: z.string().describe("deal uuid"),
|
|
594
|
+
scope: z
|
|
595
|
+
.enum(["override_only", "all"])
|
|
596
|
+
.optional()
|
|
597
|
+
.describe("default: override_only"),
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
async ({ dealId, scope }) =>
|
|
601
|
+
callApi("DELETE", `/api/deals/${encodeURIComponent(dealId)}/memo`, {
|
|
602
|
+
scope: scope ?? "override_only",
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
|
|
520
606
|
// ============================================================
|
|
521
607
|
// Prompts
|
|
522
608
|
// ============================================================
|
package/bin/llama.mjs
CHANGED
|
@@ -242,7 +242,7 @@ Skill corrections (persona-owner pushback — read by persona-watcher):
|
|
|
242
242
|
llama skill-correction add <skill-slug> "<correction text>" [--deal <uuid>] [--block <blockId>]
|
|
243
243
|
llama skill-correction delete <id>
|
|
244
244
|
Server enforces persona owner OR system admin on POST/DELETE; GET is open.
|
|
245
|
-
External personas (owner_email=null
|
|
245
|
+
External personas (owner_email=null) are admin-only for write.
|
|
246
246
|
|
|
247
247
|
Mentions / Inbox:
|
|
248
248
|
llama mentions # default: my unresolved cues
|
|
@@ -256,6 +256,12 @@ Wiki:
|
|
|
256
256
|
llama wiki read <slug>
|
|
257
257
|
llama wiki save <slug> --title "..." --content "..." --sources "url1;url2" [--type company] [--related "A;B"]
|
|
258
258
|
|
|
259
|
+
Memo (long-form HTML investment memo — Memo tab in the UI):
|
|
260
|
+
llama memo show <dealId> [--out <path>] [--json] # default: html → stdout (pipeable to file / browser)
|
|
261
|
+
llama memo regenerate <dealId> [--opus] # streams panel progress to stderr; result version → stdout
|
|
262
|
+
llama memo save <dealId> --file <path> # paste a hand-written HTML as manual override
|
|
263
|
+
llama memo reset <dealId> [--all] # default drops manual override; --all drops every version
|
|
264
|
+
|
|
259
265
|
Admin (system admin only — server returns 403 for non-admin tokens):
|
|
260
266
|
llama admin auth-events [--kind X] [--actor email] [--subject email] [--since 24h|7d|30d|<ISO>] [--limit 100]
|
|
261
267
|
llama admin deal-events [--kind X] [--actor email] [--deal <uuid>] [--since 24h] [--limit 100]
|
|
@@ -318,9 +324,9 @@ Inspect / clean up:
|
|
|
318
324
|
llama pitch status # session id, idle minutes, finalized?
|
|
319
325
|
llama pitch end # clear local session state
|
|
320
326
|
|
|
321
|
-
Caps
|
|
322
|
-
|
|
323
|
-
|
|
327
|
+
Caps:
|
|
328
|
+
Server-enforced per-IP / per-email / per-session rate limits apply.
|
|
329
|
+
The CLI surfaces server messages if a limit is hit.
|
|
324
330
|
|
|
325
331
|
Environment:
|
|
326
332
|
LLAMA_API_URL override base URL (dev: http://localhost:3000)
|
|
@@ -411,7 +417,7 @@ Environment:
|
|
|
411
417
|
cleared: !!had,
|
|
412
418
|
session_file: EXTERNAL_SESSION_FILE,
|
|
413
419
|
note: had
|
|
414
|
-
? "Local session state cleared. Server-side session may still be active until idle timeout
|
|
420
|
+
? "Local session state cleared. Server-side session may still be active until idle timeout."
|
|
415
421
|
: "No local session was active.",
|
|
416
422
|
});
|
|
417
423
|
return;
|
|
@@ -1588,6 +1594,198 @@ https://command.llamaventures.vc/settings/tokens, run
|
|
|
1588
1594
|
);
|
|
1589
1595
|
}
|
|
1590
1596
|
|
|
1597
|
+
// ----- Memo (long-form HTML investment memo) -----
|
|
1598
|
+
// The Memo tab in the deal page renders HTML stored in deal_memos.
|
|
1599
|
+
// Two sources of memo content:
|
|
1600
|
+
// - composed: generated by the server-side memo composer on demand
|
|
1601
|
+
// - manual: a hand-written HTML you paste in
|
|
1602
|
+
// Manual always beats composed on read; reset to drop the manual row
|
|
1603
|
+
// and fall back to the composed one.
|
|
1604
|
+
if (area === "memo") {
|
|
1605
|
+
const sub = action;
|
|
1606
|
+
|
|
1607
|
+
// show — fetch the current memo. Default: print HTML to stdout
|
|
1608
|
+
// (pipeable to file or browser). --out writes to a path. --json
|
|
1609
|
+
// returns the full envelope (memo + mode + inflight info).
|
|
1610
|
+
if (sub === "show") {
|
|
1611
|
+
const dealId = rest[0];
|
|
1612
|
+
if (!dealId) {
|
|
1613
|
+
throw new Error("Usage: llama memo show <dealId> [--out <path>] [--json]");
|
|
1614
|
+
}
|
|
1615
|
+
const { flags } = parseFlags(rest.slice(1));
|
|
1616
|
+
const data = await request(
|
|
1617
|
+
"GET",
|
|
1618
|
+
`/api/deals/${encodeURIComponent(dealId)}/memo`
|
|
1619
|
+
);
|
|
1620
|
+
if (flags.json) {
|
|
1621
|
+
print(data);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const html = data?.memo?.html;
|
|
1625
|
+
if (!html) {
|
|
1626
|
+
if (data?.requires_compose) {
|
|
1627
|
+
throw new Error(
|
|
1628
|
+
"No memo for this deal yet — run `llama memo regenerate <dealId>` to compose one."
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
throw new Error("Memo response missing html field.");
|
|
1632
|
+
}
|
|
1633
|
+
if (flags.out) {
|
|
1634
|
+
const { writeFileSync } = await import("fs");
|
|
1635
|
+
writeFileSync(String(flags.out), html);
|
|
1636
|
+
console.error(`Wrote ${html.length} bytes → ${flags.out}`);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
// Stdout — supports `llama memo show <id> > memo.html` and piping
|
|
1640
|
+
// to e.g. `open -f -a Safari` for quick preview.
|
|
1641
|
+
process.stdout.write(html);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// regenerate — kick off the server-side composer. Streams panel
|
|
1646
|
+
// progress events to stderr so you can see live status; prints
|
|
1647
|
+
// final summary JSON (version, model, duration) to stdout.
|
|
1648
|
+
if (sub === "regenerate") {
|
|
1649
|
+
const dealId = rest[0];
|
|
1650
|
+
if (!dealId) {
|
|
1651
|
+
throw new Error("Usage: llama memo regenerate <dealId> [--opus]");
|
|
1652
|
+
}
|
|
1653
|
+
const { flags } = parseFlags(rest.slice(1));
|
|
1654
|
+
const tier = flags.opus ? "opus" : "sonnet";
|
|
1655
|
+
const authHeaders = await getAuthHeaders();
|
|
1656
|
+
if (Object.keys(authHeaders).length === 0) {
|
|
1657
|
+
throw new Error(
|
|
1658
|
+
"Not authenticated. Run `gcloud auth login` or `llama token set <llc_...>` first."
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
const res = await fetch(
|
|
1662
|
+
`${getBaseUrl()}/api/deals/${encodeURIComponent(dealId)}/memo`,
|
|
1663
|
+
{
|
|
1664
|
+
method: "POST",
|
|
1665
|
+
headers: { "Content-Type": "application/json", ...authHeaders },
|
|
1666
|
+
body: JSON.stringify({
|
|
1667
|
+
action: "regenerate",
|
|
1668
|
+
stream: true,
|
|
1669
|
+
model: tier,
|
|
1670
|
+
}),
|
|
1671
|
+
}
|
|
1672
|
+
);
|
|
1673
|
+
if (!res.ok || !res.body) {
|
|
1674
|
+
const text = await res.text().catch(() => "");
|
|
1675
|
+
throw new Error(`HTTP ${res.status}: ${text.slice(0, 300)}`);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const reader = res.body.getReader();
|
|
1679
|
+
const decoder = new TextDecoder();
|
|
1680
|
+
let buffer = "";
|
|
1681
|
+
let doneEvent = null;
|
|
1682
|
+
const startedAt = Date.now();
|
|
1683
|
+
const progress = { done: 0, total: 12, placeholders: 0, retries: 0 };
|
|
1684
|
+
|
|
1685
|
+
while (true) {
|
|
1686
|
+
const { value, done: streamDone } = await reader.read();
|
|
1687
|
+
if (streamDone) break;
|
|
1688
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1689
|
+
let idx;
|
|
1690
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
1691
|
+
const frame = buffer.slice(0, idx);
|
|
1692
|
+
buffer = buffer.slice(idx + 2);
|
|
1693
|
+
const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
|
|
1694
|
+
if (!dataLine) continue;
|
|
1695
|
+
let event;
|
|
1696
|
+
try {
|
|
1697
|
+
event = JSON.parse(dataLine.replace(/^data:\s?/, ""));
|
|
1698
|
+
} catch {
|
|
1699
|
+
continue;
|
|
1700
|
+
}
|
|
1701
|
+
const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
1702
|
+
const phase = event.phase || "?";
|
|
1703
|
+
if (phase === "panel_done") {
|
|
1704
|
+
progress.done = event.panels_completed ?? progress.done + 1;
|
|
1705
|
+
progress.total = event.panels_total ?? progress.total;
|
|
1706
|
+
if (event.status === "placeholder") progress.placeholders += 1;
|
|
1707
|
+
if (event.status === "retry-recovered") progress.retries += 1;
|
|
1708
|
+
const mark =
|
|
1709
|
+
event.status === "ok"
|
|
1710
|
+
? "✓"
|
|
1711
|
+
: event.status === "retry-recovered"
|
|
1712
|
+
? "↻"
|
|
1713
|
+
: "⚠";
|
|
1714
|
+
console.error(
|
|
1715
|
+
`${elapsed}s ${mark} ${event.panel} [${progress.done}/${progress.total}]`
|
|
1716
|
+
);
|
|
1717
|
+
} else if (phase === "anchor_done") {
|
|
1718
|
+
console.error(
|
|
1719
|
+
`${elapsed}s anchor → ${event.verdict_label || event.verdict}`
|
|
1720
|
+
);
|
|
1721
|
+
} else if (phase === "assembling") {
|
|
1722
|
+
console.error(`${elapsed}s assembling…`);
|
|
1723
|
+
} else if (phase === "done") {
|
|
1724
|
+
doneEvent = event;
|
|
1725
|
+
} else if (phase === "error") {
|
|
1726
|
+
throw new Error(`Memo composer error: ${event.error}`);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
if (!doneEvent) {
|
|
1732
|
+
throw new Error("Stream ended without 'done' event.");
|
|
1733
|
+
}
|
|
1734
|
+
print({
|
|
1735
|
+
ok: true,
|
|
1736
|
+
version: doneEvent.version,
|
|
1737
|
+
degraded: doneEvent.degraded,
|
|
1738
|
+
model: doneEvent.model,
|
|
1739
|
+
duration_ms: doneEvent.duration_ms,
|
|
1740
|
+
placeholders: progress.placeholders,
|
|
1741
|
+
retries: progress.retries,
|
|
1742
|
+
});
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// save — upload hand-written HTML as a manual override.
|
|
1747
|
+
if (sub === "save") {
|
|
1748
|
+
const { flags } = parseFlags(rest);
|
|
1749
|
+
const dealId = rest[0];
|
|
1750
|
+
if (!dealId || !flags.file) {
|
|
1751
|
+
throw new Error("Usage: llama memo save <dealId> --file <path>");
|
|
1752
|
+
}
|
|
1753
|
+
const { readFileSync } = await import("fs");
|
|
1754
|
+
const html = readFileSync(String(flags.file), "utf-8");
|
|
1755
|
+
if (!html.trim()) throw new Error(`File ${flags.file} is empty.`);
|
|
1756
|
+
print(
|
|
1757
|
+
await request(
|
|
1758
|
+
"PUT",
|
|
1759
|
+
`/api/deals/${encodeURIComponent(dealId)}/memo`,
|
|
1760
|
+
{ html }
|
|
1761
|
+
)
|
|
1762
|
+
);
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// reset — default drops only the manual override (next read returns
|
|
1767
|
+
// the composed row, if any); --all drops every version for this deal.
|
|
1768
|
+
if (sub === "reset") {
|
|
1769
|
+
const dealId = rest[0];
|
|
1770
|
+
if (!dealId) {
|
|
1771
|
+
throw new Error("Usage: llama memo reset <dealId> [--all]");
|
|
1772
|
+
}
|
|
1773
|
+
const { flags } = parseFlags(rest.slice(1));
|
|
1774
|
+
print(
|
|
1775
|
+
await request(
|
|
1776
|
+
"DELETE",
|
|
1777
|
+
`/api/deals/${encodeURIComponent(dealId)}/memo`,
|
|
1778
|
+
{ scope: flags.all ? "all" : "override_only" }
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
throw new Error(
|
|
1785
|
+
`Unknown memo subcommand "${sub || ""}". Use: show / regenerate / save / reset.`
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1591
1789
|
usage();
|
|
1592
1790
|
process.exitCode = 1;
|
|
1593
1791
|
}
|
package/lib/external.mjs
CHANGED
|
@@ -18,14 +18,11 @@ import { getBaseUrl } from "./client.mjs";
|
|
|
18
18
|
const SESSION_DIR = path.join(os.homedir(), ".llama");
|
|
19
19
|
const SESSION_FILE = path.join(SESSION_DIR, "external-session.json");
|
|
20
20
|
|
|
21
|
-
// Server-side proof-of-work prefix.
|
|
22
|
-
//
|
|
23
|
-
// commodity hardware (~50–500ms in node).
|
|
21
|
+
// Server-side proof-of-work prefix. Server-validated; tune in tandem
|
|
22
|
+
// with the server policy if changed.
|
|
24
23
|
const POW_DIFFICULTY_PREFIX = "0000";
|
|
25
24
|
|
|
26
|
-
//
|
|
27
|
-
// backdate by 4s when computing PoW so the request lands inside the
|
|
28
|
-
// validity window without waiting.
|
|
25
|
+
// Backdate offset for the rendered-at timestamp passed to the server.
|
|
29
26
|
const POW_BACKDATE_MS = 4_000;
|
|
30
27
|
|
|
31
28
|
// ============================================================
|
|
@@ -360,13 +357,13 @@ export async function uploadExternalFile(filePath) {
|
|
|
360
357
|
|
|
361
358
|
if (!res.ok) {
|
|
362
359
|
if (res.status === 413) {
|
|
363
|
-
throw new Error("File too large
|
|
360
|
+
throw new Error("File too large.");
|
|
364
361
|
}
|
|
365
362
|
if (res.status === 415) {
|
|
366
363
|
throw new Error(`MIME type "${mimetype}" not in server allowlist.`);
|
|
367
364
|
}
|
|
368
365
|
if (res.status === 429) {
|
|
369
|
-
throw new Error("Upload cap reached
|
|
366
|
+
throw new Error("Upload cap reached.");
|
|
370
367
|
}
|
|
371
368
|
if (res.status === 401 || res.status === 403) {
|
|
372
369
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llamaventures/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "CLI + MCP server for the Llama Ventures investment workbench (command.llamaventures.vc).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"llama": "bin/llama.mjs",
|