@pyxmate/memory 0.31.1 → 0.31.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 +115 -53
  2. package/package.json +1 -1
@@ -166,9 +166,35 @@ function getDefaultKeychain() {
166
166
  return new NapiKeychainProvider();
167
167
  }
168
168
 
169
+ // src/cli/probe.ts
170
+ var PROBE_PATH = "/api/memory/entries?limit=1";
171
+ var PROBE_TIMEOUT_MS = 8e3;
172
+ async function probeCredentials(args) {
173
+ const fetcher = args.fetcher ?? fetch;
174
+ const url = `${args.endpoint}${PROBE_PATH}`;
175
+ const ctl = new AbortController();
176
+ const timer = setTimeout(() => ctl.abort(), PROBE_TIMEOUT_MS);
177
+ try {
178
+ const res = await fetcher(url, {
179
+ method: "GET",
180
+ headers: { Authorization: `Bearer ${args.apiKey}`, Accept: "application/json" },
181
+ signal: ctl.signal
182
+ });
183
+ if (res.status === 401 || res.status === 403) return { kind: "invalid", status: res.status };
184
+ if (res.ok) return { kind: "ok", status: res.status };
185
+ return { kind: "unreachable", reason: `HTTP ${res.status}` };
186
+ } catch (err) {
187
+ const msg = err instanceof Error ? err.message : String(err);
188
+ return { kind: "unreachable", reason: msg };
189
+ } finally {
190
+ clearTimeout(timer);
191
+ }
192
+ }
193
+
169
194
  // src/cli/commands/doctor.ts
170
195
  async function doctorCommand(opts = {}) {
171
196
  const provider = opts.keychain ?? getDefaultKeychain();
197
+ const fetcher = opts.fetchImpl ?? fetch;
172
198
  const checks = [];
173
199
  const accessCheck = await provider.check();
174
200
  if (accessCheck.ok) {
@@ -198,9 +224,10 @@ async function doctorCommand(opts = {}) {
198
224
  });
199
225
  }
200
226
  const accessOk = accessCheck.ok;
