@pyxmate/memory 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/pyx-mem.mjs +229 -121
  2. package/package.json +1 -1
@@ -352,7 +352,7 @@ async function promptMasked(label) {
352
352
  `);
353
353
  return value;
354
354
  }
355
- return new Promise((resolve3, reject) => {
355
+ return new Promise((resolve4, reject) => {
356
356
  const previousEncoding = stdin.readableEncoding;
357
357
  stdin.setEncoding("utf8");
358
358
  stdin.setRawMode(true);
@@ -370,7 +370,7 @@ async function promptMasked(label) {
370
370
  if (ch === "\n" || ch === "\r") {
371
371
  cleanup();
372
372
  stdout.write("\n");
373
- resolve3(buf.trim());
373
+ resolve4(buf.trim());
374
374
  return;
375
375
  }
376
376
  if (ch === "") {
@@ -564,6 +564,8 @@ function createReadCredentials(providerFactory) {
564
564
  }
565
565
 
566
566
  // src/mcp/proxy-server.ts
567
+ import { readFile, stat } from "fs/promises";
568
+ import { basename, isAbsolute, resolve } from "path";
567
569
  import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
568
570
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
569
571
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -574,108 +576,6 @@ import {
574
576
  ListPromptsRequestSchema,
575
577
  ListToolsRequestSchema
576
578
  } from "@modelcontextprotocol/sdk/types.js";
577
- var REMOTE_MCP_PATH = "/mcp";
578
- function createProxyServer(client, version) {
579
- const caps = client.getServerCapabilities();
580
- const server = new Server(client.getServerVersion() ?? { name: "pyx-memory", version }, {
581
- capabilities: {
582
- // Advertise the PRESENCE of tools/prompts, but NOT their `listChanged`
583
- // sub-capability: this proxy does not relay the remote's list_changed
584
- // notifications, so claiming listChanged would be a capability the host
585
- // could rely on but we never honor (Codex: keep the proxy transparent).
586
- ...caps?.tools ? { tools: {} } : {},
587
- ...caps?.prompts ? { prompts: {} } : {}
588
- },
589
- instructions: client.getInstructions()
590
- });
591
- if (caps?.tools) {
592
- server.setRequestHandler(
593
- ListToolsRequestSchema,
594
- (req) => client.listTools(req.params)
595
- );
596
- server.setRequestHandler(
597
- CallToolRequestSchema,
598
- (req) => client.callTool(req.params)
599
- );
600
- }
601
- if (caps?.prompts) {
602
- server.setRequestHandler(
603
- ListPromptsRequestSchema,
604
- (req) => client.listPrompts(req.params)
605
- );
606
- server.setRequestHandler(
607
- GetPromptRequestSchema,
608
- (req) => client.getPrompt(req.params)
609
- );
610
- }
611
- return server;
612
- }
613
- async function runMcpProxyServer(opts) {
614
- const version = opts.version ?? (true ? "1.1.2" : "0.0.0-dev");
615
- const read = await opts.readCredentials();
616
- if (!read.ok) {
617
- const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
618
- throw new Error(text || "pyx-memory credentials are unavailable. Run: pyx-mem login");
619
- }
620
- const { endpoint, apiKey } = read.credentials;
621
- const url = new URL(`${endpoint.replace(/\/+$/, "")}${REMOTE_MCP_PATH}`);
622
- const client = new McpClient({ name: "pyx-mem-proxy", version }, { capabilities: {} });
623
- const clientTransport = new StreamableHTTPClientTransport(url, {
624
- requestInit: { headers: { Authorization: `Bearer ${apiKey}` } }
625
- });
626
- try {
627
- await client.connect(clientTransport);
628
- } catch (err) {
629
- const msg = err instanceof Error ? err.message : String(err);
630
- throw new Error(
631
- `Could not reach the pyx-memory remote MCP at ${url.href}: ${msg}. Verify the endpoint and key with \`pyx-mem doctor\`, or run the bundled server with \`pyx-mem mcp\`.`
632
- );
633
- }
634
- try {
635
- const server = createProxyServer(client, version);
636
- const transport = new StdioServerTransport();
637
- const closed = new Promise((resolve3) => {
638
- transport.onclose = () => resolve3();
639
- });
640
- await server.connect(transport);
641
- await closed;
642
- } finally {
643
- await client.close().catch(() => {
644
- });
645
- }
646
- }
647
-
648
- // src/mcp/server.ts
649
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
650
- import { StdioServerTransport as StdioServerTransport2 } from "@modelcontextprotocol/sdk/server/stdio.js";
651
-
652
- // src/mcp/prompts/index.ts
653
- import { z } from "zod";
654
- var structureGraphPrompt = {
655
- name: "structure_graph",
656
- description: STRUCTURE_GRAPH_PROMPT_DESC,
657
- register(server) {
658
- server.registerPrompt(
659
- "structure_graph",
660
- {
661
- title: "Structure content into a knowledge graph",
662
- description: STRUCTURE_GRAPH_PROMPT_DESC,
663
- argsSchema: {
664
- content: z.string().min(1).describe("The memory content to extract graph fields from.")
665
- }
666
- },
667
- ({ content }) => ({
668
- messages: [
669
- { role: "user", content: { type: "text", text: buildGraphStructuringPrompt(content) } }
670
- ]
671
- })
672
- );
673
- }
674
- };
675
- var ALL_PROMPTS = [structureGraphPrompt];
676
-
677
- // src/mcp/tools/corrections.ts
678
- import { z as z3 } from "zod";
679
579
 
