peon-mem 1.0.8 → 1.0.9

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/bin/peon-mem.mjs CHANGED
@@ -25,12 +25,16 @@ const DAEMON = join(PKG, "dist", "daemon-cli.js");
25
25
  const MCP = join(PKG, "dist", "index.js");
26
26
  const NODE = process.execPath;
27
27
 
28
- const cmd = process.argv[2] || "help";
28
+ // An MCP client launches `npx -y peon-mem` with no arguments and stdin as a pipe; that is
29
+ // exactly what the MCP registry entry and marketplaces install. Serve MCP then. A person at a
30
+ // terminal (stdin is a TTY) still gets the help text.
31
+ const cmd = process.argv[2] || (process.stdin.isTTY ? "help" : "mcp");
29
32
  const DRY = process.argv.includes("--dry-run");
30
33
  const YES = process.argv.includes("--yes") || !process.stdout.isTTY;
31
34
  const log = (s) => console.log(s);
32
35
  const act = (desc, fn) => { log((DRY ? " [dry-run] " : " ✔ ") + desc); if (!DRY) fn(); };
33
- const rl = YES ? null : createInterface({ input: process.stdin, output: process.stdout });
36
+ // In MCP mode stdin/stdout carry JSON-RPC, so nothing else may read stdin or write stdout.
37
+ const rl = YES || cmd === "mcp" ? null : createInterface({ input: process.stdin, output: process.stdout });
34
38
  async function ask(q, def) {
35
39
  if (!rl) return def;
36
40
  const a = (await rl.question(`${q}${def ? ` [${def}]` : ""}: `)).trim();
@@ -306,6 +310,8 @@ if (cmd === "install") {
306
310
  log("Remove [mcp_servers.peon] / mcpServers.peon from Codex/Gemini/Cursor/Cline configs if you added them.");
307
311
  log("Memory data untouched: <project>/.peon/ and " + DEFAULT_HOME);
308
312
  rl?.close();
313
+ } else if (cmd === "mcp") {
314
+ await import(MCP);
309
315
  } else if (cmd === "daemon") {
310
316
  await import(DAEMON);
311
317
  } else if (cmd === "doctor") {
@@ -319,6 +325,7 @@ if (cmd === "install") {
319
325
  log("peon-mem — memory brain for AI coding agents");
320
326
  log(" peon-mem install [--yes] [--dry-run] guided setup (memory home → LLM → daemon → apps)");
321
327
  log(" peon-mem uninstall remove service + hooks (data stays)");
328
+ log(" peon-mem mcp run the MCP server over stdio (what MCP clients launch)");
322
329
  log(" peon-mem daemon run daemon in foreground");
323
330
  log(" peon-mem doctor health check");
324
331
  rl?.close();
package/dist/tools.d.ts CHANGED
@@ -233,4 +233,13 @@ export interface PeonTools {
233
233
  export declare function openStoreCacheStats(): {
234
234
  openStores: number;
235
235
  };
236
+ /**
237
+ * PEON_DAEMON_URL as an http(s) base URL, or undefined to run the tools in-process.
238
+ *
239
+ * The variable is optional, and registry listings render it with a placeholder value
240
+ * ("your-peon-daemon-url-here"). A copied placeholder must not turn a working in-process
241
+ * server into one where every call fails, so anything that is not an http(s) URL is
242
+ * dropped with a warning on stderr (stdout carries the MCP protocol).
243
+ */
244
+ export declare function resolveDaemonUrl(raw: string | undefined, warn?: (message: string) => void): string | undefined;
236
245
  export declare function createPeonTools(options?: CreatePeonToolsOptions): PeonTools;
package/dist/tools.js CHANGED
@@ -28,9 +28,33 @@ let openStoreCount = 0;
28
28
  export function openStoreCacheStats() {
29
29
  return { openStores: openStoreCount };
30
30
  }
31
+ /**
32
+ * PEON_DAEMON_URL as an http(s) base URL, or undefined to run the tools in-process.
33
+ *
34
+ * The variable is optional, and registry listings render it with a placeholder value
35
+ * ("your-peon-daemon-url-here"). A copied placeholder must not turn a working in-process
36
+ * server into one where every call fails, so anything that is not an http(s) URL is
37
+ * dropped with a warning on stderr (stdout carries the MCP protocol).
38
+ */
39
+ export function resolveDaemonUrl(raw, warn = (message) => console.warn(message)) {
40
+ const value = raw?.trim();
41
+ if (!value)
42
+ return undefined;
43
+ try {
44
+ const { protocol } = new URL(value);
45
+ if (protocol === "http:" || protocol === "https:")
46
+ return value;
47
+ }
48
+ catch {
49
+ // not a URL at all: fall through to the warning
50
+ }
51
+ warn(`[peon] ignoring PEON_DAEMON_URL=${JSON.stringify(value)}: not an http(s) URL. Running the tools in-process instead.`);
52
+ return undefined;
53
+ }
31
54
  export function createPeonTools(options = {}) {
32
- if (options.daemonUrl) {
33
- return createDaemonBackedTools(options.daemonUrl);
55
+ const daemonUrl = resolveDaemonUrl(options.daemonUrl);
56
+ if (daemonUrl) {
57
+ return createDaemonBackedTools(daemonUrl);
34
58
  }
35
59
  const storesByProject = new Map();
36
60
  const sessionIndex = new SessionIndex(options.sessionIndexPath);
@@ -392,7 +416,7 @@ function createDaemonBackedTools(daemonUrl) {
392
416
  url.searchParams.set("query", input.query);
393
417
  if (input.maxChars)
394
418
  url.searchParams.set("maxChars", String(input.maxChars));
395
- const response = await fetch(url);
419
+ const response = await daemonFetch(url);
396
420
  return readJsonResponse(response);
397
421
  },
398
422
  async inspectBrain(input) {
@@ -402,7 +426,7 @@ function createDaemonBackedTools(daemonUrl) {
402
426
  url.searchParams.set("query", input.query);
403
427
  if (input.maxChars)
404
428
  url.searchParams.set("maxChars", String(input.maxChars));
405
- const response = await fetch(url);
429
+ const response = await daemonFetch(url);
406
430
  return readJsonResponse(response);
407
431
  },
408
432
  async searchMemory(input) {
@@ -413,7 +437,7 @@ function createDaemonBackedTools(daemonUrl) {
413
437
  url.searchParams.set("limit", String(input.limit));
414
438
  if (input.maxChars)
415
439
  url.searchParams.set("maxChars", String(input.maxChars));
416
- const response = await fetch(url);
440
+ const response = await daemonFetch(url);
417
441
  return readJsonResponse(response);
418
442
  },
419
443
  async qualityReport(input) {
@@ -421,7 +445,7 @@ function createDaemonBackedTools(daemonUrl) {
421
445
  url.searchParams.set("projectPath", input.projectPath);
422
446
  if (input.staleAfterDays)
423
447
  url.searchParams.set("staleAfterDays", String(input.staleAfterDays));
424
- const response = await fetch(url);
448
+ const response = await daemonFetch(url);
425
449
  return readJsonResponse(response);
426
450
  },
427
451
  async rememberGlobal(input) {
@@ -435,7 +459,7 @@ function createDaemonBackedTools(daemonUrl) {
435
459
  url.searchParams.set("type", input.type);
436
460
  if (input.status)
437
461
  url.searchParams.set("status", input.status);
438
- const response = await fetch(url);
462
+ const response = await daemonFetch(url);
439
463
  return readJsonResponse(response);
440
464
  },
441
465
  async importGlobalMemory(input) {
@@ -460,17 +484,17 @@ function createDaemonBackedTools(daemonUrl) {
460
484
  const url = new URL(`${baseUrl}/brain/activity`);
461
485
  if (input.limit)
462
486
  url.searchParams.set("limit", String(input.limit));
463
- return readJsonResponse(await fetch(url));
487
+ return readJsonResponse(await daemonFetch(url));
464
488
  },
465
489
  async globalDashboard() {
466
- return readJsonResponse(await fetch(`${baseUrl}/global/dashboard`));
490
+ return readJsonResponse(await daemonFetch(`${baseUrl}/global/dashboard`));
467
491
  },
468
492
  async brainActions(input) {
469
493
  const url = new URL(`${baseUrl}/brain/actions`);
470
494
  url.searchParams.set("projectPath", input.projectPath);
471
495
  if (input.limit)
472
496
  url.searchParams.set("limit", String(input.limit));
473
- return readJsonResponse(await fetch(url));
497
+ return readJsonResponse(await daemonFetch(url));
474
498
  },
475
499
  async restoreBackup(input) {
476
500
  return postJson(`${baseUrl}/brain/restore`, input);
@@ -496,7 +520,7 @@ function createDaemonBackedTools(daemonUrl) {
496
520
  url.searchParams.set("maxChars", String(input.maxChars));
497
521
  if (input.includeInactive)
498
522
  url.searchParams.set("includeInactive", "true");
499
- const response = await fetch(url);
523
+ const response = await daemonFetch(url);
500
524
  return readJsonResponse(response);
501
525
  },
502
526
  async crossProjectSearch(input) {
@@ -512,7 +536,7 @@ function createDaemonBackedTools(daemonUrl) {
512
536
  url.searchParams.set("limit", String(input.limit));
513
537
  if (input.maxProjects)
514
538
  url.searchParams.set("maxProjects", String(input.maxProjects));
515
- const response = await fetch(url);
539
+ const response = await daemonFetch(url);
516
540
  return readJsonResponse(response);
517
541
  },
518
542
  async processMemory(input) {
@@ -554,13 +578,25 @@ function formatSearchInjectionPreview(records) {
554
578
  return ["Peon Search Results", ...records.map(formatRankedMemoryRecord)].join("\n");
555
579
  }
556
580
  async function postJson(url, body) {
557
- const response = await fetch(url, {
581
+ const response = await daemonFetch(url, {
558
582
  method: "POST",
559
583
  headers: { "content-type": "application/json" },
560
584
  body: JSON.stringify(body)
561
585
  });
562
586
  return readJsonResponse(response);
563
587
  }
588
+ /** fetch against the daemon, with an actionable error when nothing answers there. */
589
+ async function daemonFetch(url, init) {
590
+ try {
591
+ return await fetch(url, init);
592
+ }
593
+ catch (error) {
594
+ const reason = error.cause?.code ?? (error instanceof Error ? error.message : String(error));
595
+ throw new Error(`Peon daemon is not reachable at ${new URL(String(url)).origin} (${reason}). Start it with ` +
596
+ `"npx peon-mem install" (sets it up as a background service) or "npx peon-mem daemon", ` +
597
+ `or unset PEON_DAEMON_URL to run the tools in-process.`);
598
+ }
599
+ }
564
600
  async function readJsonResponse(response) {
565
601
  const text = await response.text();
566
602
  const body = text ? JSON.parse(text) : {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peon-mem",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {