@pyxmate/memory 1.2.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/dist/agent-contract.d.ts +1 -1
- package/dist/agent-contract.mjs +1 -1
- package/dist/{chunk-3SDKJ5TB.mjs → chunk-BS6K64SA.mjs} +140 -4
- package/dist/{chunk-XHEVB23R.mjs → chunk-ML6VKYAL.mjs} +3 -2
- package/dist/{chunk-3PBLTOBR.mjs → chunk-MZF55IUR.mjs} +1 -1
- package/dist/cli/pyx-mem.mjs +221 -810
- package/dist/dashboard.mjs +2 -3
- package/dist/index.mjs +4 -6
- package/dist/react.mjs +2 -3
- package/package.json +1 -1
- package/dist/chunk-X6AYWXW7.mjs +0 -143
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
STORE_ENTITIES_DESC,
|
|
7
|
-
STORE_EVENT_TIME_DESC,
|
|
8
|
-
STORE_RELATIONSHIPS_DESC,
|
|
9
|
-
STORE_TOOL_DESC,
|
|
10
|
-
STORE_TRIPLES_DESC,
|
|
11
|
-
STRUCTURE_GRAPH_PROMPT_DESC,
|
|
12
|
-
buildDesignGuide,
|
|
13
|
-
buildGraphStructuringPrompt
|
|
14
|
-
} from "../chunk-XHEVB23R.mjs";
|
|
15
|
-
import {
|
|
16
|
-
normalizeGraphLabel,
|
|
17
|
-
normalizeNameKey
|
|
18
|
-
} from "../chunk-X6AYWXW7.mjs";
|
|
3
|
+
PERSISTENT_MEMORY_SECTION,
|
|
4
|
+
buildDesignGuide
|
|
5
|
+
} from "../chunk-ML6VKYAL.mjs";
|
|
19
6
|
|
|
20
7
|
// src/cli/exit-codes.ts
|
|
21
8
|
var EXIT = {
|
|
@@ -352,7 +339,7 @@ async function promptMasked(label) {
|
|
|
352
339
|
`);
|
|
353
340
|
return value;
|
|
354
341
|
}
|
|
355
|
-
return new Promise((
|
|
342
|
+
return new Promise((resolve3, reject) => {
|
|
356
343
|
const previousEncoding = stdin.readableEncoding;
|
|
357
344
|
stdin.setEncoding("utf8");
|
|
358
345
|
stdin.setRawMode(true);
|
|
@@ -370,7 +357,7 @@ async function promptMasked(label) {
|
|
|
370
357
|
if (ch === "\n" || ch === "\r") {
|
|
371
358
|
cleanup();
|
|
372
359
|
stdout.write("\n");
|
|
373
|
-
|
|
360
|
+
resolve3(buf.trim());
|
|
374
361
|
return;
|
|
375
362
|
}
|
|
376
363
|
if (ch === "") {
|
|
@@ -811,7 +798,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
811
798
|
return server;
|
|
812
799
|
}
|
|
813
800
|
async function runMcpProxyServer(opts) {
|
|
814
|
-
const version = opts.version ?? (true ? "1.
|
|
801
|
+
const version = opts.version ?? (true ? "1.4.0" : "0.0.0-dev");
|
|
815
802
|
const read = await opts.readCredentials();
|
|
816
803
|
if (!read.ok) {
|
|
817
804
|
const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
|
|
@@ -828,14 +815,14 @@ async function runMcpProxyServer(opts) {
|
|
|
828
815
|
} catch (err) {
|
|
829
816
|
const msg = err instanceof Error ? err.message : String(err);
|
|
830
817
|
throw new Error(
|
|
831
|
-
`Could not reach the pyx-memory remote MCP at ${url.href}: ${msg}. Verify the endpoint and key with \`pyx-mem doctor
|
|
818
|
+
`Could not reach the pyx-memory remote MCP at ${url.href}: ${msg}. Verify the endpoint and key with \`pyx-mem doctor\`.`
|
|
832
819
|
);
|
|
833
820
|
}
|
|
834
821
|
try {
|
|
835
822
|
const server = createProxyServer(client, version, createLocalFileUploader(read.credentials));
|
|
836
823
|
const transport = new StdioServerTransport();
|
|
837
|
-
const closed = new Promise((
|
|
838
|
-
transport.onclose = () =>
|
|
824
|
+
const closed = new Promise((resolve3) => {
|
|
825
|
+
transport.onclose = () => resolve3();
|
|
839
826
|
});
|
|
840
827
|
await server.connect(transport);
|
|
841
828
|
await closed;
|
|
@@ -845,765 +832,15 @@ async function runMcpProxyServer(opts) {
|
|
|
845
832
|
}
|
|
846
833
|
}
|
|
847
834
|
|
|
848
|
-
// src/mcp/server.ts
|
|
849
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
850
|
-
import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
851
|
-
|
|
852
|
-
// src/mcp/prompts/index.ts
|
|
853
|
-
import { z } from "zod";
|
|
854
|
-
var structureGraphPrompt = {
|
|
855
|
-
name: "structure_graph",
|
|
856
|
-
description: STRUCTURE_GRAPH_PROMPT_DESC,
|
|
857
|
-
register(server) {
|
|
858
|
-
server.registerPrompt(
|
|
859
|
-
"structure_graph",
|
|
860
|
-
{
|
|
861
|
-
title: "Structure content into a knowledge graph",
|
|
862
|
-
description: STRUCTURE_GRAPH_PROMPT_DESC,
|
|
863
|
-
argsSchema: {
|
|
864
|
-
content: z.string().min(1).describe("The memory content to extract graph fields from.")
|
|
865
|
-
}
|
|
866
|
-
},
|
|
867
|
-
({ content }) => ({
|
|
868
|
-
messages: [
|
|
869
|
-
{ role: "user", content: { type: "text", text: buildGraphStructuringPrompt(content) } }
|
|
870
|
-
]
|
|
871
|
-
})
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
var ALL_PROMPTS = [structureGraphPrompt];
|
|
876
|
-
|
|
877
|
-
// src/mcp/tools/corrections.ts
|
|
878
|
-
import { z as z3 } from "zod";
|
|
879
|
-
|
|
880
|
-
// src/mcp/tools/scopes.ts
|
|
881
|
-
import { z as z2 } from "zod";
|
|
882
|
-
var scopeShape = {
|
|
883
|
-
tenantId: z2.string().optional().describe("Sent as X-Tenant-Id for multi-tenant isolation."),
|
|
884
|
-
userId: z2.string().optional().describe("Sent as X-User-Id."),
|
|
885
|
-
teamId: z2.string().optional().describe("Sent as X-Team-Id."),
|
|
886
|
-
callerAccessLevel: z2.enum(["public", "internal", "secret"]).optional().describe("Sent as X-Caller-Access-Level for sensitivity filtering.")
|
|
887
|
-
};
|
|
888
|
-
|
|
889
|
-
// src/mcp/tools/corrections.ts
|
|
890
|
-
var recordInputShape = {
|
|
891
|
-
namespaceId: z3.string().min(1).optional().describe("Namespace id to record into. Omit in single-tenant deployments (namespace-free)."),
|
|
892
|
-
whatWasWrong: z3.string().min(1).describe("What the agent did wrong."),
|
|
893
|
-
whatToDoInstead: z3.string().min(1).describe("The corrective instruction to follow instead."),
|
|
894
|
-
appliesWhen: z3.string().min(1).describe("When this correction applies \u2014 the task context future tasks are matched against."),
|
|
895
|
-
project: z3.string().optional().describe("Optional project scope; omit to apply to all projects."),
|
|
896
|
-
taskShape: z3.string().optional().describe("Optional task-shape hint stored for provenance."),
|
|
897
|
-
...scopeShape
|
|
898
|
-
};
|
|
899
|
-
var recordCorrectionTool = {
|
|
900
|
-
name: "record_correction",
|
|
901
|
-
config: {
|
|
902
|
-
title: "Record a pyx-memory correction",
|
|
903
|
-
description: 'Record an explicit user correction so the same mistake can be avoided next time. Call this when the user explicitly corrects the agent ("you did X wrong; do Y instead"). HTTP proxy to POST /api/memory/corrections \u2014 returns `{id, createdAt}`.',
|
|
904
|
-
inputSchema: recordInputShape,
|
|
905
|
-
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
906
|
-
},
|
|
907
|
-
handler: (deps) => async (raw) => {
|
|
908
|
-
const args = raw;
|
|
909
|
-
const creds = await deps.readCredentials();
|
|
910
|
-
if (!creds.ok) return creds.result;
|
|
911
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
912
|
-
const res = await http.requestJson({
|
|
913
|
-
method: "POST",
|
|
914
|
-
path: "/api/memory/corrections",
|
|
915
|
-
body: {
|
|
916
|
-
...args.namespaceId !== void 0 ? { namespaceId: args.namespaceId } : {},
|
|
917
|
-
whatWasWrong: args.whatWasWrong,
|
|
918
|
-
whatToDoInstead: args.whatToDoInstead,
|
|
919
|
-
appliesWhen: args.appliesWhen,
|
|
920
|
-
...args.project !== void 0 ? { project: args.project } : {},
|
|
921
|
-
...args.taskShape !== void 0 ? { taskShape: args.taskShape } : {}
|
|
922
|
-
},
|
|
923
|
-
scope: args
|
|
924
|
-
});
|
|
925
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
var fetchInputShape = {
|
|
929
|
-
namespaceId: z3.string().min(1).optional().describe("Namespace id whose corrections to fetch. Omit in single-tenant deployments."),
|
|
930
|
-
taskShape: z3.string().min(1).describe("Shape of the task about to run \u2014 scored against each correction."),
|
|
931
|
-
project: z3.string().optional().describe("Optional project filter."),
|
|
932
|
-
limit: z3.number().int().positive().optional().describe("Max corrections to return (cap 5)."),
|
|
933
|
-
...scopeShape
|
|
934
|
-
};
|
|
935
|
-
var fetchApplicableCorrectionsTool = {
|
|
936
|
-
name: "fetch_applicable_corrections",
|
|
937
|
-
config: {
|
|
938
|
-
title: "Fetch applicable pyx-memory corrections",
|
|
939
|
-
description: "Fetch the corrections applicable to a task shape (\u22645, ranked by overlap then recency). pyx never auto-prepends \u2014 you decide whether to follow them. HTTP proxy to GET /api/memory/corrections \u2014 returns a `CorrectionRecord[]` (`[]` when none apply).",
|
|
940
|
-
inputSchema: fetchInputShape,
|
|
941
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
942
|
-
},
|
|
943
|
-
handler: (deps) => async (raw) => {
|
|
944
|
-
const args = raw;
|
|
945
|
-
const creds = await deps.readCredentials();
|
|
946
|
-
if (!creds.ok) return creds.result;
|
|
947
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
948
|
-
const res = await http.requestJson({
|
|
949
|
-
method: "GET",
|
|
950
|
-
path: "/api/memory/corrections",
|
|
951
|
-
query: {
|
|
952
|
-
namespaceId: args.namespaceId,
|
|
953
|
-
taskShape: args.taskShape,
|
|
954
|
-
project: args.project,
|
|
955
|
-
limit: args.limit
|
|
956
|
-
},
|
|
957
|
-
scope: args
|
|
958
|
-
});
|
|
959
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
// src/mcp/tools/delete.ts
|
|
964
|
-
import { z as z4 } from "zod";
|
|
965
|
-
var inputShape = {
|
|
966
|
-
id: z4.string().min(1).describe("Required memory entry id to delete."),
|
|
967
|
-
reason: z4.string().min(1).describe("Required reason for deletion (audit)."),
|
|
968
|
-
...scopeShape
|
|
969
|
-
};
|
|
970
|
-
var deleteMemoryTool = {
|
|
971
|
-
name: "delete_memory",
|
|
972
|
-
config: {
|
|
973
|
-
title: "Delete pyx-memory entry",
|
|
974
|
-
description: "Delete one memory entry by id with an explicit reason. Self-hosted instances require an admin key; cloud delegates to tenant policy.",
|
|
975
|
-
inputSchema: inputShape,
|
|
976
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true }
|
|
977
|
-
},
|
|
978
|
-
handler: (deps) => async (raw) => {
|
|
979
|
-
const args = raw;
|
|
980
|
-
const creds = await deps.readCredentials();
|
|
981
|
-
if (!creds.ok) return creds.result;
|
|
982
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
983
|
-
const res = await http.requestJson({
|
|
984
|
-
method: "DELETE",
|
|
985
|
-
path: `/api/memory/entries/${encodeURIComponent(args.id)}`,
|
|
986
|
-
query: { reason: args.reason },
|
|
987
|
-
scope: args
|
|
988
|
-
});
|
|
989
|
-
return res.ok ? mcpJson({ deleted: args.id }) : res.result;
|
|
990
|
-
}
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
// src/mcp/tools/get.ts
|
|
994
|
-
import { z as z5 } from "zod";
|
|
995
|
-
var inputShape2 = {
|
|
996
|
-
id: z5.string().min(1).describe("Required memory entry id."),
|
|
997
|
-
...scopeShape
|
|
998
|
-
};
|
|
999
|
-
var getMemoryTool = {
|
|
1000
|
-
name: "get_memory",
|
|
1001
|
-
config: {
|
|
1002
|
-
title: "Get pyx-memory entry",
|
|
1003
|
-
description: "Fetch one memory entry by id. Multi-tenant headers and X-Caller-Access-Level are honored for sensitivity redaction.",
|
|
1004
|
-
inputSchema: inputShape2,
|
|
1005
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1006
|
-
},
|
|
1007
|
-
handler: (deps) => async (raw) => {
|
|
1008
|
-
const args = raw;
|
|
1009
|
-
const creds = await deps.readCredentials();
|
|
1010
|
-
if (!creds.ok) return creds.result;
|
|
1011
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1012
|
-
const res = await http.requestJson({
|
|
1013
|
-
method: "GET",
|
|
1014
|
-
path: `/api/memory/entries/${encodeURIComponent(args.id)}`,
|
|
1015
|
-
scope: args
|
|
1016
|
-
});
|
|
1017
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
// src/mcp/tools/ingest.ts
|
|
1022
|
-
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
1023
|
-
import { basename as basename2, extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
1024
|
-
import { z as z6 } from "zod";
|
|
1025
|
-
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff", ".svg"]);
|
|
1026
|
-
var MAX_BYTES = 50 * 1024 * 1024;
|
|
1027
|
-
var INGEST_ENDPOINT = "/api/memory/ingest/file";
|
|
1028
|
-
var inputShape3 = {
|
|
1029
|
-
path: z6.string().min(1).describe(
|
|
1030
|
-
"Local file path readable by the pyx-mem process. Uploaded as multipart `file`. Images require `description`; documents auto-extract text."
|
|
1031
|
-
),
|
|
1032
|
-
description: z6.string().optional().describe(
|
|
1033
|
-
"REQUIRED for images so the entry is semantically searchable. Optional for documents with extractable text."
|
|
1034
|
-
),
|
|
1035
|
-
namespaceId: z6.string().optional().describe("Optional ReBAC namespace for entries created from this file."),
|
|
1036
|
-
...scopeShape
|
|
1037
|
-
};
|
|
1038
|
-
var ingestMemoryFileTool = {
|
|
1039
|
-
name: "ingest_memory_file",
|
|
1040
|
-
config: {
|
|
1041
|
-
title: "Ingest file into pyx-memory",
|
|
1042
|
-
description: "Upload a document or image into pyx-memory. Images REQUIRE description. Large docx/xlsx/pptx should be pre-extracted to txt/md. 50MB cap.",
|
|
1043
|
-
inputSchema: inputShape3,
|
|
1044
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true }
|
|
1045
|
-
},
|
|
1046
|
-
// `path` is a host-local file. On the remote `/mcp` route the server cannot
|
|
1047
|
-
// read it, so this descriptor lets the local proxy bridge the upload (read
|
|
1048
|
-
// bytes here, multipart-POST to the declared endpoint). The bundled handler
|
|
1049
|
-
// below ignores it. Field names map 1:1 to the multipart form built below.
|
|
1050
|
-
localUpload: {
|
|
1051
|
-
pathArg: "path",
|
|
1052
|
-
endpoint: INGEST_ENDPOINT,
|
|
1053
|
-
fileField: "file",
|
|
1054
|
-
optionalArgs: ["description", "namespaceId"],
|
|
1055
|
-
maxBytes: MAX_BYTES
|
|
1056
|
-
},
|
|
1057
|
-
handler: (deps) => async (raw) => {
|
|
1058
|
-
const args = raw;
|
|
1059
|
-
const ext = extname(args.path).toLowerCase();
|
|
1060
|
-
const isImage = IMAGE_EXT.has(ext);
|
|
1061
|
-
if (isImage && !args.description?.trim()) {
|
|
1062
|
-
return mcpText(
|
|
1063
|
-
`Image ingest requires a non-empty \`description\`. Provide a 1-2 sentence summary of the image content so it becomes searchable.`,
|
|
1064
|
-
true
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
const abs = isAbsolute2(args.path) ? args.path : resolve2(args.path);
|
|
1068
|
-
let bytes;
|
|
1069
|
-
try {
|
|
1070
|
-
const info = await stat2(abs);
|
|
1071
|
-
if (info.size > MAX_BYTES) {
|
|
1072
|
-
return mcpText(`File too large: ${info.size} bytes > 50MB cap.`, true);
|
|
1073
|
-
}
|
|
1074
|
-
bytes = await readFile2(abs);
|
|
1075
|
-
} catch (err) {
|
|
1076
|
-
return mcpText(
|
|
1077
|
-
`Failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}.`,
|
|
1078
|
-
true
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
const creds = await deps.readCredentials();
|
|
1082
|
-
if (!creds.ok) return creds.result;
|
|
1083
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1084
|
-
const res = await http.requestMultipart({
|
|
1085
|
-
path: INGEST_ENDPOINT,
|
|
1086
|
-
scope: args,
|
|
1087
|
-
formData: () => {
|
|
1088
|
-
const form = new FormData();
|
|
1089
|
-
const file = new File([new Uint8Array(bytes)], basename2(abs));
|
|
1090
|
-
form.set("file", file);
|
|
1091
|
-
if (args.description) form.set("description", args.description);
|
|
1092
|
-
if (args.namespaceId) form.set("namespaceId", args.namespaceId);
|
|
1093
|
-
return form;
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1096
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1097
|
-
}
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
// src/mcp/tools/lineage.ts
|
|
1101
|
-
import { z as z7 } from "zod";
|
|
1102
|
-
var inputShape4 = {
|
|
1103
|
-
subject: z7.string().optional().describe("Graph subject to trace, used with relation for graph lineage."),
|
|
1104
|
-
relation: z7.string().optional().describe("Graph relation to trace for the subject."),
|
|
1105
|
-
entryId: z7.string().optional().describe("Memory entry id to trace through its supersededBy chain."),
|
|
1106
|
-
asOf: z7.string().optional().describe("Only include lineage versions ingested by this ISO-8601 time."),
|
|
1107
|
-
eventTimeStart: z7.string().optional().describe("Inclusive event-time start (ISO-8601)."),
|
|
1108
|
-
eventTimeEnd: z7.string().optional().describe("Inclusive event-time end (ISO-8601)."),
|
|
1109
|
-
beforeValue: z7.string().optional().describe("Stop before the lineage version with this value."),
|
|
1110
|
-
limit: z7.number().int().positive().optional().describe("Maximum lineage versions to return."),
|
|
1111
|
-
...scopeShape
|
|
1112
|
-
};
|
|
1113
|
-
var lineageTool = {
|
|
1114
|
-
name: "lineage",
|
|
1115
|
-
config: {
|
|
1116
|
-
title: "Trace pyx-memory fact lineage",
|
|
1117
|
-
description: "Return the time-ordered history of how a fact evolved: subject+relation traces graph lineage, entryId traces a supersededBy chain. Use for questions like what did X use before Y or how did this fact evolve.",
|
|
1118
|
-
inputSchema: inputShape4,
|
|
1119
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1120
|
-
},
|
|
1121
|
-
handler: (deps) => async (raw) => {
|
|
1122
|
-
const args = raw;
|
|
1123
|
-
const creds = await deps.readCredentials();
|
|
1124
|
-
if (!creds.ok) return creds.result;
|
|
1125
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1126
|
-
const res = await http.requestJson({
|
|
1127
|
-
method: "GET",
|
|
1128
|
-
path: "/api/memory/lineage",
|
|
1129
|
-
query: {
|
|
1130
|
-
subject: args.subject,
|
|
1131
|
-
relation: args.relation,
|
|
1132
|
-
entryId: args.entryId,
|
|
1133
|
-
asOf: args.asOf,
|
|
1134
|
-
eventTimeStart: args.eventTimeStart,
|
|
1135
|
-
eventTimeEnd: args.eventTimeEnd,
|
|
1136
|
-
beforeValue: args.beforeValue,
|
|
1137
|
-
limit: args.limit
|
|
1138
|
-
},
|
|
1139
|
-
scope: args
|
|
1140
|
-
});
|
|
1141
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1142
|
-
}
|
|
1143
|
-
};
|
|
1144
|
-
|
|
1145
|
-
// src/mcp/tools/list.ts
|
|
1146
|
-
import { z as z8 } from "zod";
|
|
1147
|
-
var inputShape5 = {
|
|
1148
|
-
mode: z8.enum(["entries", "log"]).optional().describe(
|
|
1149
|
-
"Listing mode. `entries` (default) returns paginated entries by filter; `log` returns the chronological memory log."
|
|
1150
|
-
),
|
|
1151
|
-
page: z8.number().int().min(1).optional().describe("1-based page index for entries mode."),
|
|
1152
|
-
limit: z8.number().int().min(1).max(200).optional().describe("Page size; server clamps."),
|
|
1153
|
-
type: z8.enum(["short-term", "long-term", "working", "episodic", "summary"]).optional().describe("Filter by memory type."),
|
|
1154
|
-
agentId: z8.string().optional().describe("Filter to memories from this agentId."),
|
|
1155
|
-
since: z8.string().optional().describe("ISO-8601 lower bound for log mode."),
|
|
1156
|
-
...scopeShape
|
|
1157
|
-
};
|
|
1158
|
-
var listMemoriesTool = {
|
|
1159
|
-
name: "list_memories",
|
|
1160
|
-
config: {
|
|
1161
|
-
title: "List pyx-memory",
|
|
1162
|
-
description: "List recent memory entries or a chronological memory log. Same tool for both reads \u2014 `mode` controls which.",
|
|
1163
|
-
inputSchema: inputShape5,
|
|
1164
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1165
|
-
},
|
|
1166
|
-
handler: (deps) => async (raw) => {
|
|
1167
|
-
const args = raw;
|
|
1168
|
-
const creds = await deps.readCredentials();
|
|
1169
|
-
if (!creds.ok) return creds.result;
|
|
1170
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1171
|
-
const mode = args.mode ?? "entries";
|
|
1172
|
-
const path = mode === "log" ? "/api/memory/log" : "/api/memory/entries";
|
|
1173
|
-
const res = await http.requestJson({
|
|
1174
|
-
method: "GET",
|
|
1175
|
-
path,
|
|
1176
|
-
query: {
|
|
1177
|
-
page: args.page,
|
|
1178
|
-
limit: args.limit,
|
|
1179
|
-
type: args.type,
|
|
1180
|
-
agentId: args.agentId,
|
|
1181
|
-
since: args.since
|
|
1182
|
-
},
|
|
1183
|
-
scope: args
|
|
1184
|
-
});
|
|
1185
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
// src/mcp/tools/profile.ts
|
|
1190
|
-
import { z as z9 } from "zod";
|
|
1191
|
-
var getInputShape = {
|
|
1192
|
-
namespaceId: z9.string().min(1).describe("Namespace id whose user-profile to fetch."),
|
|
1193
|
-
...scopeShape
|
|
1194
|
-
};
|
|
1195
|
-
var getUserProfileTool = {
|
|
1196
|
-
name: "get_user_profile",
|
|
1197
|
-
config: {
|
|
1198
|
-
title: "Get pyx-memory user profile",
|
|
1199
|
-
description: "Fetch the current user-profile snapshot for a namespace. HTTP proxy to GET /api/memory/profile/user \u2014 returns `{content, updatedAt, contentSize}` or a not-found error.",
|
|
1200
|
-
inputSchema: getInputShape,
|
|
1201
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1202
|
-
},
|
|
1203
|
-
handler: (deps) => async (raw) => {
|
|
1204
|
-
const args = raw;
|
|
1205
|
-
const creds = await deps.readCredentials();
|
|
1206
|
-
if (!creds.ok) return creds.result;
|
|
1207
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1208
|
-
const res = await http.requestJson({
|
|
1209
|
-
method: "GET",
|
|
1210
|
-
path: "/api/memory/profile/user",
|
|
1211
|
-
query: { namespaceId: args.namespaceId },
|
|
1212
|
-
scope: args
|
|
1213
|
-
});
|
|
1214
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1215
|
-
}
|
|
1216
|
-
};
|
|
1217
|
-
var upsertInputShape = {
|
|
1218
|
-
namespaceId: z9.string().min(1).describe("Namespace id to upsert the user-profile into."),
|
|
1219
|
-
content: z9.string().min(1).describe("Full freeform profile content (UTF-8, \u22648192 bytes; server enforces the cap)."),
|
|
1220
|
-
...scopeShape
|
|
1221
|
-
};
|
|
1222
|
-
var upsertUserProfileTool = {
|
|
1223
|
-
name: "upsert_user_profile",
|
|
1224
|
-
config: {
|
|
1225
|
-
title: "Upsert pyx-memory user profile",
|
|
1226
|
-
description: "Upsert the user-profile snapshot for a namespace. HTTP proxy to PUT /api/memory/profile/user \u2014 returns `{updatedAt, contentSize}`. Idempotent: re-upsert preserves `createdAt` and refreshes `updatedAt`.",
|
|
1227
|
-
inputSchema: upsertInputShape,
|
|
1228
|
-
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
1229
|
-
},
|
|
1230
|
-
handler: (deps) => async (raw) => {
|
|
1231
|
-
const args = raw;
|
|
1232
|
-
const creds = await deps.readCredentials();
|
|
1233
|
-
if (!creds.ok) return creds.result;
|
|
1234
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1235
|
-
const res = await http.requestJson({
|
|
1236
|
-
method: "PUT",
|
|
1237
|
-
path: "/api/memory/profile/user",
|
|
1238
|
-
body: { namespaceId: args.namespaceId, content: args.content },
|
|
1239
|
-
scope: args
|
|
1240
|
-
});
|
|
1241
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
|
|
1245
|
-
// src/mcp/tools/reinforce.ts
|
|
1246
|
-
import { z as z10 } from "zod";
|
|
1247
|
-
var inputShape6 = {
|
|
1248
|
-
entryIds: z10.array(z10.string().min(1)).min(1).describe("Memory entry ids that were actually used."),
|
|
1249
|
-
signal: z10.enum(["context_included", "cited", "explicit_positive"]).describe("Reinforcement signal: context_included < cited < explicit_positive."),
|
|
1250
|
-
at: z10.union([z10.string(), z10.number()]).optional().describe("Recall time as epoch-ms or full ISO-8601 timestamp with timezone."),
|
|
1251
|
-
...scopeShape
|
|
1252
|
-
};
|
|
1253
|
-
var reinforceTool = {
|
|
1254
|
-
name: "reinforce",
|
|
1255
|
-
config: {
|
|
1256
|
-
title: "Reinforce used pyx-memory entries",
|
|
1257
|
-
description: "Reinforce memories the agent actually USED (recall reinforces \u2014 raises strength so they surface in quick/medium effort tiers). signal: context_included < cited < explicit_positive.",
|
|
1258
|
-
inputSchema: inputShape6,
|
|
1259
|
-
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
1260
|
-
},
|
|
1261
|
-
handler: (deps) => async (raw) => {
|
|
1262
|
-
const args = raw;
|
|
1263
|
-
const creds = await deps.readCredentials();
|
|
1264
|
-
if (!creds.ok) return creds.result;
|
|
1265
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1266
|
-
const res = await http.requestJson({
|
|
1267
|
-
method: "POST",
|
|
1268
|
-
path: "/api/memory/reinforce",
|
|
1269
|
-
body: { entryIds: args.entryIds, signal: args.signal, at: args.at },
|
|
1270
|
-
scope: args
|
|
1271
|
-
});
|
|
1272
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
|
|
1276
|
-
// src/mcp/tools/search.ts
|
|
1277
|
-
import { z as z11 } from "zod";
|
|
1278
|
-
var inputShape7 = {
|
|
1279
|
-
query: z11.string().min(1).describe("Required natural-language search text."),
|
|
1280
|
-
limit: z11.number().int().min(1).max(100).optional().describe(SEARCH_LIMIT_DESC),
|
|
1281
|
-
strategy: z11.enum(["naive", "graph", "hybrid"]).optional().describe(
|
|
1282
|
-
"RAG strategy. Defaults to `hybrid` (cross-encoder reranking, multi-entity decomposition, confidence scoring) and is sent explicitly when omitted; pass `naive` for a lighter vector-only search or `graph` for graph-augmented retrieval."
|
|
1283
|
-
),
|
|
1284
|
-
effort: z11.enum(["quick", "medium", "deep"]).optional().describe(
|
|
1285
|
-
"Retrieval depth: quick=strongest, medium=default-depth, deep=everything including archived/superseded."
|
|
1286
|
-
),
|
|
1287
|
-
type: z11.enum(["short-term", "long-term", "working", "episodic", "summary"]).optional().describe("Filter by memory type."),
|
|
1288
|
-
agentId: z11.string().optional().describe("Filter to memories stored for this agentId."),
|
|
1289
|
-
abstentionThreshold: z11.number().min(0).max(1).optional().describe("Enable confidence scoring; abstain when confidence falls below this value (0\u20131)."),
|
|
1290
|
-
eventTimeStart: z11.string().optional().describe("Inclusive event-time start (ISO-8601); must be paired with eventTimeEnd."),
|
|
1291
|
-
eventTimeEnd: z11.string().optional().describe("Inclusive event-time end (ISO-8601); must be paired with eventTimeStart."),
|
|
1292
|
-
asOf: z11.string().optional().describe("Only include memories ingested before this ISO-8601 timestamp."),
|
|
1293
|
-
anchorTime: z11.string().optional().describe(
|
|
1294
|
-
'Soft recency ANCHOR (ISO-8601) \u2014 ranks results by proximity to this time instead of now; never excludes anything. When the question names a relative time ("two months ago", "last year", "3\uB144 \uC804"), resolve it against the current date yourself and pass the absolute timestamp here; prefer this over eventTimeStart/End unless a strict window is required, because hard filters drop last-known-before facts.'
|
|
1295
|
-
),
|
|
1296
|
-
enumerationConcept: z11.string().trim().min(1).optional().describe(SEARCH_ENUMERATION_CONCEPT_DESC),
|
|
1297
|
-
enableRerank: z11.boolean().optional().describe(
|
|
1298
|
-
"Opt into multilingual cross-encoder reranking (hybrid strategy only). Sharply improves Korean/cross-lingual ordering at higher latency; leave off for the fast default path."
|
|
1299
|
-
),
|
|
1300
|
-
...scopeShape
|
|
1301
|
-
};
|
|
1302
|
-
var searchMemoriesTool = {
|
|
1303
|
-
name: "search_memories",
|
|
1304
|
-
config: {
|
|
1305
|
-
title: "Search pyx-memory",
|
|
1306
|
-
description: "Search durable pyx-memory entries with hybrid/vector/keyword/graph strategy, tenant scope, sensitivity access, and optional confidence/abstention.",
|
|
1307
|
-
inputSchema: inputShape7,
|
|
1308
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1309
|
-
},
|
|
1310
|
-
handler: (deps) => async (raw) => {
|
|
1311
|
-
const args = raw;
|
|
1312
|
-
const creds = await deps.readCredentials();
|
|
1313
|
-
if (!creds.ok) return creds.result;
|
|
1314
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1315
|
-
const res = await http.requestJson({
|
|
1316
|
-
method: "GET",
|
|
1317
|
-
path: "/api/memory/search",
|
|
1318
|
-
query: {
|
|
1319
|
-
query: args.query,
|
|
1320
|
-
limit: args.limit,
|
|
1321
|
-
// Force `hybrid` unless the caller explicitly overrides. The server's
|
|
1322
|
-
// own default is hybrid too, but older tenant instances default to
|
|
1323
|
-
// `naive` (1–2 results vs hybrid's 15–20) — sending it explicitly makes
|
|
1324
|
-
// recall deterministic regardless of the instance version.
|
|
1325
|
-
strategy: args.strategy ?? "hybrid",
|
|
1326
|
-
effort: args.effort,
|
|
1327
|
-
type: args.type,
|
|
1328
|
-
agentId: args.agentId,
|
|
1329
|
-
abstentionThreshold: args.abstentionThreshold,
|
|
1330
|
-
eventTimeStart: args.eventTimeStart,
|
|
1331
|
-
eventTimeEnd: args.eventTimeEnd,
|
|
1332
|
-
asOf: args.asOf,
|
|
1333
|
-
anchorTime: args.anchorTime,
|
|
1334
|
-
enumerationConcept: args.enumerationConcept,
|
|
1335
|
-
enableRerank: args.enableRerank ? "true" : void 0
|
|
1336
|
-
},
|
|
1337
|
-
scope: args
|
|
1338
|
-
});
|
|
1339
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1340
|
-
}
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
// src/mcp/tools/status.ts
|
|
1344
|
-
var inputShape8 = {};
|
|
1345
|
-
var statusTool = {
|
|
1346
|
-
name: "status",
|
|
1347
|
-
config: {
|
|
1348
|
-
title: "Get pyx-memory topology",
|
|
1349
|
-
description: "Fetch the running pyx-memory server topology from GET /status when available. Hosted gateways that expose /health but not /status return an explicit topologyUnavailable result with the health payload.",
|
|
1350
|
-
inputSchema: inputShape8,
|
|
1351
|
-
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
1352
|
-
},
|
|
1353
|
-
handler: (deps) => async () => {
|
|
1354
|
-
const creds = await deps.readCredentials();
|
|
1355
|
-
if (!creds.ok) return creds.result;
|
|
1356
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1357
|
-
const res = await http.requestJson({ method: "GET", path: "/status" });
|
|
1358
|
-
if (res.ok) return mcpJson(res.data);
|
|
1359
|
-
if (res.status !== 404) return res.result;
|
|
1360
|
-
const health = await http.requestJson({ method: "GET", path: "/health" });
|
|
1361
|
-
if (!health.ok) return res.result;
|
|
1362
|
-
return mcpJson({
|
|
1363
|
-
topologyUnavailable: true,
|
|
1364
|
-
reason: "GET /status returned 404; this endpoint exposes /health but not topology status.",
|
|
1365
|
-
statusEndpoint: "/status",
|
|
1366
|
-
healthEndpoint: "/health",
|
|
1367
|
-
health: health.data
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
};
|
|
1371
|
-
|
|
1372
|
-
// src/mcp/tools/store.ts
|
|
1373
|
-
import { z as z12 } from "zod";
|
|
1374
|
-
var entityTypes = ["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"];
|
|
1375
|
-
var preferredRelationshipTypes = [
|
|
1376
|
-
"USES",
|
|
1377
|
-
"OWNS",
|
|
1378
|
-
"DEPENDS_ON",
|
|
1379
|
-
"RELATED_TO",
|
|
1380
|
-
"CREATED_BY",
|
|
1381
|
-
"PART_OF",
|
|
1382
|
-
"IS_A",
|
|
1383
|
-
"WORKS_AT",
|
|
1384
|
-
"LOCATED_IN"
|
|
1385
|
-
];
|
|
1386
|
-
var RELATIONSHIP_TYPE_DESC = `Relationship type \u2014 freeform label; preferred: ${preferredRelationshipTypes.join(", ")}.`;
|
|
1387
|
-
var storeTargets = ["sqlite", "vector", "graph"];
|
|
1388
|
-
var entityShape = z12.object({
|
|
1389
|
-
name: z12.string().min(1).describe("Entity name as referenced in content."),
|
|
1390
|
-
type: z12.enum(entityTypes).describe("Entity type.")
|
|
1391
|
-
});
|
|
1392
|
-
var inputShape9 = {
|
|
1393
|
-
content: z12.string().min(1).describe("Concise factual statement to persist; decision, not deliberation."),
|
|
1394
|
-
topic: z12.string().min(1).describe("Required metadata.topic for retrieval grouping."),
|
|
1395
|
-
project: z12.string().min(1).describe("Required metadata.project namespace."),
|
|
1396
|
-
type: z12.enum(["short-term", "long-term", "working", "episodic", "summary"]).optional().describe("Memory type. Default long-term."),
|
|
1397
|
-
targets: z12.array(z12.enum(storeTargets)).optional().describe(
|
|
1398
|
-
"Storage targets. Include 'graph' when you provide entities/relationships or want zero graph write counts reported."
|
|
1399
|
-
),
|
|
1400
|
-
importance: z12.number().int().min(1).max(10).optional().describe("Importance 1\u201310."),
|
|
1401
|
-
eventTime: z12.string().optional().describe(STORE_EVENT_TIME_DESC),
|
|
1402
|
-
source: z12.string().optional().describe("Free-form origin (filename, URL, conversation id)."),
|
|
1403
|
-
agentId: z12.string().optional().describe("Agent identifier stored alongside the entry."),
|
|
1404
|
-
sessionId: z12.string().optional().describe("Session identifier for grouping."),
|
|
1405
|
-
parentId: z12.string().optional().describe("Parent memory entry id (hierarchical)."),
|
|
1406
|
-
entities: z12.array(entityShape).optional().describe(STORE_ENTITIES_DESC),
|
|
1407
|
-
relationships: z12.array(
|
|
1408
|
-
z12.object({
|
|
1409
|
-
source: z12.string().min(1).describe("Source entity name (must appear in entities array)."),
|
|
1410
|
-
target: z12.string().min(1).describe("Target entity name (must appear in entities array)."),
|
|
1411
|
-
type: z12.string().min(1).describe(RELATIONSHIP_TYPE_DESC)
|
|
1412
|
-
})
|
|
1413
|
-
).optional().describe(STORE_RELATIONSHIPS_DESC),
|
|
1414
|
-
triples: z12.array(
|
|
1415
|
-
z12.object({
|
|
1416
|
-
subject: entityShape.describe("Subject entity (source node)."),
|
|
1417
|
-
relation: z12.string().min(1).describe(RELATIONSHIP_TYPE_DESC),
|
|
1418
|
-
object: entityShape.describe("Object entity (target node).")
|
|
1419
|
-
})
|
|
1420
|
-
).optional().describe(STORE_TRIPLES_DESC),
|
|
1421
|
-
extractEntities: z12.boolean().optional().describe(
|
|
1422
|
-
"Override extraction: false skips extraction; true asks the server-side extraction brain to run and errors loudly when none is configured."
|
|
1423
|
-
),
|
|
1424
|
-
entitiesOnly: z12.boolean().optional().describe(
|
|
1425
|
-
"Set true to deliberately store entities with no relationships (e.g. a single concept). Otherwise a graph store with 2+ entities and 0 relationships is refused so isolated nodes do not accumulate."
|
|
1426
|
-
),
|
|
1427
|
-
...scopeShape
|
|
1428
|
-
};
|
|
1429
|
-
function materializeGraphInput(args) {
|
|
1430
|
-
const entities = /* @__PURE__ */ new Map();
|
|
1431
|
-
const addEntity = (e) => {
|
|
1432
|
-
const key = `${normalizeNameKey(e.name)}|${e.type}`;
|
|
1433
|
-
if (!entities.has(key)) entities.set(key, e);
|
|
1434
|
-
};
|
|
1435
|
-
const relationships = /* @__PURE__ */ new Map();
|
|
1436
|
-
const addRel = (rel) => {
|
|
1437
|
-
const key = `${normalizeNameKey(rel.source)}|${normalizeNameKey(rel.target)}|${normalizeGraphLabel(rel.type, "RELATED_TO")}`;
|
|
1438
|
-
if (!relationships.has(key)) relationships.set(key, rel);
|
|
1439
|
-
};
|
|
1440
|
-
for (const e of args.entities ?? []) addEntity(e);
|
|
1441
|
-
for (const r of args.relationships ?? []) addRel(r);
|
|
1442
|
-
for (const t of args.triples ?? []) {
|
|
1443
|
-
addEntity(t.subject);
|
|
1444
|
-
addEntity(t.object);
|
|
1445
|
-
addRel({ source: t.subject.name, target: t.object.name, type: t.relation });
|
|
1446
|
-
}
|
|
1447
|
-
return { entities: [...entities.values()], relationships: [...relationships.values()] };
|
|
1448
|
-
}
|
|
1449
|
-
var storeMemoryTool = {
|
|
1450
|
-
name: "store_memory",
|
|
1451
|
-
config: {
|
|
1452
|
-
title: "Store pyx-memory entry",
|
|
1453
|
-
description: STORE_TOOL_DESC,
|
|
1454
|
-
inputSchema: inputShape9,
|
|
1455
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true }
|
|
1456
|
-
},
|
|
1457
|
-
handler: (deps) => async (raw) => {
|
|
1458
|
-
const args = raw;
|
|
1459
|
-
const { entities, relationships } = materializeGraphInput(args);
|
|
1460
|
-
const graphTargeted = !args.targets || args.targets.includes("graph");
|
|
1461
|
-
const entityNameKeys = new Set(entities.map((e) => normalizeNameKey(e.name)));
|
|
1462
|
-
const resolvableRelationships = relationships.filter((r) => {
|
|
1463
|
-
const source = normalizeNameKey(r.source);
|
|
1464
|
-
const target = normalizeNameKey(r.target);
|
|
1465
|
-
return source !== target && entityNameKeys.has(source) && entityNameKeys.has(target);
|
|
1466
|
-
}).length;
|
|
1467
|
-
if (graphTargeted && entities.length >= 2 && resolvableRelationships === 0 && !args.entitiesOnly && args.extractEntities !== true) {
|
|
1468
|
-
return mcpText(
|
|
1469
|
-
`GRAPH_RELATIONSHIPS_REQUIRED: this store_memory call declares ${entities.length} entities but no relationship that connects them. Isolated nodes will not connect into the knowledge graph. Re-call store_memory with a \`relationships\` array (or \`triples\`) linking the entities (each { source, target, type }; source/target must be entity names from this call), OR set \`entitiesOnly: true\` if these entities are intentionally unconnected. Do not retry unchanged. If your MCP host exposes prompts, the \`structure_graph\` prompt produces the relationship/triple fields to add.`,
|
|
1470
|
-
true
|
|
1471
|
-
);
|
|
1472
|
-
}
|
|
1473
|
-
const creds = await deps.readCredentials();
|
|
1474
|
-
if (!creds.ok) return creds.result;
|
|
1475
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1476
|
-
const body = {
|
|
1477
|
-
content: args.content,
|
|
1478
|
-
type: args.type ?? "long-term",
|
|
1479
|
-
targets: args.targets,
|
|
1480
|
-
metadata: {
|
|
1481
|
-
source: "agent",
|
|
1482
|
-
topic: args.topic,
|
|
1483
|
-
project: args.project
|
|
1484
|
-
},
|
|
1485
|
-
importance: args.importance,
|
|
1486
|
-
eventTime: args.eventTime,
|
|
1487
|
-
agentId: args.agentId,
|
|
1488
|
-
sessionId: args.sessionId,
|
|
1489
|
-
parentId: args.parentId,
|
|
1490
|
-
entities: entities.length > 0 ? entities : void 0,
|
|
1491
|
-
relationships: relationships.length > 0 ? relationships : void 0,
|
|
1492
|
-
...args.extractEntities !== void 0 ? { extractEntities: args.extractEntities } : {}
|
|
1493
|
-
};
|
|
1494
|
-
const res = await http.requestJson({
|
|
1495
|
-
method: "POST",
|
|
1496
|
-
path: "/api/memory/ingest",
|
|
1497
|
-
body,
|
|
1498
|
-
scope: args
|
|
1499
|
-
});
|
|
1500
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1501
|
-
}
|
|
1502
|
-
};
|
|
1503
|
-
|
|
1504
|
-
// src/mcp/tools/summarize.ts
|
|
1505
|
-
import { z as z13 } from "zod";
|
|
1506
|
-
var inputShape10 = {
|
|
1507
|
-
entityName: z13.string().min(1).describe(
|
|
1508
|
-
"Entity name to synthesize a profile for (PERSON, ORG, CONCEPT, TOOL, LOCATION, EVENT)."
|
|
1509
|
-
),
|
|
1510
|
-
entityType: z13.enum(["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"]).optional().describe("Optional explicit entity type when the same name spans multiple categories."),
|
|
1511
|
-
refresh: z13.boolean().optional().describe(
|
|
1512
|
-
"Default false: fetch the existing synthesis. true: rebuild the synthesis from current memory (POST)."
|
|
1513
|
-
),
|
|
1514
|
-
...scopeShape
|
|
1515
|
-
};
|
|
1516
|
-
var summarizeMemoryEntityTool = {
|
|
1517
|
-
name: "summarize_memory_entity",
|
|
1518
|
-
config: {
|
|
1519
|
-
title: "Synthesize pyx-memory entity",
|
|
1520
|
-
description: "Fetch or refresh a reusable synthesis/profile for an entity (person, customer, project, tool, organization). Default fetches existing; set refresh:true to rebuild.",
|
|
1521
|
-
inputSchema: inputShape10,
|
|
1522
|
-
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true }
|
|
1523
|
-
},
|
|
1524
|
-
handler: (deps) => async (raw) => {
|
|
1525
|
-
const args = raw;
|
|
1526
|
-
const creds = await deps.readCredentials();
|
|
1527
|
-
if (!creds.ok) return creds.result;
|
|
1528
|
-
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1529
|
-
if (args.refresh) {
|
|
1530
|
-
const res2 = await http.requestJson({
|
|
1531
|
-
method: "POST",
|
|
1532
|
-
path: "/api/memory/synthesis/entity",
|
|
1533
|
-
body: { name: args.entityName, entityType: args.entityType },
|
|
1534
|
-
scope: args
|
|
1535
|
-
});
|
|
1536
|
-
return res2.ok ? mcpJson(res2.data) : res2.result;
|
|
1537
|
-
}
|
|
1538
|
-
const res = await http.requestJson({
|
|
1539
|
-
method: "GET",
|
|
1540
|
-
path: "/api/memory/synthesis/entity",
|
|
1541
|
-
query: { name: args.entityName, entityType: args.entityType },
|
|
1542
|
-
scope: args
|
|
1543
|
-
});
|
|
1544
|
-
return res.ok ? mcpJson(res.data) : res.result;
|
|
1545
|
-
}
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
// src/mcp/tools/index.ts
|
|
1549
|
-
var ALL_TOOLS = [
|
|
1550
|
-
searchMemoriesTool,
|
|
1551
|
-
storeMemoryTool,
|
|
1552
|
-
getMemoryTool,
|
|
1553
|
-
lineageTool,
|
|
1554
|
-
reinforceTool,
|
|
1555
|
-
listMemoriesTool,
|
|
1556
|
-
deleteMemoryTool,
|
|
1557
|
-
ingestMemoryFileTool,
|
|
1558
|
-
summarizeMemoryEntityTool,
|
|
1559
|
-
statusTool,
|
|
1560
|
-
getUserProfileTool,
|
|
1561
|
-
upsertUserProfileTool,
|
|
1562
|
-
recordCorrectionTool,
|
|
1563
|
-
fetchApplicableCorrectionsTool
|
|
1564
|
-
];
|
|
1565
|
-
var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
|
|
1566
|
-
|
|
1567
|
-
// src/mcp/server.ts
|
|
1568
|
-
async function runMcpServer(opts) {
|
|
1569
|
-
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
1570
|
-
const version = opts.version ?? (true ? "1.2.0" : "0.0.0-dev");
|
|
1571
|
-
const server = new McpServer(
|
|
1572
|
-
{ name: "pyx-memory", version },
|
|
1573
|
-
{ instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {}, prompts: {} } }
|
|
1574
|
-
);
|
|
1575
|
-
for (const tool of ALL_TOOLS) {
|
|
1576
|
-
const handle = tool.handler({
|
|
1577
|
-
readCredentials: opts.readCredentials,
|
|
1578
|
-
fetchImpl
|
|
1579
|
-
});
|
|
1580
|
-
server.registerTool(
|
|
1581
|
-
tool.name,
|
|
1582
|
-
tool.config,
|
|
1583
|
-
async (args, extra) => handle(args, { signal: extra?.signal })
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
for (const prompt of ALL_PROMPTS) {
|
|
1587
|
-
prompt.register(server);
|
|
1588
|
-
}
|
|
1589
|
-
const transport = new StdioServerTransport2();
|
|
1590
|
-
const closed = new Promise((resolve4) => {
|
|
1591
|
-
transport.onclose = () => resolve4();
|
|
1592
|
-
});
|
|
1593
|
-
await server.connect(transport);
|
|
1594
|
-
await closed;
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
835
|
// src/cli/commands/mcp.ts
|
|
1598
|
-
async function mcpCommand(
|
|
836
|
+
async function mcpCommand() {
|
|
1599
837
|
const readCredentials = createReadCredentials(() => getDefaultKeychain());
|
|
1600
|
-
const onStdinEnd = new Promise((
|
|
1601
|
-
process.stdin.once("end",
|
|
1602
|
-
process.stdin.once("close",
|
|
838
|
+
const onStdinEnd = new Promise((resolve3) => {
|
|
839
|
+
process.stdin.once("end", resolve3);
|
|
840
|
+
process.stdin.once("close", resolve3);
|
|
1603
841
|
});
|
|
1604
842
|
try {
|
|
1605
|
-
|
|
1606
|
-
await Promise.race([run, onStdinEnd]);
|
|
843
|
+
await Promise.race([runMcpProxyServer({ readCredentials }), onStdinEnd]);
|
|
1607
844
|
return EXIT.OK;
|
|
1608
845
|
} catch (err) {
|
|
1609
846
|
process.stderr.write(`Internal error: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -1612,12 +849,13 @@ async function mcpCommand(opts = {}) {
|
|
|
1612
849
|
}
|
|
1613
850
|
}
|
|
1614
851
|
function resolveMcpMode(flags) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
852
|
+
if (flags.bundled === true) {
|
|
853
|
+
return {
|
|
854
|
+
ok: false,
|
|
855
|
+
error: "The bundled stdio MCP server was removed in v1.3.0. `pyx-mem mcp` now uses the hosted zero-touch path (server-owned tools + instructions; your key stays in the OS keychain). Drop `--bundled`. For a self-hosted server, point at it with `pyx-mem login --endpoint <url>` first."
|
|
856
|
+
};
|
|
1619
857
|
}
|
|
1620
|
-
return { ok: true
|
|
858
|
+
return { ok: true };
|
|
1621
859
|
}
|
|
1622
860
|
|
|
1623
861
|
// src/cli/commands/mcp-install.ts
|
|
@@ -1740,6 +978,149 @@ function indent(text) {
|
|
|
1740
978
|
return text.split("\n").map((line) => ` ${line}`).join("\n");
|
|
1741
979
|
}
|
|
1742
980
|
|
|
981
|
+
// src/cli/commands/memory-rules-merge.ts
|
|
982
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
983
|
+
import { dirname as dirname2 } from "path";
|
|
984
|
+
var RULES_BEGIN_MARKER = "<!-- pyx-memory:begin (managed by `pyx-mem mcp install`; edit outside these markers) -->";
|
|
985
|
+
var RULES_END_MARKER = "<!-- pyx-memory:end -->";
|
|
986
|
+
var BEGIN_MARKER_PREFIX = "<!-- pyx-memory:begin";
|
|
987
|
+
var PM_HEADING_RE = /^ {0,3}##[ \t]+Persistent Memory[ \t]*$/;
|
|
988
|
+
var H1_OR_H2_RE = /^ {0,3}#{1,2}[ \t]+\S/;
|
|
989
|
+
var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
|
|
990
|
+
function fenceMask(lines) {
|
|
991
|
+
const mask = new Array(lines.length).fill(false);
|
|
992
|
+
let openIdx = -1;
|
|
993
|
+
let delim = "";
|
|
994
|
+
let openLen = 0;
|
|
995
|
+
for (let i = 0; i < lines.length; i++) {
|
|
996
|
+
const fence = FENCE_RE.exec(lines[i] ?? "")?.[1];
|
|
997
|
+
if (!fence) continue;
|
|
998
|
+
if (openIdx === -1) {
|
|
999
|
+
openIdx = i;
|
|
1000
|
+
delim = fence.charAt(0);
|
|
1001
|
+
openLen = fence.length;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (fence.charAt(0) === delim && fence.length >= openLen) {
|
|
1005
|
+
for (let j = openIdx; j <= i; j++) mask[j] = true;
|
|
1006
|
+
openIdx = -1;
|
|
1007
|
+
delim = "";
|
|
1008
|
+
openLen = 0;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return mask;
|
|
1012
|
+
}
|
|
1013
|
+
function nextH1OrH2(lines, mask, from) {
|
|
1014
|
+
for (let i = from; i < lines.length; i++) {
|
|
1015
|
+
const line = lines[i];
|
|
1016
|
+
if (line !== void 0 && !mask[i] && H1_OR_H2_RE.test(line)) return i;
|
|
1017
|
+
}
|
|
1018
|
+
return lines.length;
|
|
1019
|
+
}
|
|
1020
|
+
function findCompleteManagedRange(lines, mask) {
|
|
1021
|
+
let begin = -1;
|
|
1022
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1023
|
+
const line = lines[i];
|
|
1024
|
+
if (line === void 0 || mask[i]) continue;
|
|
1025
|
+
const t = line.trim();
|
|
1026
|
+
if (begin === -1) {
|
|
1027
|
+
if (t.startsWith(BEGIN_MARKER_PREFIX)) begin = i;
|
|
1028
|
+
} else if (t === RULES_END_MARKER) {
|
|
1029
|
+
return [begin, i + 1];
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
function findOrphanBeginLine(lines, mask) {
|
|
1035
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1036
|
+
const line = lines[i];
|
|
1037
|
+
if (line !== void 0 && !mask[i] && line.trim().startsWith(BEGIN_MARKER_PREFIX)) return i;
|
|
1038
|
+
}
|
|
1039
|
+
return -1;
|
|
1040
|
+
}
|
|
1041
|
+
function findLegacyPyxRange(lines, mask) {
|
|
1042
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1043
|
+
const line = lines[i];
|
|
1044
|
+
if (line === void 0 || mask[i] || !PM_HEADING_RE.test(line)) continue;
|
|
1045
|
+
const end = nextH1OrH2(lines, mask, i + 1);
|
|
1046
|
+
if (lines.slice(i, end).join("\n").toLowerCase().includes("pyx-memory")) return [i, end];
|
|
1047
|
+
}
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
function spliceBlock(lines, from, toExclusive, blockLines) {
|
|
1051
|
+
const before = lines.slice(0, from);
|
|
1052
|
+
const after = lines.slice(toExclusive);
|
|
1053
|
+
while (before.length && (before[before.length - 1] ?? "").trim() === "") before.pop();
|
|
1054
|
+
while (after.length && (after[0] ?? "").trim() === "") after.shift();
|
|
1055
|
+
const out = [...before];
|
|
1056
|
+
if (out.length) out.push("");
|
|
1057
|
+
out.push(...blockLines);
|
|
1058
|
+
if (after.length) out.push("");
|
|
1059
|
+
out.push(...after);
|
|
1060
|
+
return out;
|
|
1061
|
+
}
|
|
1062
|
+
function joinWithTrailingNewline(lines, eol) {
|
|
1063
|
+
const body = lines.join(eol);
|
|
1064
|
+
return body.endsWith(eol) ? body : `${body}${eol}`;
|
|
1065
|
+
}
|
|
1066
|
+
function mergeMemoryRules(existing, section) {
|
|
1067
|
+
if (section.includes(BEGIN_MARKER_PREFIX) || section.includes(RULES_END_MARKER)) {
|
|
1068
|
+
throw new Error("pyx-memory rules section must not contain the managed markers.");
|
|
1069
|
+
}
|
|
1070
|
+
const eol = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
1071
|
+
const blockLines = [RULES_BEGIN_MARKER, ...section.split(/\r?\n/), RULES_END_MARKER];
|
|
1072
|
+
const normalized = existing.replace(/\r\n/g, "\n");
|
|
1073
|
+
if (normalized.trim().length === 0) {
|
|
1074
|
+
return { content: joinWithTrailingNewline(blockLines, eol), action: "created" };
|
|
1075
|
+
}
|
|
1076
|
+
let lines = normalized.split("\n");
|
|
1077
|
+
let mask = fenceMask(lines);
|
|
1078
|
+
const complete = findCompleteManagedRange(lines, mask);
|
|
1079
|
+
if (complete) {
|
|
1080
|
+
const next2 = spliceBlock(lines, complete[0], complete[1], blockLines);
|
|
1081
|
+
return { content: joinWithTrailingNewline(next2, eol), action: "updated" };
|
|
1082
|
+
}
|
|
1083
|
+
const orphan = findOrphanBeginLine(lines, mask);
|
|
1084
|
+
if (orphan !== -1) {
|
|
1085
|
+
lines = [...lines.slice(0, orphan), ...lines.slice(orphan + 1)];
|
|
1086
|
+
mask = fenceMask(lines);
|
|
1087
|
+
}
|
|
1088
|
+
const legacy = findLegacyPyxRange(lines, mask);
|
|
1089
|
+
if (legacy) {
|
|
1090
|
+
const next2 = spliceBlock(lines, legacy[0], legacy[1], blockLines);
|
|
1091
|
+
return { content: joinWithTrailingNewline(next2, eol), action: "updated" };
|
|
1092
|
+
}
|
|
1093
|
+
const next = spliceBlock(lines, lines.length, lines.length, blockLines);
|
|
1094
|
+
return { content: joinWithTrailingNewline(next, eol), action: "appended" };
|
|
1095
|
+
}
|
|
1096
|
+
function writeMemoryRulesFile(opts) {
|
|
1097
|
+
const { filePath, section } = opts;
|
|
1098
|
+
const fileExists = existsSync2(filePath);
|
|
1099
|
+
const existing = fileExists ? readFileSync2(filePath, "utf8") : "";
|
|
1100
|
+
const { content, action } = mergeMemoryRules(existing, section);
|
|
1101
|
+
if (content === existing) {
|
|
1102
|
+
return {
|
|
1103
|
+
action: "unchanged",
|
|
1104
|
+
filePath,
|
|
1105
|
+
message: `pyx-memory rules already current in ${filePath} (no changes written).
|
|
1106
|
+
`
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
const parent = dirname2(filePath);
|
|
1110
|
+
if (!existsSync2(parent)) mkdirSync2(parent, { recursive: true });
|
|
1111
|
+
const mode = fileExists ? statSync(filePath).mode & 511 : 420;
|
|
1112
|
+
const tmpPath = `${filePath}.tmp`;
|
|
1113
|
+
writeFileSync2(tmpPath, content, { mode });
|
|
1114
|
+
renameSync2(tmpPath, filePath);
|
|
1115
|
+
const verb = action === "created" ? "Wrote" : action === "appended" ? "Added" : "Updated";
|
|
1116
|
+
return {
|
|
1117
|
+
action,
|
|
1118
|
+
filePath,
|
|
1119
|
+
message: `${verb} the pyx-memory rules in ${filePath}.
|
|
1120
|
+
`
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1743
1124
|
// src/cli/commands/mcp-install.ts
|
|
1744
1125
|
var VALID_SCOPES = /* @__PURE__ */ new Set(["local", "user", "project"]);
|
|
1745
1126
|
var SERVER_NAME = "pyx-memory";
|
|
@@ -2046,6 +1427,28 @@ function printHermesManualInstructions(remote) {
|
|
|
2046
1427
|
].join("\n")
|
|
2047
1428
|
);
|
|
2048
1429
|
}
|
|
1430
|
+
var RULES_FILE_TARGETS = {
|
|
1431
|
+
"claude-code": [".claude", "CLAUDE.md"],
|
|
1432
|
+
codex: [".codex", "AGENTS.md"],
|
|
1433
|
+
"gemini-cli": [".gemini", "GEMINI.md"],
|
|
1434
|
+
"oh-my-pi": [".omp", "agent", "RULES.md"]
|
|
1435
|
+
};
|
|
1436
|
+
function installMemoryRules(opts) {
|
|
1437
|
+
const segs = RULES_FILE_TARGETS[opts.target];
|
|
1438
|
+
if (!segs) return;
|
|
1439
|
+
if (opts.noRules) {
|
|
1440
|
+
process.stdout.write(
|
|
1441
|
+
'Skipped writing the memory rules (--no-rules). The tools are installed, but the agent will reach for memory only when you ask; add the "## Persistent Memory" rules yourself to change that.\n'
|
|
1442
|
+
);
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const home = opts._homeDir ?? homedir();
|
|
1446
|
+
const result = writeMemoryRulesFile({
|
|
1447
|
+
filePath: join(home, ...segs),
|
|
1448
|
+
section: PERSISTENT_MEMORY_SECTION
|
|
1449
|
+
});
|
|
1450
|
+
process.stdout.write(result.message);
|
|
1451
|
+
}
|
|
2049
1452
|
function validateScope(scope) {
|
|
2050
1453
|
if (VALID_SCOPES.has(scope)) return true;
|
|
2051
1454
|
process.stderr.write(`Error: invalid --scope \`${scope}\`. Expected: local | user | project.
|
|
@@ -2074,8 +1477,8 @@ function writeJsonAndReport(filePath, agentLabel, entry, opts = {}) {
|
|
|
2074
1477
|
}
|
|
2075
1478
|
|
|
2076
1479
|
// src/cli/commands/scaffold.ts
|
|
2077
|
-
import { existsSync as
|
|
2078
|
-
import { basename as
|
|
1480
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1481
|
+
import { basename as basename2, join as join2, resolve as resolve2 } from "path";
|
|
2079
1482
|
var SERVER_IMAGE = "ghcr.io/pyx-corp/pyx-memory-v1:latest";
|
|
2080
1483
|
var DOCKER_COMPOSE = `services:
|
|
2081
1484
|
pyx-memory:
|
|
@@ -2198,8 +1601,8 @@ function resolveTarget(args) {
|
|
|
2198
1601
|
process.stderr.write("Error: --name requires a non-empty directory name.\n");
|
|
2199
1602
|
return null;
|
|
2200
1603
|
}
|
|
2201
|
-
const targetDir = trimmedName ?
|
|
2202
|
-
const appName = trimmedName ||
|
|
1604
|
+
const targetDir = trimmedName ? resolve2(cwd, trimmedName) : cwd;
|
|
1605
|
+
const appName = trimmedName || basename2(resolve2(cwd));
|
|
2203
1606
|
return { targetDir, appName };
|
|
2204
1607
|
}
|
|
2205
1608
|
function buildFiles(targetDir, appName) {
|
|
@@ -2216,21 +1619,21 @@ function buildFiles(targetDir, appName) {
|
|
|
2216
1619
|
function scaffoldCommand(args = {}) {
|
|
2217
1620
|
const target = resolveTarget(args);
|
|
2218
1621
|
if (target === null) return EXIT.USAGE;
|
|
2219
|
-
if (
|
|
1622
|
+
if (existsSync3(target.targetDir) && !statSync2(target.targetDir).isDirectory()) {
|
|
2220
1623
|
process.stderr.write(`Error: target exists and is not a directory: ${target.targetDir}
|
|
2221
1624
|
`);
|
|
2222
1625
|
return EXIT.USAGE;
|
|
2223
1626
|
}
|
|
2224
|
-
|
|
1627
|
+
mkdirSync3(target.targetDir, { recursive: true });
|
|
2225
1628
|
const created = [];
|
|
2226
1629
|
const skipped = [];
|
|
2227
1630
|
for (const file of buildFiles(target.targetDir, target.appName)) {
|
|
2228
|
-
const relativePath =
|
|
2229
|
-
if (
|
|
1631
|
+
const relativePath = basename2(file.path);
|
|
1632
|
+
if (existsSync3(file.path)) {
|
|
2230
1633
|
skipped.push(relativePath);
|
|
2231
1634
|
continue;
|
|
2232
1635
|
}
|
|
2233
|
-
|
|
1636
|
+
writeFileSync3(file.path, file.contents, "utf8");
|
|
2234
1637
|
created.push(relativePath);
|
|
2235
1638
|
}
|
|
2236
1639
|
process.stdout.write(
|
|
@@ -2305,21 +1708,27 @@ Commands:
|
|
|
2305
1708
|
logout Delete stored pyx-memory credentials.
|
|
2306
1709
|
doctor [--json] Diagnose keychain, credentials, backend, MCP startup.
|
|
2307
1710
|
scaffold [--name <dir>] Generate Docker, env, SDK, and memory design-guide starter files.
|
|
2308
|
-
mcp
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
mcp install <target> [--scope user|local|project] [--remote]
|
|
2314
|
-
Install pyx-memory MCP config for your AI agent.
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
1711
|
+
mcp Start the stdio MCP proxy to the hosted server, so
|
|
1712
|
+
tools + instructions stay server-owned (zero-touch
|
|
1713
|
+
updates; key stays in the OS keychain). Self-host:
|
|
1714
|
+
run 'login --endpoint <url>' first to point it at
|
|
1715
|
+
your own server.
|
|
1716
|
+
mcp install <target> [--scope user|local|project] [--remote] [--no-rules]
|
|
1717
|
+
Install pyx-memory MCP config for your AI agent. The
|
|
1718
|
+
config launches the hosted zero-touch thin proxy;
|
|
1719
|
+
--remote is an accepted no-op alias (the bundled
|
|
1720
|
+
stdio server was removed in v1.3.0).
|
|
1721
|
+
For claude-code, codex, gemini-cli, and oh-my-pi it
|
|
1722
|
+
also writes the always-on "## Persistent Memory" rules
|
|
1723
|
+
into that tool's global instructions file (idempotent;
|
|
1724
|
+
your other content is preserved) so memory fires by
|
|
1725
|
+
default. Pass --no-rules to skip that write.
|
|
2318
1726
|
Targets: claude-code, codex, cursor, cline, continue, windsurf, gemini-cli, pi, oh-my-pi, openclaw, hermes.
|
|
2319
1727
|
|
|
2320
1728
|
Notes:
|
|
2321
1729
|
- Credentials are stored only in the OS credential store (Keychain / libsecret / Credential Manager).
|
|
2322
1730
|
- No plaintext token file is ever written.
|
|
1731
|
+
- \`mcp --bundled\` was removed in v1.3.0 and now errors \u2014 drop the flag (the hosted path is the only transport).
|
|
2323
1732
|
- Run \`pyx-mem doctor\` if something looks wrong.
|
|
2324
1733
|
`;
|
|
2325
1734
|
|
|
@@ -2382,7 +1791,7 @@ var INSTALL_TARGETS = {
|
|
|
2382
1791
|
hermes: mcpInstallHermesCommand
|
|
2383
1792
|
};
|
|
2384
1793
|
var VALID_TARGETS = Object.keys(INSTALL_TARGETS);
|
|
2385
|
-
function runMcpInstall(target, scope, remote) {
|
|
1794
|
+
function runMcpInstall(target, scope, remote, noRules) {
|
|
2386
1795
|
if (target === void 0 || !Object.hasOwn(INSTALL_TARGETS, target)) {
|
|
2387
1796
|
process.stderr.write(
|
|
2388
1797
|
`Error: unknown install target \`${target ?? ""}\`. Expected: ${VALID_TARGETS.join(", ")}.
|
|
@@ -2391,7 +1800,9 @@ function runMcpInstall(target, scope, remote) {
|
|
|
2391
1800
|
return EXIT.USAGE;
|
|
2392
1801
|
}
|
|
2393
1802
|
const handler = INSTALL_TARGETS[target];
|
|
2394
|
-
|
|
1803
|
+
const code = handler({ scope, remote });
|
|
1804
|
+
if (code === EXIT.OK) installMemoryRules({ target, noRules });
|
|
1805
|
+
return code;
|
|
2395
1806
|
}
|
|
2396
1807
|
function runMcpCommand(parsed) {
|
|
2397
1808
|
if (parsed.subcommand === void 0) {
|
|
@@ -2404,17 +1815,17 @@ function runMcpCommand(parsed) {
|
|
|
2404
1815
|
`);
|
|
2405
1816
|
return EXIT.USAGE;
|
|
2406
1817
|
}
|
|
2407
|
-
|
|
2408
|
-
process.stderr.write(
|
|
2409
|
-
"pyx-memory: `pyx-mem mcp` now connects to the hosted server (zero-touch \u2014 tools and instructions stay server-owned). Pass `--bundled` for the legacy in-package server.\n"
|
|
2410
|
-
);
|
|
2411
|
-
}
|
|
2412
|
-
return mcpCommand({ bundled: resolved.bundled });
|
|
1818
|
+
return mcpCommand();
|
|
2413
1819
|
}
|
|
2414
1820
|
if (parsed.subcommand === "install") {
|
|
2415
1821
|
const target = parsed.positional[0];
|
|
2416
1822
|
const scope = typeof parsed.flags.scope === "string" ? parsed.flags.scope : void 0;
|
|
2417
|
-
return runMcpInstall(
|
|
1823
|
+
return runMcpInstall(
|
|
1824
|
+
target,
|
|
1825
|
+
scope,
|
|
1826
|
+
parsed.flags.remote === true,
|
|
1827
|
+
parsed.flags["no-rules"] === true
|
|
1828
|
+
);
|
|
2418
1829
|
}
|
|
2419
1830
|
process.stderr.write(
|
|
2420
1831
|
`Error: unknown subcommand \`mcp ${parsed.subcommand}\`. Run \`pyx-mem --help\`.
|