680
580
  // src/mcp/http-client.ts
681
581
  var DEFAULT_TIMEOUT_MS = 15e3;
@@ -781,6 +681,202 @@ function extractEnvelopeError(parsed) {
781
681
  return null;
782
682
  }
783
683
 
684
+ // src/mcp/tools/shared.ts
685
+ var LOCAL_UPLOAD_META_KEY = "pyx.dev/local-upload";
686
+
687
+ // src/mcp/proxy-server.ts
688
+ var REMOTE_MCP_PATH = "/mcp";
689
+ function extractLocalUpload(meta) {
690
+ if (meta === null || typeof meta !== "object") return void 0;
691
+ const descriptor = meta[LOCAL_UPLOAD_META_KEY];
692
+ if (descriptor === null || typeof descriptor !== "object") return void 0;
693
+ const obj = descriptor;
694
+ if (typeof obj.pathArg !== "string" || typeof obj.endpoint !== "string" || typeof obj.fileField !== "string") {
695
+ return void 0;
696
+ }
697
+ if (!obj.endpoint.startsWith("/") || obj.endpoint.startsWith("//")) {
698
+ return void 0;
699
+ }
700
+ return obj;
701
+ }
702
+ async function readCappedLocalFile(rawPath, descriptor) {
703
+ if (typeof rawPath !== "string" || rawPath.length === 0) {
704
+ return {
705
+ ok: false,
706
+ result: mcpText(`\`${descriptor.pathArg}\` must be a non-empty local file path.`, true)
707
+ };
708
+ }
709
+ const abs = isAbsolute(rawPath) ? rawPath : resolve(rawPath);
710
+ try {
711
+ if (typeof descriptor.maxBytes === "number") {
712
+ const info = await stat(abs);
713
+ if (info.size > descriptor.maxBytes) {
714
+ return {
715
+ ok: false,
716
+ result: mcpText(`File too large: ${info.size} bytes > ${descriptor.maxBytes} cap.`, true)
717
+ };
718
+ }
719
+ }
720
+ return { ok: true, bytes: await readFile(abs), name: basename(abs) };
721
+ } catch (err) {
722
+ return {
723
+ ok: false,
724
+ result: mcpText(
725
+ `Failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}.`,
726
+ true
727
+ )
728
+ };
729
+ }
730
+ }
731
+ function createLocalFileUploader(credentials, fetchImpl = fetch) {
732
+ return async (descriptor, args) => {
733
+ const file = await readCappedLocalFile(args[descriptor.pathArg], descriptor);
734
+ if (!file.ok) return file.result;
735
+ const http = createHttpClient(credentials, fetchImpl);
736
+ const res = await http.requestMultipart({
737
+ path: descriptor.endpoint,
738
+ formData: () => {
739
+ const form = new FormData();
740
+ form.set(descriptor.fileField, new File([new Uint8Array(file.bytes)], file.name));
741
+ for (const key of descriptor.optionalArgs ?? []) {
742
+ const value = args[key];
743
+ if (typeof value === "string" && value.length > 0) form.set(key, value);
744
+ }
745
+ return form;
746
+ }
747
+ });
748
+ return res.ok ? mcpJson(res.data) : res.result;
749
+ };
750
+ }
751
+ function createProxyServer(client, version, uploadLocalFile) {
752
+ const caps = client.getServerCapabilities();
753
+ const server = new Server(client.getServerVersion() ?? { name: "pyx-memory", version }, {
754
+ capabilities: {
755
+ // Advertise the PRESENCE of tools/prompts, but NOT their `listChanged`
756
+ // sub-capability: this proxy does not relay the remote's list_changed
757
+ // notifications, so claiming listChanged would be a capability the host
758
+ // could rely on but we never honor (Codex: keep the proxy transparent).
759
+ ...caps?.tools ? { tools: {} } : {},
760
+ ...caps?.prompts ? { prompts: {} } : {}
761
+ },
762
+ instructions: client.getInstructions()
763
+ });
764
+ const localUploads = /* @__PURE__ */ new Map();
765
+ let listedOnce = false;
766
+ const refreshFromList = (result) => {
767
+ const tools = result.tools;
768
+ if (Array.isArray(tools)) {
769
+ listedOnce = true;
770
+ for (const tool of tools) {
771
+ const descriptor = uploadLocalFile ? extractLocalUpload(tool._meta) : void 0;
772
+ if (descriptor) localUploads.set(tool.name, descriptor);
773
+ else localUploads.delete(tool.name);
774
+ }
775
+ }
776
+ return result;
777
+ };
778
+ if (caps?.tools) {
779
+ server.setRequestHandler(
780
+ ListToolsRequestSchema,
781
+ async (req) => refreshFromList(await client.listTools(req.params))
782
+ );
783
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
784
+ if (uploadLocalFile && !listedOnce) {
785
+ try {
786
+ refreshFromList(await client.listTools());
787
+ } catch {
788
+ }
789
+ }
790
+ const descriptor = uploadLocalFile ? localUploads.get(req.params.name) : void 0;
791
+ if (uploadLocalFile && descriptor) {
792
+ const result = await uploadLocalFile(
793
+ descriptor,
794
+ req.params.arguments ?? {}
795
+ );
796
+ return result;
797
+ }
798
+ return client.callTool(req.params);
799
+ });
800
+ }
801
+ if (caps?.prompts) {
802
+ server.setRequestHandler(
803
+ ListPromptsRequestSchema,
804
+ (req) => client.listPrompts(req.params)
805
+ );
806
+ server.setRequestHandler(
807
+ GetPromptRequestSchema,
808
+ (req) => client.getPrompt(req.params)
809
+ );
810
+ }
811
+ return server;
812
+ }
813
+ async function runMcpProxyServer(opts) {
814
+ const version = opts.version ?? (true ? "1.1.3" : "0.0.0-dev");
815
+ const read = await opts.readCredentials();
816
+ if (!read.ok) {
817
+ const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
818
+ throw new Error(text || "pyx-memory credentials are unavailable. Run: pyx-mem login");
819
+ }
820
+ const { endpoint, apiKey } = read.credentials;
821
+ const url = new URL(`${endpoint.replace(/\/+$/, "")}${REMOTE_MCP_PATH}`);
822
+ const client = new McpClient({ name: "pyx-mem-proxy", version }, { capabilities: {} });
823
+ const clientTransport = new StreamableHTTPClientTransport(url, {
824
+ requestInit: { headers: { Authorization: `Bearer ${apiKey}` } }
825
+ });
826
+ try {
827
+ await client.connect(clientTransport);
828
+ } catch (err) {
829
+ const msg = err instanceof Error ? err.message : String(err);
830
+ 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\`, or run the bundled server with \`pyx-mem mcp\`.`
832
+ );
833
+ }
834
+ try {
835
+ const server = createProxyServer(client, version, createLocalFileUploader(read.credentials));
836
+ const transport = new StdioServerTransport();
837
+ const closed = new Promise((resolve4) => {
838
+ transport.onclose = () => resolve4();
839
+ });
840
+ await server.connect(transport);
841
+ await closed;
842
+ } finally {
843
+ await client.close().catch(() => {
844
+ });
845
+ }
846
+ }
847
+
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
+
784
880
  // src/mcp/tools/scopes.ts
