@pyxmate/memory 0.45.0 → 1.0.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/README.md +9 -10
- package/dist/agent-contract.d.ts +40 -0
- package/dist/agent-contract.mjs +32 -0
- package/dist/{chunk-UV2DFSKR.mjs → chunk-PXQLVQAA.mjs} +1 -7
- package/dist/{chunk-KSTI4M52.mjs → chunk-X6AYWXW7.mjs} +20 -1
- package/dist/chunk-XHEVB23R.mjs +169 -0
- package/dist/{chunk-DKNGLNN4.mjs → chunk-ZCGJGI2O.mjs} +1 -1
- package/dist/cli/pyx-mem.mjs +358 -228
- package/dist/dashboard.mjs +3 -3
- package/dist/index.d.ts +87 -17
- package/dist/index.mjs +21 -4
- package/dist/react.mjs +3 -3
- package/package.json +6 -1
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
PYX_MEMORY_INSTRUCTIONS,
|
|
4
|
+
SEARCH_ENUMERATION_CONCEPT_DESC,
|
|
5
|
+
SEARCH_LIMIT_DESC,
|
|
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";
|
|
6
19
|
|
|
7
20
|
// src/cli/exit-codes.ts
|
|
8
21
|
var EXIT = {
|
|
@@ -339,7 +352,7 @@ async function promptMasked(label) {
|
|
|
339
352
|
`);
|
|
340
353
|
return value;
|
|
341
354
|
}
|
|
342
|
-
return new Promise((
|
|
355
|
+
return new Promise((resolve3, reject) => {
|
|
343
356
|
const previousEncoding = stdin.readableEncoding;
|
|
344
357
|
stdin.setEncoding("utf8");
|
|
345
358
|
stdin.setRawMode(true);
|
|
@@ -357,7 +370,7 @@ async function promptMasked(label) {
|
|
|
357
370
|
if (ch === "\n" || ch === "\r") {
|
|
358
371
|
cleanup();
|
|
359
372
|
stdout.write("\n");
|
|
360
|
-
|
|
373
|
+
resolve3(buf.trim());
|
|
361
374
|
return;
|
|
362
375
|
}
|
|
363
376
|
if (ch === "") {
|
|
@@ -554,49 +567,33 @@ function createReadCredentials(providerFactory) {
|
|
|
554
567
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
555
568
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
556
569
|
|
|
557
|
-
// src/mcp/
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
throw new Error(
|
|
571
|
-
"MCP sampling unavailable: the connected client did not advertise the `sampling` capability. Either supply entities/relationships explicitly or set extractEntities:false on the request."
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
const result = await server.server.createMessage(
|
|
575
|
-
{
|
|
576
|
-
messages: [{ role: "user", content: { type: "text", text: prompt } }],
|
|
577
|
-
maxTokens: opts?.maxTokens ?? 2048
|
|
578
|
-
},
|
|
579
|
-
opts?.signal ? { signal: opts.signal } : void 0
|
|
580
|
-
);
|
|
581
|
-
const content = result.content;
|
|
582
|
-
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
583
|
-
const block = content;
|
|
584
|
-
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
585
|
-
}
|
|
586
|
-
if (Array.isArray(content)) {
|
|
587
|
-
for (const block of content) {
|
|
588
|
-
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
589
|
-
return block.text;
|
|
590
|
-
}
|
|
570
|
+
// src/mcp/prompts/index.ts
|
|
571
|
+
import { z } from "zod";
|
|
572
|
+
var structureGraphPrompt = {
|
|
573
|
+
name: "structure_graph",
|
|
574
|
+
description: STRUCTURE_GRAPH_PROMPT_DESC,
|
|
575
|
+
register(server) {
|
|
576
|
+
server.registerPrompt(
|
|
577
|
+
"structure_graph",
|
|
578
|
+
{
|
|
579
|
+
title: "Structure content into a knowledge graph",
|
|
580
|
+
description: STRUCTURE_GRAPH_PROMPT_DESC,
|
|
581
|
+
argsSchema: {
|
|
582
|
+
content: z.string().min(1).describe("The memory content to extract graph fields from.")
|
|
591
583
|
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
584
|
+
},
|
|
585
|
+
({ content }) => ({
|
|
586
|
+
messages: [
|
|
587
|
+
{ role: "user", content: { type: "text", text: buildGraphStructuringPrompt(content) } }
|
|
588
|
+
]
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
var ALL_PROMPTS = [structureGraphPrompt];
|
|
597
594
|
|
|
598
595
|
// src/mcp/tools/corrections.ts
|
|
599
|
-
import { z as
|
|
596
|
+
import { z as z3 } from "zod";
|
|
600
597
|
|
|
601
598
|
// src/mcp/http-client.ts
|
|
602
599
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -703,22 +700,22 @@ function extractEnvelopeError(parsed) {
|
|
|
703
700
|
}
|
|
704
701
|
|
|
705
702
|
// src/mcp/tools/scopes.ts
|
|
706
|
-
import { z } from "zod";
|
|
703
|
+
import { z as z2 } from "zod";
|
|
707
704
|
var scopeShape = {
|
|
708
|
-
tenantId:
|
|
709
|
-
userId:
|
|
710
|
-
teamId:
|
|
711
|
-
callerAccessLevel:
|
|
705
|
+
tenantId: z2.string().optional().describe("Sent as X-Tenant-Id for multi-tenant isolation."),
|
|
706
|
+
userId: z2.string().optional().describe("Sent as X-User-Id."),
|
|
707
|
+
teamId: z2.string().optional().describe("Sent as X-Team-Id."),
|
|
708
|
+
callerAccessLevel: z2.enum(["public", "internal", "secret"]).optional().describe("Sent as X-Caller-Access-Level for sensitivity filtering.")
|
|
712
709
|
};
|
|
713
710
|
|
|
714
711
|
// src/mcp/tools/corrections.ts
|
|
715
712
|
var recordInputShape = {
|
|
716
|
-
namespaceId:
|
|
717
|
-
whatWasWrong:
|
|
718
|
-
whatToDoInstead:
|
|
719
|
-
appliesWhen:
|
|
720
|
-
project:
|
|
721
|
-
taskShape:
|
|
713
|
+
namespaceId: z3.string().min(1).optional().describe("Namespace id to record into. Omit in single-tenant deployments (namespace-free)."),
|
|
714
|
+
whatWasWrong: z3.string().min(1).describe("What the agent did wrong."),
|
|
715
|
+
whatToDoInstead: z3.string().min(1).describe("The corrective instruction to follow instead."),
|
|
716
|
+
appliesWhen: z3.string().min(1).describe("When this correction applies \u2014 the task context future tasks are matched against."),
|
|
717
|
+
project: z3.string().optional().describe("Optional project scope; omit to apply to all projects."),
|
|
718
|
+
taskShape: z3.string().optional().describe("Optional task-shape hint stored for provenance."),
|
|
722
719
|
...scopeShape
|
|
723
720
|
};
|
|
724
721
|
var recordCorrectionTool = {
|
|
@@ -751,10 +748,10 @@ var recordCorrectionTool = {
|
|
|
751
748
|
}
|
|
752
749
|
};
|
|
753
750
|
var fetchInputShape = {
|
|
754
|
-
namespaceId:
|
|
755
|
-
taskShape:
|
|
756
|
-
project:
|
|
757
|
-
limit:
|
|
751
|
+
namespaceId: z3.string().min(1).optional().describe("Namespace id whose corrections to fetch. Omit in single-tenant deployments."),
|
|
752
|
+
taskShape: z3.string().min(1).describe("Shape of the task about to run \u2014 scored against each correction."),
|
|
753
|
+
project: z3.string().optional().describe("Optional project filter."),
|
|
754
|
+
limit: z3.number().int().positive().optional().describe("Max corrections to return (cap 5)."),
|
|
758
755
|
...scopeShape
|
|
759
756
|
};
|
|
760
757
|
var fetchApplicableCorrectionsTool = {
|
|
@@ -786,10 +783,10 @@ var fetchApplicableCorrectionsTool = {
|
|
|
786
783
|
};
|
|
787
784
|
|
|
788
785
|
// src/mcp/tools/delete.ts
|
|
789
|
-
import { z as
|
|
786
|
+
import { z as z4 } from "zod";
|
|
790
787
|
var inputShape = {
|
|
791
|
-
id:
|
|
792
|
-
reason:
|
|
788
|
+
id: z4.string().min(1).describe("Required memory entry id to delete."),
|
|
789
|
+
reason: z4.string().min(1).describe("Required reason for deletion (audit)."),
|
|
793
790
|
...scopeShape
|
|
794
791
|
};
|
|
795
792
|
var deleteMemoryTool = {
|
|
@@ -816,9 +813,9 @@ var deleteMemoryTool = {
|
|
|
816
813
|
};
|
|
817
814
|
|
|
818
815
|
// src/mcp/tools/get.ts
|
|
819
|
-
import { z as
|
|
816
|
+
import { z as z5 } from "zod";
|
|
820
817
|
var inputShape2 = {
|
|
821
|
-
id:
|
|
818
|
+
id: z5.string().min(1).describe("Required memory entry id."),
|
|
822
819
|
...scopeShape
|
|
823
820
|
};
|
|
824
821
|
var getMemoryTool = {
|
|
@@ -846,17 +843,17 @@ var getMemoryTool = {
|
|
|
846
843
|
// src/mcp/tools/ingest.ts
|
|
847
844
|
import { readFile, stat } from "fs/promises";
|
|
848
845
|
import { basename, extname, isAbsolute, resolve } from "path";
|
|
849
|
-
import { z as
|
|
846
|
+
import { z as z6 } from "zod";
|
|
850
847
|
var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff", ".svg"]);
|
|
851
848
|
var MAX_BYTES = 50 * 1024 * 1024;
|
|
852
849
|
var inputShape3 = {
|
|
853
|
-
path:
|
|
850
|
+
path: z6.string().min(1).describe(
|
|
854
851
|
"Local file path readable by the pyx-mem process. Uploaded as multipart `file`. Images require `description`; documents auto-extract text."
|
|
855
852
|
),
|
|
856
|
-
description:
|
|
853
|
+
description: z6.string().optional().describe(
|
|
857
854
|
"REQUIRED for images so the entry is semantically searchable. Optional for documents with extractable text."
|
|
858
855
|
),
|
|
859
|
-
namespaceId:
|
|
856
|
+
namespaceId: z6.string().optional().describe("Optional ReBAC namespace for entries created from this file."),
|
|
860
857
|
...scopeShape
|
|
861
858
|
};
|
|
862
859
|
var ingestMemoryFileTool = {
|
|
@@ -911,16 +908,16 @@ var ingestMemoryFileTool = {
|
|
|
911
908
|
};
|
|
912
909
|
|
|
913
910
|
// src/mcp/tools/lineage.ts
|
|
914
|
-
import { z as
|
|
911
|
+
import { z as z7 } from "zod";
|
|
915
912
|
var inputShape4 = {
|
|
916
|
-
subject:
|
|
917
|
-
relation:
|
|
918
|
-
entryId:
|
|
919
|
-
asOf:
|
|
920
|
-
eventTimeStart:
|
|
921
|
-
eventTimeEnd:
|
|
922
|
-
beforeValue:
|
|
923
|
-
limit:
|
|
913
|
+
subject: z7.string().optional().describe("Graph subject to trace, used with relation for graph lineage."),
|
|
914
|
+
relation: z7.string().optional().describe("Graph relation to trace for the subject."),
|
|
915
|
+
entryId: z7.string().optional().describe("Memory entry id to trace through its supersededBy chain."),
|
|
916
|
+
asOf: z7.string().optional().describe("Only include lineage versions ingested by this ISO-8601 time."),
|
|
917
|
+
eventTimeStart: z7.string().optional().describe("Inclusive event-time start (ISO-8601)."),
|
|
918
|
+
eventTimeEnd: z7.string().optional().describe("Inclusive event-time end (ISO-8601)."),
|
|
919
|
+
beforeValue: z7.string().optional().describe("Stop before the lineage version with this value."),
|
|
920
|
+
limit: z7.number().int().positive().optional().describe("Maximum lineage versions to return."),
|
|
924
921
|
...scopeShape
|
|
925
922
|
};
|
|
926
923
|
var lineageTool = {
|
|
@@ -956,16 +953,16 @@ var lineageTool = {
|
|
|
956
953
|
};
|
|
957
954
|
|
|
958
955
|
// src/mcp/tools/list.ts
|
|
959
|
-
import { z as
|
|
956
|
+
import { z as z8 } from "zod";
|
|
960
957
|
var inputShape5 = {
|
|
961
|
-
mode:
|
|
958
|
+
mode: z8.enum(["entries", "log"]).optional().describe(
|
|
962
959
|
"Listing mode. `entries` (default) returns paginated entries by filter; `log` returns the chronological memory log."
|
|
963
960
|
),
|
|
964
|
-
page:
|
|
965
|
-
limit:
|
|
966
|
-
type:
|
|
967
|
-
agentId:
|
|
968
|
-
since:
|
|
961
|
+
page: z8.number().int().min(1).optional().describe("1-based page index for entries mode."),
|
|
962
|
+
limit: z8.number().int().min(1).max(200).optional().describe("Page size; server clamps."),
|
|
963
|
+
type: z8.enum(["short-term", "long-term", "working", "episodic", "summary"]).optional().describe("Filter by memory type."),
|
|
964
|
+
agentId: z8.string().optional().describe("Filter to memories from this agentId."),
|
|
965
|
+
since: z8.string().optional().describe("ISO-8601 lower bound for log mode."),
|
|
969
966
|
...scopeShape
|
|
970
967
|
};
|
|
971
968
|
var listMemoriesTool = {
|
|
@@ -1000,9 +997,9 @@ var listMemoriesTool = {
|
|
|
1000
997
|
};
|
|
1001
998
|
|
|
1002
999
|
// src/mcp/tools/profile.ts
|
|
1003
|
-
import { z as
|
|
1000
|
+
import { z as z9 } from "zod";
|
|
1004
1001
|
var getInputShape = {
|
|
1005
|
-
namespaceId:
|
|
1002
|
+
namespaceId: z9.string().min(1).describe("Namespace id whose user-profile to fetch."),
|
|
1006
1003
|
...scopeShape
|
|
1007
1004
|
};
|
|
1008
1005
|
var getUserProfileTool = {
|
|
@@ -1028,8 +1025,8 @@ var getUserProfileTool = {
|
|
|
1028
1025
|
}
|
|
1029
1026
|
};
|
|
1030
1027
|
var upsertInputShape = {
|
|
1031
|
-
namespaceId:
|
|
1032
|
-
content:
|
|
1028
|
+
namespaceId: z9.string().min(1).describe("Namespace id to upsert the user-profile into."),
|
|
1029
|
+
content: z9.string().min(1).describe("Full freeform profile content (UTF-8, \u22648192 bytes; server enforces the cap)."),
|
|
1033
1030
|
...scopeShape
|
|
1034
1031
|
};
|
|
1035
1032
|
var upsertUserProfileTool = {
|
|
@@ -1056,11 +1053,11 @@ var upsertUserProfileTool = {
|
|
|
1056
1053
|
};
|
|
1057
1054
|
|
|
1058
1055
|
// src/mcp/tools/reinforce.ts
|
|
1059
|
-
import { z as
|
|
1056
|
+
import { z as z10 } from "zod";
|
|
1060
1057
|
var inputShape6 = {
|
|
1061
|
-
entryIds:
|
|
1062
|
-
signal:
|
|
1063
|
-
at:
|
|
1058
|
+
entryIds: z10.array(z10.string().min(1)).min(1).describe("Memory entry ids that were actually used."),
|
|
1059
|
+
signal: z10.enum(["context_included", "cited", "explicit_positive"]).describe("Reinforcement signal: context_included < cited < explicit_positive."),
|
|
1060
|
+
at: z10.union([z10.string(), z10.number()]).optional().describe("Recall time as epoch-ms or full ISO-8601 timestamp with timezone."),
|
|
1064
1061
|
...scopeShape
|
|
1065
1062
|
};
|
|
1066
1063
|
var reinforceTool = {
|
|
@@ -1087,31 +1084,27 @@ var reinforceTool = {
|
|
|
1087
1084
|
};
|
|
1088
1085
|
|
|
1089
1086
|
// src/mcp/tools/search.ts
|
|
1090
|
-
import { z as
|
|
1087
|
+
import { z as z11 } from "zod";
|
|
1091
1088
|
var inputShape7 = {
|
|
1092
|
-
query:
|
|
1093
|
-
limit:
|
|
1094
|
-
|
|
1095
|
-
),
|
|
1096
|
-
strategy: z10.enum(["naive", "graph", "hybrid"]).optional().describe(
|
|
1089
|
+
query: z11.string().min(1).describe("Required natural-language search text."),
|
|
1090
|
+
limit: z11.number().int().min(1).max(100).optional().describe(SEARCH_LIMIT_DESC),
|
|
1091
|
+
strategy: z11.enum(["naive", "graph", "hybrid"]).optional().describe(
|
|
1097
1092
|
"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."
|
|
1098
1093
|
),
|
|
1099
|
-
effort:
|
|
1094
|
+
effort: z11.enum(["quick", "medium", "deep"]).optional().describe(
|
|
1100
1095
|
"Retrieval depth: quick=strongest, medium=default-depth, deep=everything including archived/superseded."
|
|
1101
1096
|
),
|
|
1102
|
-
type:
|
|
1103
|
-
agentId:
|
|
1104
|
-
abstentionThreshold:
|
|
1105
|
-
eventTimeStart:
|
|
1106
|
-
eventTimeEnd:
|
|
1107
|
-
asOf:
|
|
1108
|
-
anchorTime:
|
|
1097
|
+
type: z11.enum(["short-term", "long-term", "working", "episodic", "summary"]).optional().describe("Filter by memory type."),
|
|
1098
|
+
agentId: z11.string().optional().describe("Filter to memories stored for this agentId."),
|
|
1099
|
+
abstentionThreshold: z11.number().min(0).max(1).optional().describe("Enable confidence scoring; abstain when confidence falls below this value (0\u20131)."),
|
|
1100
|
+
eventTimeStart: z11.string().optional().describe("Inclusive event-time start (ISO-8601); must be paired with eventTimeEnd."),
|
|
1101
|
+
eventTimeEnd: z11.string().optional().describe("Inclusive event-time end (ISO-8601); must be paired with eventTimeStart."),
|
|
1102
|
+
asOf: z11.string().optional().describe("Only include memories ingested before this ISO-8601 timestamp."),
|
|
1103
|
+
anchorTime: z11.string().optional().describe(
|
|
1109
1104
|
'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.'
|
|
1110
1105
|
),
|
|
1111
|
-
enumerationConcept:
|
|
1112
|
-
|
|
1113
|
-
),
|
|
1114
|
-
enableRerank: z10.boolean().optional().describe(
|
|
1106
|
+
enumerationConcept: z11.string().trim().min(1).optional().describe(SEARCH_ENUMERATION_CONCEPT_DESC),
|
|
1107
|
+
enableRerank: z11.boolean().optional().describe(
|
|
1115
1108
|
"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."
|
|
1116
1109
|
),
|
|
1117
1110
|
...scopeShape
|
|
@@ -1188,67 +1181,8 @@ var statusTool = {
|
|
|
1188
1181
|
|
|
1189
1182
|
// src/mcp/tools/store.ts
|
|
1190
1183
|
import { z as z12 } from "zod";
|
|
1191
|
-
|
|
1192
|
-
// src/mcp/extraction-prompt.ts
|
|
1193
|
-
import { z as z11 } from "zod";
|
|
1194
|
-
var ENTITY_TYPES = ["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"];
|
|
1195
|
-
var RELATION_TYPES = [
|
|
1196
|
-
"USES",
|
|
1197
|
-
"OWNS",
|
|
1198
|
-
"DEPENDS_ON",
|
|
1199
|
-
"RELATED_TO",
|
|
1200
|
-
"CREATED_BY",
|
|
1201
|
-
"PART_OF",
|
|
1202
|
-
"IS_A",
|
|
1203
|
-
"WORKS_AT",
|
|
1204
|
-
"LOCATED_IN"
|
|
1205
|
-
];
|
|
1206
|
-
var ExtractionSchema = z11.object({
|
|
1207
|
-
entities: z11.array(
|
|
1208
|
-
z11.object({
|
|
1209
|
-
name: z11.string().min(1),
|
|
1210
|
-
type: z11.string().min(1).transform((value) => normalizeGraphLabel(value, "CONCEPT"))
|
|
1211
|
-
})
|
|
1212
|
-
),
|
|
1213
|
-
relations: z11.array(
|
|
1214
|
-
z11.object({
|
|
1215
|
-
source: z11.string().min(1),
|
|
1216
|
-
target: z11.string().min(1),
|
|
1217
|
-
type: z11.string().min(1).transform((value) => normalizeGraphLabel(value, "RELATED_TO"))
|
|
1218
|
-
})
|
|
1219
|
-
)
|
|
1220
|
-
});
|
|
1221
|
-
function buildExtractionPrompt(content) {
|
|
1222
|
-
return [
|
|
1223
|
-
"Extract graph facts as JSON only. No prose, no fences, no commentary.",
|
|
1224
|
-
`Schema: {"entities":[{"name":string,"type":EntityType}],"relations":[{"source":string,"target":string,"type":RelationType}]}.`,
|
|
1225
|
-
`Prefer EntityType values when applicable: ${ENTITY_TYPES.join(", ")}.`,
|
|
1226
|
-
`Prefer RelationType values when applicable: ${RELATION_TYPES.join(", ")}.`,
|
|
1227
|
-
"Emergent domain-specific labels are allowed; use uppercase words separated by underscores.",
|
|
1228
|
-
"Include only entities/relations explicitly named or strongly implied in the content. Empty arrays are valid.",
|
|
1229
|
-
`Content: ${content}`
|
|
1230
|
-
].join("\n");
|
|
1231
|
-
}
|
|
1232
|
-
function parseExtractionResponse(raw) {
|
|
1233
|
-
let parsed;
|
|
1234
|
-
try {
|
|
1235
|
-
parsed = JSON.parse(raw);
|
|
1236
|
-
} catch (cause) {
|
|
1237
|
-
throw new Error(
|
|
1238
|
-
`MCP sampling returned non-JSON text (first 120 chars: ${raw.slice(0, 120).replace(/\n/g, "\\n")})`,
|
|
1239
|
-
{ cause: cause instanceof Error ? cause : void 0 }
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
const result = ExtractionSchema.safeParse(parsed);
|
|
1243
|
-
if (!result.success) {
|
|
1244
|
-
throw new Error(`MCP sampling response failed schema validation: ${result.error.message}`);
|
|
1245
|
-
}
|
|
1246
|
-
return result.data;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
// src/mcp/tools/store.ts
|
|
1250
1184
|
var entityTypes = ["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"];
|
|
1251
|
-
var
|
|
1185
|
+
var preferredRelationshipTypes = [
|
|
1252
1186
|
"USES",
|
|
1253
1187
|
"OWNS",
|
|
1254
1188
|
"DEPENDS_ON",
|
|
@@ -1259,7 +1193,12 @@ var relationshipTypes = [
|
|
|
1259
1193
|
"WORKS_AT",
|
|
1260
1194
|
"LOCATED_IN"
|
|
1261
1195
|
];
|
|
1196
|
+
var RELATIONSHIP_TYPE_DESC = `Relationship type \u2014 freeform label; preferred: ${preferredRelationshipTypes.join(", ")}.`;
|
|
1262
1197
|
var storeTargets = ["sqlite", "vector", "graph"];
|
|
1198
|
+
var entityShape = z12.object({
|
|
1199
|
+
name: z12.string().min(1).describe("Entity name as referenced in content."),
|
|
1200
|
+
type: z12.enum(entityTypes).describe("Entity type.")
|
|
1201
|
+
});
|
|
1263
1202
|
var inputShape9 = {
|
|
1264
1203
|
content: z12.string().min(1).describe("Concise factual statement to persist; decision, not deliberation."),
|
|
1265
1204
|
topic: z12.string().min(1).describe("Required metadata.topic for retrieval grouping."),
|
|
@@ -1269,71 +1208,81 @@ var inputShape9 = {
|
|
|
1269
1208
|
"Storage targets. Include 'graph' when you provide entities/relationships or want zero graph write counts reported."
|
|
1270
1209
|
),
|
|
1271
1210
|
importance: z12.number().int().min(1).max(10).optional().describe("Importance 1\u201310."),
|
|
1272
|
-
eventTime: z12.string().optional().describe(
|
|
1273
|
-
'ISO-8601 time the fact happened or took effect (bi-temporal). Pass for any fact that can change or go stale \u2014 recency ordering, dated ("as of") queries, and stale-vs-current conflict resolution key off it.'
|
|
1274
|
-
),
|
|
1211
|
+
eventTime: z12.string().optional().describe(STORE_EVENT_TIME_DESC),
|
|
1275
1212
|
source: z12.string().optional().describe("Free-form origin (filename, URL, conversation id)."),
|
|
1276
1213
|
agentId: z12.string().optional().describe("Agent identifier stored alongside the entry."),
|
|
1277
1214
|
sessionId: z12.string().optional().describe("Session identifier for grouping."),
|
|
1278
1215
|
parentId: z12.string().optional().describe("Parent memory entry id (hierarchical)."),
|
|
1279
|
-
entities: z12.array(
|
|
1280
|
-
z12.object({
|
|
1281
|
-
name: z12.string().min(1).describe("Entity name as referenced in content."),
|
|
1282
|
-
type: z12.enum(entityTypes).describe("Entity type.")
|
|
1283
|
-
})
|
|
1284
|
-
).optional().describe(
|
|
1285
|
-
"Named entities mentioned by the content. The caller LLM must extract and pass these when the content names people, organizations, tools, places, events, or key concepts; the server does not auto-extract them."
|
|
1286
|
-
),
|
|
1216
|
+
entities: z12.array(entityShape).optional().describe(STORE_ENTITIES_DESC),
|
|
1287
1217
|
relationships: z12.array(
|
|
1288
1218
|
z12.object({
|
|
1289
1219
|
source: z12.string().min(1).describe("Source entity name (must appear in entities array)."),
|
|
1290
1220
|
target: z12.string().min(1).describe("Target entity name (must appear in entities array)."),
|
|
1291
|
-
type: z12.
|
|
1221
|
+
type: z12.string().min(1).describe(RELATIONSHIP_TYPE_DESC)
|
|
1292
1222
|
})
|
|
1293
|
-
).optional().describe(
|
|
1294
|
-
|
|
1295
|
-
|
|
1223
|
+
).optional().describe(STORE_RELATIONSHIPS_DESC),
|
|
1224
|
+
triples: z12.array(
|
|
1225
|
+
z12.object({
|
|
1226
|
+
subject: entityShape.describe("Subject entity (source node)."),
|
|
1227
|
+
relation: z12.string().min(1).describe(RELATIONSHIP_TYPE_DESC),
|
|
1228
|
+
object: entityShape.describe("Object entity (target node).")
|
|
1229
|
+
})
|
|
1230
|
+
).optional().describe(STORE_TRIPLES_DESC),
|
|
1296
1231
|
extractEntities: z12.boolean().optional().describe(
|
|
1297
|
-
"Override extraction: false skips
|
|
1232
|
+
"Override extraction: false skips extraction; true asks the server-side extraction brain to run and errors loudly when none is configured."
|
|
1233
|
+
),
|
|
1234
|
+
entitiesOnly: z12.boolean().optional().describe(
|
|
1235
|
+
"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."
|
|
1298
1236
|
),
|
|
1299
1237
|
...scopeShape
|
|
1300
1238
|
};
|
|
1239
|
+
function materializeGraphInput(args) {
|
|
1240
|
+
const entities = /* @__PURE__ */ new Map();
|
|
1241
|
+
const addEntity = (e) => {
|
|
1242
|
+
const key = `${normalizeNameKey(e.name)}|${e.type}`;
|
|
1243
|
+
if (!entities.has(key)) entities.set(key, e);
|
|
1244
|
+
};
|
|
1245
|
+
const relationships = /* @__PURE__ */ new Map();
|
|
1246
|
+
const addRel = (rel) => {
|
|
1247
|
+
const key = `${normalizeNameKey(rel.source)}|${normalizeNameKey(rel.target)}|${normalizeGraphLabel(rel.type, "RELATED_TO")}`;
|
|
1248
|
+
if (!relationships.has(key)) relationships.set(key, rel);
|
|
1249
|
+
};
|
|
1250
|
+
for (const e of args.entities ?? []) addEntity(e);
|
|
1251
|
+
for (const r of args.relationships ?? []) addRel(r);
|
|
1252
|
+
for (const t of args.triples ?? []) {
|
|
1253
|
+
addEntity(t.subject);
|
|
1254
|
+
addEntity(t.object);
|
|
1255
|
+
addRel({ source: t.subject.name, target: t.object.name, type: t.relation });
|
|
1256
|
+
}
|
|
1257
|
+
return { entities: [...entities.values()], relationships: [...relationships.values()] };
|
|
1258
|
+
}
|
|
1301
1259
|
var storeMemoryTool = {
|
|
1302
1260
|
name: "store_memory",
|
|
1303
1261
|
config: {
|
|
1304
1262
|
title: "Store pyx-memory entry",
|
|
1305
|
-
description:
|
|
1263
|
+
description: STORE_TOOL_DESC,
|
|
1306
1264
|
inputSchema: inputShape9,
|
|
1307
1265
|
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true }
|
|
1308
1266
|
},
|
|
1309
|
-
handler: (deps) => async (raw
|
|
1267
|
+
handler: (deps) => async (raw) => {
|
|
1310
1268
|
const args = raw;
|
|
1269
|
+
const { entities, relationships } = materializeGraphInput(args);
|
|
1270
|
+
const graphTargeted = !args.targets || args.targets.includes("graph");
|
|
1271
|
+
const entityNameKeys = new Set(entities.map((e) => normalizeNameKey(e.name)));
|
|
1272
|
+
const resolvableRelationships = relationships.filter((r) => {
|
|
1273
|
+
const source = normalizeNameKey(r.source);
|
|
1274
|
+
const target = normalizeNameKey(r.target);
|
|
1275
|
+
return source !== target && entityNameKeys.has(source) && entityNameKeys.has(target);
|
|
1276
|
+
}).length;
|
|
1277
|
+
if (graphTargeted && entities.length >= 2 && resolvableRelationships === 0 && !args.entitiesOnly && args.extractEntities !== true) {
|
|
1278
|
+
return mcpText(
|
|
1279
|
+
`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.`,
|
|
1280
|
+
true
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1311
1283
|
const creds = await deps.readCredentials();
|
|
1312
1284
|
if (!creds.ok) return creds.result;
|
|
1313
1285
|
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
1314
|
-
let entities = args.entities;
|
|
1315
|
-
let relationships = args.relationships;
|
|
1316
|
-
const samplingAvailable = deps.samplingClient?.isAvailable() ?? false;
|
|
1317
|
-
const optedOut = args.extractEntities === false;
|
|
1318
|
-
const forced = args.extractEntities === true;
|
|
1319
|
-
const graphTargeted = args.targets?.includes("graph") ?? true;
|
|
1320
|
-
const hasCallerEntities = (entities?.length ?? 0) > 0;
|
|
1321
|
-
if (forced && graphTargeted && !samplingAvailable && !hasCallerEntities) {
|
|
1322
|
-
throw new Error(
|
|
1323
|
-
"extractEntities=true requested but the connected MCP client did not advertise the sampling capability. Pass entities/relationships explicitly, set extractEntities:false, or connect a sampling-capable client."
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
if (graphTargeted && deps.samplingClient && samplingAvailable && !optedOut && !hasCallerEntities) {
|
|
1327
|
-
const prompt = buildExtractionPrompt(args.content);
|
|
1328
|
-
const completion = await deps.samplingClient.complete(
|
|
1329
|
-
prompt,
|
|
1330
|
-
ctx?.signal ? { signal: ctx.signal } : void 0
|
|
1331
|
-
);
|
|
1332
|
-
const extracted = parseExtractionResponse(completion);
|
|
1333
|
-
const merged = mergeExtractedEntities(args.entities, args.relationships, extracted);
|
|
1334
|
-
entities = merged.entities;
|
|
1335
|
-
relationships = merged.relationships;
|
|
1336
|
-
}
|
|
1337
1286
|
const body = {
|
|
1338
1287
|
content: args.content,
|
|
1339
1288
|
type: args.type ?? "long-term",
|
|
@@ -1348,9 +1297,9 @@ var storeMemoryTool = {
|
|
|
1348
1297
|
agentId: args.agentId,
|
|
1349
1298
|
sessionId: args.sessionId,
|
|
1350
1299
|
parentId: args.parentId,
|
|
1351
|
-
entities,
|
|
1352
|
-
relationships,
|
|
1353
|
-
...args.extractEntities
|
|
1300
|
+
entities: entities.length > 0 ? entities : void 0,
|
|
1301
|
+
relationships: relationships.length > 0 ? relationships : void 0,
|
|
1302
|
+
...args.extractEntities !== void 0 ? { extractEntities: args.extractEntities } : {}
|
|
1354
1303
|
};
|
|
1355
1304
|
const res = await http.requestJson({
|
|
1356
1305
|
method: "POST",
|
|
@@ -1391,7 +1340,7 @@ var summarizeMemoryEntityTool = {
|
|
|
1391
1340
|
const res2 = await http.requestJson({
|
|
1392
1341
|
method: "POST",
|
|
1393
1342
|
path: "/api/memory/synthesis/entity",
|
|
1394
|
-
body: {
|
|
1343
|
+
body: { name: args.entityName, entityType: args.entityType },
|
|
1395
1344
|
scope: args
|
|
1396
1345
|
});
|
|
1397
1346
|
return res2.ok ? mcpJson(res2.data) : res2.result;
|
|
@@ -1399,7 +1348,7 @@ var summarizeMemoryEntityTool = {
|
|
|
1399
1348
|
const res = await http.requestJson({
|
|
1400
1349
|
method: "GET",
|
|
1401
1350
|
path: "/api/memory/synthesis/entity",
|
|
1402
|
-
query: {
|
|
1351
|
+
query: { name: args.entityName, entityType: args.entityType },
|
|
1403
1352
|
scope: args
|
|
1404
1353
|
});
|
|
1405
1354
|
return res.ok ? mcpJson(res.data) : res.result;
|
|
@@ -1428,17 +1377,15 @@ var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
|
|
|
1428
1377
|
// src/mcp/server.ts
|
|
1429
1378
|
async function runMcpServer(opts) {
|
|
1430
1379
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
1431
|
-
const version = opts.version ?? (true ? "0.
|
|
1380
|
+
const version = opts.version ?? (true ? "1.0.0" : "0.0.0-dev");
|
|
1432
1381
|
const server = new McpServer(
|
|
1433
1382
|
{ name: "pyx-memory", version },
|
|
1434
|
-
{ instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {} } }
|
|
1383
|
+
{ instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {}, prompts: {} } }
|
|
1435
1384
|
);
|
|
1436
|
-
const samplingClient = createSamplingClient(server);
|
|
1437
1385
|
for (const tool of ALL_TOOLS) {
|
|
1438
1386
|
const handle = tool.handler({
|
|
1439
1387
|
readCredentials: opts.readCredentials,
|
|
1440
|
-
fetchImpl
|
|
1441
|
-
samplingClient
|
|
1388
|
+
fetchImpl
|
|
1442
1389
|
});
|
|
1443
1390
|
server.registerTool(
|
|
1444
1391
|
tool.name,
|
|
@@ -1446,9 +1393,12 @@ async function runMcpServer(opts) {
|
|
|
1446
1393
|
async (args, extra) => handle(args, { signal: extra?.signal })
|
|
1447
1394
|
);
|
|
1448
1395
|
}
|
|
1396
|
+
for (const prompt of ALL_PROMPTS) {
|
|
1397
|
+
prompt.register(server);
|
|
1398
|
+
}
|
|
1449
1399
|
const transport = new StdioServerTransport();
|
|
1450
|
-
const closed = new Promise((
|
|
1451
|
-
transport.onclose = () =>
|
|
1400
|
+
const closed = new Promise((resolve3) => {
|
|
1401
|
+
transport.onclose = () => resolve3();
|
|
1452
1402
|
});
|
|
1453
1403
|
await server.connect(transport);
|
|
1454
1404
|
await closed;
|
|
@@ -1457,9 +1407,9 @@ async function runMcpServer(opts) {
|
|
|
1457
1407
|
// src/cli/commands/mcp.ts
|
|
1458
1408
|
async function mcpCommand() {
|
|
1459
1409
|
const readCredentials = createReadCredentials(() => getDefaultKeychain());
|
|
1460
|
-
const onStdinEnd = new Promise((
|
|
1461
|
-
process.stdin.once("end",
|
|
1462
|
-
process.stdin.once("close",
|
|
1410
|
+
const onStdinEnd = new Promise((resolve3) => {
|
|
1411
|
+
process.stdin.once("end", resolve3);
|
|
1412
|
+
process.stdin.once("close", resolve3);
|
|
1463
1413
|
});
|
|
1464
1414
|
try {
|
|
1465
1415
|
await Promise.race([runMcpServer({ readCredentials }), onStdinEnd]);
|
|
@@ -1615,7 +1565,7 @@ function mcpInstallClaudeCodeCommand(opts = {}) {
|
|
|
1615
1565
|
process.stdout.write(
|
|
1616
1566
|
`Installed pyx-memory MCP server in Claude Code (scope: ${scope}).
|
|
1617
1567
|
Restart Claude Code to make the tools available. No API key was written to .mcp.json \u2014 credentials live in the OS credential store.
|
|
1618
|
-
To populate the knowledge graph, pass entities and relationships
|
|
1568
|
+
To populate the knowledge graph, YOU pass entities and relationships (or triples) \u2014 a multi-entity store needs at least one connecting edge or it is refused. The server does not extract for you unless a self-host operator configured a BYO extraction endpoint (then store_memory can forward extractEntities:true). Images require caller-provided descriptions/hooks.
|
|
1619
1569
|
`
|
|
1620
1570
|
);
|
|
1621
1571
|
return EXIT.OK;
|
|
@@ -1919,6 +1869,183 @@ function writeJsonAndReport(filePath, agentLabel, opts = {}) {
|
|
|
1919
1869
|
return result.exitCode;
|
|
1920
1870
|
}
|
|
1921
1871
|
|
|
1872
|
+
// src/cli/commands/scaffold.ts
|
|
1873
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1874
|
+
import { basename as basename2, join as join2, resolve as resolve2 } from "path";
|
|
1875
|
+
var SERVER_IMAGE = "ghcr.io/pyx-corp/pyx-memory-v1:latest";
|
|
1876
|
+
var DOCKER_COMPOSE = `services:
|
|
1877
|
+
pyx-memory:
|
|
1878
|
+
image: ${SERVER_IMAGE}
|
|
1879
|
+
ports:
|
|
1880
|
+
- "7822:7822"
|
|
1881
|
+
volumes:
|
|
1882
|
+
- pyx-memory-data:/data
|
|
1883
|
+
environment:
|
|
1884
|
+
DATA_DIR: "\${DATA_DIR:-/data}"
|
|
1885
|
+
API_KEY: "\${API_KEY:-}"
|
|
1886
|
+
ADMIN_API_KEY: "\${ADMIN_API_KEY:-}"
|
|
1887
|
+
TENANT_MODE: "\${TENANT_MODE:-single}"
|
|
1888
|
+
NEO4J_URL: "\${NEO4J_URL:-bolt://neo4j:7687}"
|
|
1889
|
+
NEO4J_USERNAME: "\${NEO4J_USERNAME:-neo4j}"
|
|
1890
|
+
NEO4J_PASSWORD: "\${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env or the shell.}"
|
|
1891
|
+
EMBEDDING_PROVIDER: "\${EMBEDDING_PROVIDER:-local}"
|
|
1892
|
+
EMBEDDING_ENDPOINT: "\${EMBEDDING_ENDPOINT:-}"
|
|
1893
|
+
EMBEDDING_DIMENSIONS: "\${EMBEDDING_DIMENSIONS:-}"
|
|
1894
|
+
# Graph is built by the CALLING AGENT by default (it passes entities +
|
|
1895
|
+
# relationships / triples) \u2014 the server runs no LLM. To opt into
|
|
1896
|
+
# server-side BYO extraction, set EXTRACTION_PROVIDER=http and supply
|
|
1897
|
+
# EXTRACTION_ENDPOINT in .env.
|
|
1898
|
+
EXTRACTION_PROVIDER: "\${EXTRACTION_PROVIDER:-none}"
|
|
1899
|
+
EXTRACTION_ENDPOINT: "\${EXTRACTION_ENDPOINT:-}"
|
|
1900
|
+
EXTRACTION_API_KEY: "\${EXTRACTION_API_KEY:-}"
|
|
1901
|
+
EXTRACTION_MODEL: "\${EXTRACTION_MODEL:-}"
|
|
1902
|
+
ENCRYPTION_KEY: "\${ENCRYPTION_KEY:-}"
|
|
1903
|
+
depends_on:
|
|
1904
|
+
neo4j:
|
|
1905
|
+
condition: service_healthy
|
|
1906
|
+
|
|
1907
|
+
neo4j:
|
|
1908
|
+
image: neo4j:5-community
|
|
1909
|
+
ports:
|
|
1910
|
+
- "7474:7474"
|
|
1911
|
+
- "7687:7687"
|
|
1912
|
+
volumes:
|
|
1913
|
+
- pyx-memory-neo4j-data:/data
|
|
1914
|
+
- pyx-memory-neo4j-logs:/logs
|
|
1915
|
+
environment:
|
|
1916
|
+
NEO4J_AUTH: "neo4j/\${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env or the shell.}"
|
|
1917
|
+
NEO4J_PLUGINS: '["apoc"]'
|
|
1918
|
+
healthcheck:
|
|
1919
|
+
test: ["CMD", "neo4j", "status"]
|
|
1920
|
+
interval: 10s
|
|
1921
|
+
timeout: 10s
|
|
1922
|
+
retries: 10
|
|
1923
|
+
|
|
1924
|
+
volumes:
|
|
1925
|
+
pyx-memory-data:
|
|
1926
|
+
pyx-memory-neo4j-data:
|
|
1927
|
+
pyx-memory-neo4j-logs:
|
|
1928
|
+
`;
|
|
1929
|
+
var ENV_EXAMPLE = `# pyx-memory operator config
|
|
1930
|
+
# Copy this file to .env, then fill the required values before running Docker Compose.
|
|
1931
|
+
|
|
1932
|
+
# OPTIONAL. Graph is built by the calling agent by default (the server runs no LLM).
|
|
1933
|
+
# Set to http ONLY to opt into server-side BYO entity/relationship extraction.
|
|
1934
|
+
EXTRACTION_PROVIDER=none
|
|
1935
|
+
|
|
1936
|
+
# REQUIRED only when EXTRACTION_PROVIDER=http. HTTP chat/completions-compatible base URL.
|
|
1937
|
+
# Leave empty for the default agent-owns-the-graph setup.
|
|
1938
|
+
EXTRACTION_ENDPOINT=
|
|
1939
|
+
|
|
1940
|
+
# OPTIONAL. Bearer token sent to EXTRACTION_ENDPOINT when your extraction service requires auth.
|
|
1941
|
+
EXTRACTION_API_KEY=
|
|
1942
|
+
|
|
1943
|
+
# OPTIONAL. Model identifier sent to the extraction service.
|
|
1944
|
+
EXTRACTION_MODEL=
|
|
1945
|
+
|
|
1946
|
+
# OPTIONAL for local embeddings. Set to http only when using a remote embedding service.
|
|
1947
|
+
EMBEDDING_PROVIDER=local
|
|
1948
|
+
|
|
1949
|
+
# REQUIRED only when EMBEDDING_PROVIDER=http. Base URL for the HTTP embedding service.
|
|
1950
|
+
EMBEDDING_ENDPOINT=
|
|
1951
|
+
|
|
1952
|
+
# REQUIRED only when EMBEDDING_PROVIDER=http. Must match the remote embedding vector width.
|
|
1953
|
+
# Example for embeddinggemma: 768.
|
|
1954
|
+
EMBEDDING_DIMENSIONS=
|
|
1955
|
+
|
|
1956
|
+
# RECOMMENDED for any protected or production deployment. Empty means unauthenticated local access.
|
|
1957
|
+
API_KEY=
|
|
1958
|
+
|
|
1959
|
+
# OPTIONAL. Destructive/admin operations use this; when empty, the server falls back to API_KEY.
|
|
1960
|
+
ADMIN_API_KEY=
|
|
1961
|
+
|
|
1962
|
+
# REQUIRED only when SENSITIVITY_POLICY=encrypt is configured on the server.
|
|
1963
|
+
# 32 bytes as 64 hex chars or 44 base64 chars.
|
|
1964
|
+
ENCRYPTION_KEY=
|
|
1965
|
+
|
|
1966
|
+
# REQUIRED by the scaffolded container.
|
|
1967
|
+
DATA_DIR=/data
|
|
1968
|
+
|
|
1969
|
+
# REQUIRED. Use single for one app/tenant; use multi only behind authenticated tenant routing.
|
|
1970
|
+
TENANT_MODE=single
|
|
1971
|
+
|
|
1972
|
+
# REQUIRED for graph storage in this compose stack.
|
|
1973
|
+
NEO4J_URL=bolt://neo4j:7687
|
|
1974
|
+
|
|
1975
|
+
# OPTIONAL. Defaults to neo4j when unset.
|
|
1976
|
+
NEO4J_USERNAME=neo4j
|
|
1977
|
+
|
|
1978
|
+
# REQUIRED by docker-compose.yml for the Neo4j service and pyx-memory graph connection.
|
|
1979
|
+
NEO4J_PASSWORD=
|
|
1980
|
+
`;
|
|
1981
|
+
var MEMORY_TS = `import { createPyxMemory } from '@pyxmate/memory';
|
|
1982
|
+
|
|
1983
|
+
// Reads PYX_MEMORY_URL and PYX_MEMORY_API_KEY by default.
|
|
1984
|
+
export const memory = createPyxMemory();
|
|
1985
|
+
`;
|
|
1986
|
+
function resolveTarget(args) {
|
|
1987
|
+
const cwd = args.cwd ?? process.cwd();
|
|
1988
|
+
if (args.name === true) {
|
|
1989
|
+
process.stderr.write("Error: --name requires a directory name.\n");
|
|
1990
|
+
return null;
|
|
1991
|
+
}
|
|
1992
|
+
const trimmedName = typeof args.name === "string" ? args.name.trim() : "";
|
|
1993
|
+
if (typeof args.name === "string" && trimmedName.length === 0) {
|
|
1994
|
+
process.stderr.write("Error: --name requires a non-empty directory name.\n");
|
|
1995
|
+
return null;
|
|
1996
|
+
}
|
|
1997
|
+
const targetDir = trimmedName ? resolve2(cwd, trimmedName) : cwd;
|
|
1998
|
+
const appName = trimmedName || basename2(resolve2(cwd));
|
|
1999
|
+
return { targetDir, appName };
|
|
2000
|
+
}
|
|
2001
|
+
function buildFiles(targetDir, appName) {
|
|
2002
|
+
return [
|
|
2003
|
+
{ path: join2(targetDir, "docker-compose.yml"), contents: DOCKER_COMPOSE },
|
|
2004
|
+
{ path: join2(targetDir, ".env.example"), contents: ENV_EXAMPLE },
|
|
2005
|
+
{ path: join2(targetDir, "memory.ts"), contents: MEMORY_TS },
|
|
2006
|
+
{
|
|
2007
|
+
path: join2(targetDir, "PYX_MEMORY_DESIGN_GUIDE.md"),
|
|
2008
|
+
contents: buildDesignGuide({ appName })
|
|
2009
|
+
}
|
|
2010
|
+
];
|
|
2011
|
+
}
|
|
2012
|
+
function scaffoldCommand(args = {}) {
|
|
2013
|
+
const target = resolveTarget(args);
|
|
2014
|
+
if (target === null) return EXIT.USAGE;
|
|
2015
|
+
if (existsSync2(target.targetDir) && !statSync(target.targetDir).isDirectory()) {
|
|
2016
|
+
process.stderr.write(`Error: target exists and is not a directory: ${target.targetDir}
|
|
2017
|
+
`);
|
|
2018
|
+
return EXIT.USAGE;
|
|
2019
|
+
}
|
|
2020
|
+
mkdirSync2(target.targetDir, { recursive: true });
|
|
2021
|
+
const created = [];
|
|
2022
|
+
const skipped = [];
|
|
2023
|
+
for (const file of buildFiles(target.targetDir, target.appName)) {
|
|
2024
|
+
const relativePath = basename2(file.path);
|
|
2025
|
+
if (existsSync2(file.path)) {
|
|
2026
|
+
skipped.push(relativePath);
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
writeFileSync2(file.path, file.contents, "utf8");
|
|
2030
|
+
created.push(relativePath);
|
|
2031
|
+
}
|
|
2032
|
+
process.stdout.write(
|
|
2033
|
+
[
|
|
2034
|
+
`pyx-memory scaffold: ${target.targetDir}`,
|
|
2035
|
+
`created: ${created.length > 0 ? created.join(", ") : "(none)"}`,
|
|
2036
|
+
`skipped: ${skipped.length > 0 ? skipped.join(", ") : "(none)"}`,
|
|
2037
|
+
"",
|
|
2038
|
+
"Next steps:",
|
|
2039
|
+
"1. Copy .env.example to .env and set NEO4J_PASSWORD (graph is built by the calling agent by default \u2014 set EXTRACTION_* only to opt into server-side BYO extraction).",
|
|
2040
|
+
"2. Set API_KEY before using this beyond local development.",
|
|
2041
|
+
"3. Run: docker compose up -d",
|
|
2042
|
+
"4. Set PYX_MEMORY_URL=http://localhost:7822 and PYX_MEMORY_API_KEY to match API_KEY.",
|
|
2043
|
+
""
|
|
2044
|
+
].join("\n")
|
|
2045
|
+
);
|
|
2046
|
+
return EXIT.OK;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1922
2049
|
// src/cli/commands/status.ts
|
|
1923
2050
|
async function statusCommand(opts = {}) {
|
|
1924
2051
|
const provider = opts.keychain ?? getDefaultKeychain();
|
|
@@ -1973,6 +2100,7 @@ Commands:
|
|
|
1973
2100
|
status [--json] Show endpoint, key presence, MCP config status.
|
|
1974
2101
|
logout Delete stored pyx-memory credentials.
|
|
1975
2102
|
doctor [--json] Diagnose keychain, credentials, backend, MCP startup.
|
|
2103
|
+
scaffold [--name <dir>] Generate Docker, env, SDK, and memory design-guide starter files.
|
|
1976
2104
|
mcp Start stdio MCP server.
|
|
1977
2105
|
mcp install <target> [--scope user|local|project]
|
|
1978
2106
|
Install pyx-memory MCP config for your AI agent.
|
|
@@ -2089,6 +2217,8 @@ async function main() {
|
|
|
2089
2217
|
return logoutCommand();
|
|
2090
2218
|
case "doctor":
|
|
2091
2219
|
return doctorCommand({ json: parsed.flags.json === true });
|
|
2220
|
+
case "scaffold":
|
|
2221
|
+
return scaffoldCommand({ name: parsed.flags.name });
|
|
2092
2222
|
case "mcp":
|
|
2093
2223
|
return runMcpCommand(parsed);
|
|
2094
2224
|
default:
|