227
+ let creds = null;
201
228
  if (accessOk) {
202
229
  try {
203
- const creds = await provider.read();
230
+ creds = await provider.read();
204
231
  if (creds) {
205
232
  checks.push({
206
233
  id: "credentials.present",
@@ -237,11 +264,45 @@ async function doctorCommand(opts = {}) {
237
264
  message: "skipped \u2014 keychain access failed"
238
265
  });
239
266
  }
240
- checks.push({
241
- id: "mcp.coldStart",
242
- status: "skip",
243
- message: "wired in Step 5 \u2014 Lazy MCP server"
244
- });
267
+ if (creds) {
268
+ const probe = await probeCredentials({
269
+ endpoint: creds.endpoint,
270
+ apiKey: creds.apiKey,
271
+ fetcher
272
+ });
273
+ if (probe.kind === "ok") {
274
+ checks.push({
275
+ id: "backend.reachable",
276
+ status: "pass",
277
+ message: `pyx-memory API reachable (HTTP ${probe.status})`,
278
+ endpoint: creds.endpoint
279
+ });
280
+ } else if (probe.kind === "invalid") {
281
+ checks.push({
282
+ id: "backend.reachable",
283
+ status: "fail",
284
+ code: "key-rejected",
285
+ message: `API key rejected (HTTP ${probe.status})`,
286
+ endpoint: creds.endpoint,
287
+ guidance: "Re-login with a valid key: pyx-mem login"
288
+ });
289
+ } else {
290
+ checks.push({
291
+ id: "backend.reachable",
292
+ status: "fail",
293
+ code: "unreachable",
294
+ message: `no pyx-memory API at this endpoint (${probe.reason})`,
295
+ endpoint: creds.endpoint,
296
+ guidance: "Verify the endpoint is the API backend, not a web UI: pyx-mem login --endpoint <API_URL>"
297
+ });
298
+ }
299
+ } else {
300
+ checks.push({
301
+ id: "backend.reachable",
302
+ status: "skip",
303
+ message: "skipped \u2014 no credentials to probe"
304
+ });
305
+ }
245
306
  const ok = checks.every((c) => c.status !== "fail");
246
307
  if (opts.json) {
247
308
  process.stdout.write(`${JSON.stringify({ ok, checks })}
@@ -337,8 +398,6 @@ async function readSingleLineFromStdin() {
337
398
 
338
399
  // src/cli/commands/login.ts
339
400
  var DEFAULT_ENDPOINT = "https://memory.api.pyxmate.com";
340
- var PROBE_PATH = "/api/memory/stats";
341
- var PROBE_TIMEOUT_MS = 8e3;
342
401
  async function loginCommand(opts = {}) {
343
402
  const provider = opts.keychain ?? getDefaultKeychain();
344
403
  const fetcher = opts.fetchImpl ?? fetch;
@@ -396,26 +455,6 @@ No credentials were written. pyx-mem does not write plaintext token files.
396
455
  }
397
456
  return EXIT.OK;
398
457
  }
399
- async function probeCredentials(args) {
400
- const url = `${args.endpoint}${PROBE_PATH}`;
401
- const ctl = new AbortController();
402
- const timer = setTimeout(() => ctl.abort(), PROBE_TIMEOUT_MS);
403
- try {
404
- const res = await args.fetcher(url, {
405
- method: "GET",
406
- headers: { Authorization: `Bearer ${args.apiKey}`, Accept: "application/json" },
407
- signal: ctl.signal
408
- });
409
- if (res.status === 401 || res.status === 403) return { kind: "invalid", status: res.status };
410
- if (res.ok) return { kind: "ok", status: res.status };
411
- return { kind: "unreachable", reason: `HTTP ${res.status}` };
412
- } catch (err) {
413
- const msg = err instanceof Error ? err.message : String(err);
414
- return { kind: "unreachable", reason: msg };
415
- } finally {
416
- clearTimeout(timer);
417
- }
418
- }
419
458
  async function readExistingEndpoint(provider) {
420
459
  try {
421
460
  const creds = await provider.read();
@@ -598,30 +637,10 @@ function createHttpClient(credentials, fetchImpl = fetch) {
598
637
  clearTimeout(timer);
599
638
  }
600
639
  if (res.status === 401 || res.status === 403) {
601
- return { ok: false, result: mcpAuthFailed(res.status) };
640
+ return { ok: false, status: res.status, result: mcpAuthFailed(res.status) };
602
641
  }
603
642
  const text = await res.text().catch(() => "");
604
- if (res.status >= 500) {
605
- return { ok: false, result: mcpServerError(res.status, text) };
606
- }
607
- let parsed;
608
- if (text.length > 0) {
609
- try {
610
- parsed = JSON.parse(text);
611
- } catch {
612
- if (!res.ok) return { ok: false, result: mcpHttpError(res.status, text || "no body") };
613
- return { ok: true, status: res.status, data: text };
614
- }
615
- }
616
- if (!res.ok) {
617
- const message = extractEnvelopeError(parsed) ?? text ?? `HTTP ${res.status}`;
618
- return { ok: false, result: mcpHttpError(res.status, message) };
619
- }
620
- const envelopeError = extractEnvelopeError(parsed);
621
- if (envelopeError) {
622
- return { ok: false, result: mcpEnvelopeError(envelopeError) };
623
- }
624
- return { ok: true, status: res.status, data: parsed };
643
+ return parseHttpResponse(res, text);
625
644
  }
626
645
  return {
627
646
  async requestJson(input) {
@@ -638,6 +657,39 @@ function createHttpClient(credentials, fetchImpl = fetch) {
638
657
  }
639
658
  };
640
659
  }
660
+ function parseHttpResponse(res, text) {
661
+ if (res.status >= 500) {
662
+ return { ok: false, status: res.status, result: mcpServerError(res.status, text) };
663
+ }
664
+ const parsed = parseJsonText(text);
665
+ if (!parsed.ok) {
666
+ if (!res.ok) {
667
+ return {
668
+ ok: false,
669
+ status: res.status,
670
+ result: mcpHttpError(res.status, text || "no body")
671
+ };
672
+ }
673
+ return { ok: true, status: res.status, data: text };
674
+ }
675
+ if (!res.ok) {
676
+ const message = extractEnvelopeError(parsed.value) ?? text ?? `HTTP ${res.status}`;
677
+ return { ok: false, status: res.status, result: mcpHttpError(res.status, message) };
678
+ }
679
+ const envelopeError = extractEnvelopeError(parsed.value);
680
+ if (envelopeError) {
681
+ return { ok: false, status: res.status, result: mcpEnvelopeError(envelopeError) };
682
+ }
683
+ return { ok: true, status: res.status, data: parsed.value };
684
+ }
685
+ function parseJsonText(text) {
686
+ if (text.length === 0) return { ok: true, value: void 0 };
687
+ try {
688
+ return { ok: true, value: JSON.parse(text) };
689
+ } catch {
690
+ return { ok: false };
691
+ }
692
+ }
641
693
  function extractEnvelopeError(parsed) {
642
694
  if (parsed === null || typeof parsed !== "object") return null;
643
695
  const obj = parsed;
@@ -1012,7 +1064,7 @@ var statusTool = {
1012
1064
  name: "status",
1013
1065
  config: {
1014
1066
  title: "Get pyx-memory topology",
1015
- description: "Fetch the running pyx-memory server topology: build variant (cloud|full), declared deployment role, server version, embedding location (inline|remote), and active model profile. HTTP proxy to GET /status \u2014 no in-process branch.",
1067
+ 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.",
1016
1068
  inputSchema: inputShape6,
1017
1069
  annotations: { readOnlyHint: true, openWorldHint: true }
1018
1070
  },
@@ -1021,7 +1073,17 @@ var statusTool = {
1021
1073
  if (!creds.ok) return creds.result;
1022
1074
  const http = createHttpClient(creds.credentials, deps.fetchImpl);
1023
1075
  const res = await http.requestJson({ method: "GET", path: "/status" });
1024
- return res.ok ? mcpJson(res.data) : res.result;
1076
+ if (res.ok) return mcpJson(res.data);
1077
+ if (res.status !== 404) return res.result;
1078
+ const health = await http.requestJson({ method: "GET", path: "/health" });
1079
+ if (!health.ok) return res.result;
1080
+ return mcpJson({
1081
+ topologyUnavailable: true,
1082
+ reason: "GET /status returned 404; this endpoint exposes /health but not topology status.",
1083
+ statusEndpoint: "/status",
1084
+ healthEndpoint: "/health",
1085
+ health: health.data
1086
+ });
1025
1087
  }
1026
1088
  };
1027
1089
 
@@ -1262,7 +1324,7 @@ var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
1262
1324
  // src/mcp/server.ts
1263
1325
  async function runMcpServer(opts) {
1264
1326
  const fetchImpl = opts.fetchImpl ?? fetch;
1265
- const version = opts.version ?? (true ? "0.31.1" : "0.0.0-dev");
1327
+ const version = opts.version ?? (true ? "0.31.3" : "0.0.0-dev");
1266
1328
  const server = new McpServer(
1267
1329
  { name: "pyx-memory", version },
1268
1330
  { instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {} } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyxmate/memory",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
4
4
  "type": "module",
5
5
  "description": "SDK for pyx-memory — Memory as a Service for AI agents",
6
6
  "license": "MIT",