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 +9 -2
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +49 -13
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
487
|
+
return readJsonResponse(await daemonFetch(url));
|
|
464
488
|
},
|
|
465
489
|
async globalDashboard() {
|
|
466
|
-
return readJsonResponse(await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) : {};
|