785
881
  import { z as z2 } from "zod";
786
882
  var scopeShape = {
@@ -923,11 +1019,12 @@ var getMemoryTool = {
923
1019
  };
924
1020
 
925
1021
  // src/mcp/tools/ingest.ts
926
- import { readFile, stat } from "fs/promises";
927
- import { basename, extname, isAbsolute, resolve } from "path";
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";
928
1024
  import { z as z6 } from "zod";
929
1025
  var IMAGE_EXT = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff", ".svg"]);
930
1026
  var MAX_BYTES = 50 * 1024 * 1024;
1027
+ var INGEST_ENDPOINT = "/api/memory/ingest/file";
931
1028
  var inputShape3 = {
932
1029
  path: z6.string().min(1).describe(
933
1030
  "Local file path readable by the pyx-mem process. Uploaded as multipart `file`. Images require `description`; documents auto-extract text."
@@ -946,6 +1043,17 @@ var ingestMemoryFileTool = {
946
1043
  inputSchema: inputShape3,
947
1044
  annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true }
948
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
+ },
949
1057
  handler: (deps) => async (raw) => {
950
1058
  const args = raw;
951
1059
  const ext = extname(args.path).toLowerCase();
@@ -956,14 +1064,14 @@ var ingestMemoryFileTool = {
956
1064
  true
957
1065
  );
958
1066
  }
959
- const abs = isAbsolute(args.path) ? args.path : resolve(args.path);
1067
+ const abs = isAbsolute2(args.path) ? args.path : resolve2(args.path);
960
1068
  let bytes;
961
1069
  try {
962
- const info = await stat(abs);
1070
+ const info = await stat2(abs);
963
1071
  if (info.size > MAX_BYTES) {
964
1072
  return mcpText(`File too large: ${info.size} bytes > 50MB cap.`, true);
965
1073
  }
966
- bytes = await readFile(abs);
1074
+ bytes = await readFile2(abs);
967
1075
  } catch (err) {
968
1076
  return mcpText(
969
1077
  `Failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}.`,
@@ -974,11 +1082,11 @@ var ingestMemoryFileTool = {
974
1082
  if (!creds.ok) return creds.result;
975
1083
  const http = createHttpClient(creds.credentials, deps.fetchImpl);
976
1084
  const res = await http.requestMultipart({
977
- path: "/api/memory/ingest/file",
1085
+ path: INGEST_ENDPOINT,
978
1086
  scope: args,
979
1087
  formData: () => {
980
1088
  const form = new FormData();
981
- const file = new File([new Uint8Array(bytes)], basename(abs));
1089
+ const file = new File([new Uint8Array(bytes)], basename2(abs));
982
1090
  form.set("file", file);
983
1091
  if (args.description) form.set("description", args.description);
984
1092
  if (args.namespaceId) form.set("namespaceId", args.namespaceId);
@@ -1459,7 +1567,7 @@ var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
1459
1567
  // src/mcp/server.ts
1460
1568
  async function runMcpServer(opts) {
1461
1569
  const fetchImpl = opts.fetchImpl ?? fetch;
1462
- const version = opts.version ?? (true ? "1.1.2" : "0.0.0-dev");
1570
+ const version = opts.version ?? (true ? "1.1.3" : "0.0.0-dev");
1463
1571
  const server = new McpServer(
1464
1572
  { name: "pyx-memory", version },
1465
1573
  { instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {}, prompts: {} } }
@@ -1479,8 +1587,8 @@ async function runMcpServer(opts) {
1479
1587
  prompt.register(server);
1480
1588
  }
1481
1589
  const transport = new StdioServerTransport2();
1482
- const closed = new Promise((resolve3) => {
1483
- transport.onclose = () => resolve3();
1590
+ const closed = new Promise((resolve4) => {
1591
+ transport.onclose = () => resolve4();
1484
1592
  });
1485
1593
  await server.connect(transport);
1486
1594
  await closed;
@@ -1489,9 +1597,9 @@ async function runMcpServer(opts) {
1489
1597
  // src/cli/commands/mcp.ts
1490
1598
  async function mcpCommand(opts = {}) {
1491
1599
  const readCredentials = createReadCredentials(() => getDefaultKeychain());
1492
- const onStdinEnd = new Promise((resolve3) => {
1493
- process.stdin.once("end", resolve3);
1494
- process.stdin.once("close", resolve3);
1600
+ const onStdinEnd = new Promise((resolve4) => {
1601
+ process.stdin.once("end", resolve4);
1602
+ process.stdin.once("close", resolve4);
1495
1603
  });
1496
1604
  try {
1497
1605
  const run = opts.remote ? runMcpProxyServer({ readCredentials }) : runMcpServer({ readCredentials });
@@ -1959,7 +2067,7 @@ function writeJsonAndReport(filePath, agentLabel, entry, opts = {}) {
1959
2067
 
1960
2068
  // src/cli/commands/scaffold.ts
1961
2069
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
1962
- import { basename as basename2, join as join2, resolve as resolve2 } from "path";
2070
+ import { basename as basename3, join as join2, resolve as resolve3 } from "path";
1963
2071
  var SERVER_IMAGE = "ghcr.io/pyx-corp/pyx-memory-v1:latest";
1964
2072
  var DOCKER_COMPOSE = `services:
1965
2073
  pyx-memory:
@@ -2082,8 +2190,8 @@ function resolveTarget(args) {
2082
2190
  process.stderr.write("Error: --name requires a non-empty directory name.\n");
2083
2191
  return null;
2084
2192
  }
2085
- const targetDir = trimmedName ? resolve2(cwd, trimmedName) : cwd;
2086
- const appName = trimmedName || basename2(resolve2(cwd));
2193
+ const targetDir = trimmedName ? resolve3(cwd, trimmedName) : cwd;
2194
+ const appName = trimmedName || basename3(resolve3(cwd));
2087
2195
  return { targetDir, appName };
2088
2196
  }
2089
2197
  function buildFiles(targetDir, appName) {
@@ -2109,7 +2217,7 @@ function scaffoldCommand(args = {}) {
2109
2217
  const created = [];
2110
2218
  const skipped = [];
2111
2219
  for (const file of buildFiles(target.targetDir, target.appName)) {
2112
- const relativePath = basename2(file.path);
2220
+ const relativePath = basename3(file.path);
2113
2221
  if (existsSync2(file.path)) {
2114
2222
  skipped.push(relativePath);
2115
2223
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyxmate/memory",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "description": "SDK for pyx-memory — Memory as a Service for AI agents",
6
6
  "license": "MIT",