@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.
- package/dist/cli/pyx-mem.mjs +115 -53
- package/package.json +1 -1
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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: {} } }
|