mercury-agent 0.5.17 → 0.5.18-beta.3

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.
@@ -11,6 +11,7 @@ import {
11
11
  import { createRequire } from "node:module";
12
12
  import { homedir, tmpdir } from "node:os";
13
13
  import { delimiter, dirname, join } from "node:path";
14
+ import { getApiKeyFromPiAuthFile } from "mercury-agent/storage/pi-auth";
14
15
 
15
16
  const KNOWLEDGE_DIR = "knowledge";
16
17
  const VAULT_DIRS = ["people", "projects", "references", "daily", "episodes", "weekly", "monthly", "templates"];
@@ -175,6 +176,7 @@ function runPromptAgent(
175
176
  vaultDir: string,
176
177
  promptPath: string,
177
178
  instruction: string,
179
+ extraEnv?: Record<string, string>,
178
180
  ): Promise<{ ok: boolean; detail?: string }> {
179
181
  let promptText: string;
180
182
  try {
@@ -206,7 +208,7 @@ function runPromptAgent(
206
208
  ],
207
209
  {
208
210
  cwd: vaultDir,
209
- env: envWithPiOnPath(),
211
+ env: { ...envWithPiOnPath(), ...extraEnv },
210
212
  // Capture stderr (a background job has no console to inherit usefully):
211
213
  // the captured tail is what makes a non-zero exit diagnosable in logs.
212
214
  stdio: ["ignore", "inherit", "pipe"],
@@ -236,11 +238,13 @@ function runPromptAgent(
236
238
  function runDistiller(
237
239
  vaultDir: string,
238
240
  dateFile: string,
241
+ extraEnv?: Record<string, string>,
239
242
  ): Promise<{ ok: boolean; detail?: string }> {
240
243
  return runPromptAgent(
241
244
  vaultDir,
242
245
  KB_DISTILLER_PROMPT_PATH,
243
246
  `Distill knowledge from: ${dateFile}`,
247
+ extraEnv,
244
248
  );
245
249
  }
246
250
 
@@ -399,6 +403,38 @@ export default function (mercury: {
399
403
  };
400
404
  });
401
405
 
406
+ // ---------------------------------------------------------------------------
407
+ // LLM credential resolution for host-side pi spawns
408
+ // ---------------------------------------------------------------------------
409
+
410
+ async function resolvePiAuthEnv(config: {
411
+ authPath?: string;
412
+ globalDir: string;
413
+ modelProvider: string;
414
+ }): Promise<Record<string, string>> {
415
+ const env: Record<string, string> = {};
416
+
417
+ // 1. Explicit env vars (strip MERCURY_ prefix, matching container-runner)
418
+ if (process.env.MERCURY_ANTHROPIC_API_KEY) {
419
+ env.ANTHROPIC_API_KEY = process.env.MERCURY_ANTHROPIC_API_KEY;
420
+ }
421
+ if (process.env.MERCURY_ANTHROPIC_OAUTH_TOKEN) {
422
+ env.ANTHROPIC_API_KEY = process.env.MERCURY_ANTHROPIC_OAUTH_TOKEN;
423
+ }
424
+
425
+ // 2. Fall back to Mercury's auth.json (OAuth token refresh)
426
+ if (!env.ANTHROPIC_API_KEY) {
427
+ const authPath = config.authPath ?? join(config.globalDir, "auth.json");
428
+ const key = await getApiKeyFromPiAuthFile({
429
+ provider: config.modelProvider,
430
+ authPath,
431
+ });
432
+ if (key) env.ANTHROPIC_API_KEY = key;
433
+ }
434
+
435
+ return env;
436
+ }
437
+
402
438
  // ---------------------------------------------------------------------------
403
439
  // KB Distillation job
404
440
  // ---------------------------------------------------------------------------
@@ -417,6 +453,8 @@ export default function (mercury: {
417
453
  return;
418
454
  }
419
455
 
456
+ const piAuthEnv = await resolvePiAuthEnv(ctx.config);
457
+
420
458
  const db = new Database(dbPath, { readonly: true });
421
459
 
422
460
  const spaces = db
@@ -561,7 +599,7 @@ export default function (mercury: {
561
599
  // --- Step 6: Distill each eligible date ---
562
600
  for (const date of eligibleDates) {
563
601
  const dateFile = join(messagesDir, `${date}.jsonl`);
564
- const result = await runDistiller(knowledgeDir, dateFile);
602
+ const result = await runDistiller(knowledgeDir, dateFile, piAuthEnv);
565
603
  if (result.ok) {
566
604
  ctx.log.info("Distillation complete", { spaceId, date });
567
605
  // Only persist past dates to distilled set (today will always be re-checked)
@@ -642,6 +680,9 @@ export default function (mercury: {
642
680
 
643
681
  const dbPath = join(ctx.config.dataDir, "state.db");
644
682
  if (!existsSync(dbPath)) return;
683
+
684
+ const piAuthEnv = await resolvePiAuthEnv(ctx.config);
685
+
645
686
  const db = new Database(dbPath, { readonly: true });
646
687
 
647
688
  const spaces = db
@@ -706,6 +747,7 @@ export default function (mercury: {
706
747
  knowledgeDir,
707
748
  WEEKLY_CONSOLIDATION_PROMPT_PATH,
708
749
  instruction,
750
+ piAuthEnv,
709
751
  );
710
752
  if (result.ok) {
711
753
  ctx.log.info("Weekly consolidation complete", { spaceId, week });
@@ -777,6 +819,7 @@ export default function (mercury: {
777
819
  knowledgeDir,
778
820
  MONTHLY_CONSOLIDATION_PROMPT_PATH,
779
821
  instruction,
822
+ piAuthEnv,
780
823
  );
781
824
  if (result.ok) {
782
825
  ctx.log.info("Monthly consolidation complete", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.5.17",
3
+ "version": "0.5.18-beta.3",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",
@@ -24,6 +24,10 @@
24
24
  "types": "./src/extensions/types.ts",
25
25
  "default": "./src/extensions/types.ts"
26
26
  },
27
+ "./storage/pi-auth": {
28
+ "types": "./src/storage/pi-auth.ts",
29
+ "default": "./src/storage/pi-auth.ts"
30
+ },
27
31
  "./tts": {
28
32
  "types": "./src/tts/index.ts",
29
33
  "default": "./src/tts/index.ts"
@@ -18,6 +18,7 @@ import { basename, dirname, join, resolve } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { Command } from "commander";
20
20
  import { loadConfig, resolveProjectPath } from "../config.js";
21
+ import { getCatalogEntryByName } from "../extensions/catalog.js";
21
22
  import {
22
23
  checkExtensionIndexLoads,
23
24
  getProjectDataDir,
@@ -1930,6 +1931,13 @@ function extensionsListAction(): void {
1930
1931
  console.log(
1931
1932
  `${ext.name.padEnd(nameWidth)} ${features.padEnd(featWidth)}${tag}${desc}`,
1932
1933
  );
1934
+ const cat = getCatalogEntryByName(ext.name);
1935
+ if (cat?.prerequisites?.length) {
1936
+ const prereq = cat.prerequisites.join(", ");
1937
+ console.log(
1938
+ `${"".padEnd(nameWidth)} ${"".padEnd(featWidth)} Requires: ${prereq.slice(0, 60)}${prereq.length > 60 ? "…" : ""}`,
1939
+ );
1940
+ }
1933
1941
  }
1934
1942
  }
1935
1943
 
@@ -1698,11 +1698,14 @@ export function createDashboardRoutes(ctx: DashboardContext) {
1698
1698
  .filter(Boolean)
1699
1699
  .join(", ");
1700
1700
  const label = cat?.label ?? ext.name;
1701
+ const prereqHint = cat?.prerequisites?.length
1702
+ ? `<div class="muted" style="font-size:12px;margin-top:4px">Requires: ${escapeHtml(cat.prerequisites.join(", "))}</div>`
1703
+ : "";
1701
1704
  return `
1702
1705
  <tr>
1703
1706
  <td class="mono">${escapeHtml(ext.name)}</td>
1704
1707
  <td>${escapeHtml(label)}</td>
1705
- <td class="muted" style="max-width:320px">${escapeHtml(desc)}</td>
1708
+ <td class="muted" style="max-width:320px">${escapeHtml(desc)}${prereqHint}</td>
1706
1709
  <td class="muted">${escapeHtml(feats || "—")}</td>
1707
1710
  <td>
1708
1711
  <button type="button" class="btn btn-sm btn-danger"
@@ -1731,6 +1734,9 @@ export function createDashboardRoutes(ctx: DashboardContext) {
1731
1734
  const envHint = entry.requiredEnvVars?.length
1732
1735
  ? `<div class="muted" style="font-size:12px;margin-top:4px">Env: ${escapeHtml(entry.requiredEnvVars.join(", "))}</div>`
1733
1736
  : "";
1737
+ const prereqHint = entry.prerequisites?.length
1738
+ ? `<div class="muted" style="font-size:12px;margin-top:4px">Requires: ${escapeHtml(entry.prerequisites.join(", "))}</div>`
1739
+ : "";
1734
1740
  const installBtn = missing
1735
1741
  ? `<button type="button" class="btn btn-sm" disabled title="examples/extensions missing in this install">Unavailable</button>`
1736
1742
  : `<form style="display:inline" hx-post="/dashboard/api/extensions/install" hx-target="#features-toast" hx-swap="innerHTML">
@@ -1744,6 +1750,7 @@ export function createDashboardRoutes(ctx: DashboardContext) {
1744
1750
  <td class="muted" style="max-width:320px">
1745
1751
  ${escapeHtml(entry.description)}
1746
1752
  ${envHint}
1753
+ ${prereqHint}
1747
1754
  </td>
1748
1755
  <td class="muted">${escapeHtml(entry.category)}</td>
1749
1756
  <td>${installBtn}</td>
@@ -19,6 +19,8 @@ export interface ExtensionCatalogEntry {
19
19
  /** Subdirectory of `examples/extensions/` to copy from */
20
20
  sourceDir: string;
21
21
  requiredEnvVars?: string[];
22
+ /** Host-level prerequisites (CLI tools, system packages) shown to the user before install */
23
+ prerequisites?: string[];
22
24
  requiresRestart: boolean;
23
25
  }
24
26
 
@@ -39,6 +41,7 @@ export const EXTENSION_CATALOG: ExtensionCatalogEntry[] = [
39
41
  "Obsidian-style vault, napkin CLI, and optional KB distillation job.",
40
42
  category: "knowledge",
41
43
  sourceDir: "napkin",
44
+ prerequisites: ["pi CLI: bun install -g @earendil-works/pi-coding-agent"],
42
45
  requiresRestart: true,
43
46
  },
44
47
  {