@pyxmate/memory 1.17.19 → 1.18.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/{chunk-T35ZOOES.mjs → chunk-ACFPMLNO.mjs} +1 -1
- package/dist/chunk-KVYCISUI.mjs +206 -0
- package/dist/{chunk-H6ZLMPZH.mjs → chunk-SLYCVO4C.mjs} +53 -201
- package/dist/cli/pyx-mem.mjs +478 -264
- package/dist/dashboard.mjs +3 -2
- package/dist/index.d.ts +63 -2
- package/dist/index.mjs +14 -4
- package/dist/react.mjs +3 -2
- package/package.json +1 -1
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -3,6 +3,14 @@ import {
|
|
|
3
3
|
PERSISTENT_MEMORY_SECTION,
|
|
4
4
|
buildDesignGuide
|
|
5
5
|
} from "../chunk-EZERG25I.mjs";
|
|
6
|
+
import {
|
|
7
|
+
MEMORY_PROJECT_LABEL_MAX_CHARS,
|
|
8
|
+
TAXONOMY_MAX_CATEGORIES,
|
|
9
|
+
TAXONOMY_MAX_PROJECTS,
|
|
10
|
+
TAXONOMY_MAX_SAMPLE_TOPICS,
|
|
11
|
+
TAXONOMY_MAX_TOP_ENTITIES
|
|
12
|
+
} from "../chunk-KVYCISUI.mjs";
|
|
13
|
+
import "../chunk-3OLH3HYR.mjs";
|
|
6
14
|
|
|
7
15
|
// src/cli/exit-codes.ts
|
|
8
16
|
var EXIT = {
|
|
@@ -328,6 +336,348 @@ async function doctorCommand(opts = {}) {
|
|
|
328
336
|
return ok ? EXIT.OK : EXIT.DOCTOR_FAIL;
|
|
329
337
|
}
|
|
330
338
|
|
|
339
|
+
// src/cli/commands/insights.ts
|
|
340
|
+
import { z } from "zod";
|
|
341
|
+
|
|
342
|
+
// src/cli/commands/read-surface.ts
|
|
343
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
344
|
+
|
|
345
|
+
// src/mcp/errors.ts
|
|
346
|
+
var RUN_LOGIN = "Run: pyx-mem login";
|
|
347
|
+
var RUN_DOCTOR = "Run: pyx-mem doctor";
|
|
348
|
+
function mcpText(text, isError = false) {
|
|
349
|
+
return { isError, content: [{ type: "text", text }] };
|
|
350
|
+
}
|
|
351
|
+
function mcpMissingCredentials() {
|
|
352
|
+
return mcpText(`pyx-memory credentials are not configured.
|
|
353
|
+
${RUN_LOGIN}`, true);
|
|
354
|
+
}
|
|
355
|
+
function mcpKeychainError(message, guidance) {
|
|
356
|
+
const lines = ["pyx-memory credentials could not be read from the OS credential store."];
|
|
357
|
+
if (message) lines.push(message);
|
|
358
|
+
if (guidance) lines.push(guidance);
|
|
359
|
+
lines.push(RUN_LOGIN);
|
|
360
|
+
return mcpText(lines.join("\n"), true);
|
|
361
|
+
}
|
|
362
|
+
function mcpAuthFailed(status) {
|
|
363
|
+
return mcpText(`Authentication failed (HTTP ${status}).
|
|
364
|
+
${RUN_LOGIN}`, true);
|
|
365
|
+
}
|
|
366
|
+
function mcpUnreachable(endpoint, reason) {
|
|
367
|
+
return mcpText(`Unable to reach pyx-memory at ${endpoint}: ${reason}.
|
|
368
|
+
${RUN_DOCTOR}`, true);
|
|
369
|
+
}
|
|
370
|
+
function mcpHttpError(status, body) {
|
|
371
|
+
return mcpText(`${status}: ${body}`, true);
|
|
372
|
+
}
|
|
373
|
+
function mcpServerError(status, body) {
|
|
374
|
+
return mcpText(`pyx-memory server error (HTTP ${status}): ${body || "no body"}`, true);
|
|
375
|
+
}
|
|
376
|
+
function mcpEnvelopeError(message) {
|
|
377
|
+
return mcpText(message, true);
|
|
378
|
+
}
|
|
379
|
+
function mcpJson(payload) {
|
|
380
|
+
return mcpText(JSON.stringify(payload));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/mcp/http-client.ts
|
|
384
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
385
|
+
function validateIdempotencyKey(key) {
|
|
386
|
+
if (key.length === 0 || !key.isWellFormed() || new TextEncoder().encode(key).byteLength > 256) {
|
|
387
|
+
throw new Error("idempotencyKey must be 1-256 well-formed UTF-8 bytes");
|
|
388
|
+
}
|
|
389
|
+
return key;
|
|
390
|
+
}
|
|
391
|
+
function createHttpClient(credentials, fetchImpl = fetch) {
|
|
392
|
+
const baseUrl = credentials.endpoint.replace(/\/+$/, "");
|
|
393
|
+
function buildUrl(path, query) {
|
|
394
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
395
|
+
if (query) {
|
|
396
|
+
for (const [k, v] of Object.entries(query)) {
|
|
397
|
+
if (v === void 0) continue;
|
|
398
|
+
url.searchParams.set(k, String(v));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return url.toString();
|
|
402
|
+
}
|
|
403
|
+
function buildHeaders(scope, contentType, idempotencyKey) {
|
|
404
|
+
const headers = new Headers();
|
|
405
|
+
headers.set("Authorization", `Bearer ${credentials.apiKey}`);
|
|
406
|
+
headers.set("Accept", "application/json");
|
|
407
|
+
if (contentType) headers.set("Content-Type", contentType);
|
|
408
|
+
if (scope?.tenantId) headers.set("X-Tenant-Id", scope.tenantId);
|
|
409
|
+
if (scope?.namespaceId) headers.set("X-Namespace-Id", scope.namespaceId);
|
|
410
|
+
if (scope?.userId) headers.set("X-User-Id", scope.userId);
|
|
411
|
+
if (scope?.teamId) headers.set("X-Team-Id", scope.teamId);
|
|
412
|
+
if (scope?.callerAccessLevel) headers.set("X-Caller-Access-Level", scope.callerAccessLevel);
|
|
413
|
+
if (idempotencyKey !== void 0) {
|
|
414
|
+
headers.set("Idempotency-Key", validateIdempotencyKey(idempotencyKey));
|
|
415
|
+
}
|
|
416
|
+
return headers;
|
|
417
|
+
}
|
|
418
|
+
async function send(url, method, headers, body) {
|
|
419
|
+
const ctl = new AbortController();
|
|
420
|
+
const timer = setTimeout(() => ctl.abort(), DEFAULT_TIMEOUT_MS);
|
|
421
|
+
let res;
|
|
422
|
+
try {
|
|
423
|
+
res = await fetchImpl(url, { method, headers, body, signal: ctl.signal });
|
|
424
|
+
} catch (err) {
|
|
425
|
+
return {
|
|
426
|
+
ok: false,
|
|
427
|
+
result: mcpUnreachable(baseUrl, err instanceof Error ? err.message : String(err))
|
|
428
|
+
};
|
|
429
|
+
} finally {
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
}
|
|
432
|
+
if (res.status === 401 || res.status === 403) {
|
|
433
|
+
return { ok: false, status: res.status, result: mcpAuthFailed(res.status) };
|
|
434
|
+
}
|
|
435
|
+
const text = await res.text().catch(() => "");
|
|
436
|
+
return parseHttpResponse(res, text);
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
async requestJson(input) {
|
|
440
|
+
const url = buildUrl(input.path, input.query);
|
|
441
|
+
const hasBody = input.body !== void 0;
|
|
442
|
+
const headers = buildHeaders(
|
|
443
|
+
input.scope,
|
|
444
|
+
hasBody ? "application/json" : void 0,
|
|
445
|
+
input.idempotencyKey
|
|
446
|
+
);
|
|
447
|
+
return send(url, input.method, headers, hasBody ? JSON.stringify(input.body) : void 0);
|
|
448
|
+
},
|
|
449
|
+
async requestMultipart(input) {
|
|
450
|
+
const url = buildUrl(input.path);
|
|
451
|
+
const headers = buildHeaders(input.scope, void 0, input.idempotencyKey);
|
|
452
|
+
const form = await input.formData();
|
|
453
|
+
return send(url, "POST", headers, form);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function parseHttpResponse(res, text) {
|
|
458
|
+
if (res.status >= 500) {
|
|
459
|
+
return { ok: false, status: res.status, result: mcpServerError(res.status, text) };
|
|
460
|
+
}
|
|
461
|
+
const parsed = parseJsonText(text);
|
|
462
|
+
if (!parsed.ok) {
|
|
463
|
+
if (!res.ok) {
|
|
464
|
+
return {
|
|
465
|
+
ok: false,
|
|
466
|
+
status: res.status,
|
|
467
|
+
result: mcpHttpError(res.status, text || "no body")
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return { ok: true, status: res.status, data: text };
|
|
471
|
+
}
|
|
472
|
+
if (!res.ok) {
|
|
473
|
+
const message = extractEnvelopeError(parsed.value) ?? text ?? `HTTP ${res.status}`;
|
|
474
|
+
return { ok: false, status: res.status, result: mcpHttpError(res.status, message) };
|
|
475
|
+
}
|
|
476
|
+
const envelopeError = extractEnvelopeError(parsed.value);
|
|
477
|
+
if (envelopeError) {
|
|
478
|
+
return { ok: false, status: res.status, result: mcpEnvelopeError(envelopeError) };
|
|
479
|
+
}
|
|
480
|
+
return { ok: true, status: res.status, data: parsed.value };
|
|
481
|
+
}
|
|
482
|
+
function parseJsonText(text) {
|
|
483
|
+
if (text.length === 0) return { ok: true, value: void 0 };
|
|
484
|
+
try {
|
|
485
|
+
return { ok: true, value: JSON.parse(text) };
|
|
486
|
+
} catch {
|
|
487
|
+
return { ok: false };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function extractEnvelopeError(parsed) {
|
|
491
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
492
|
+
const obj = parsed;
|
|
493
|
+
if (obj.success === false) {
|
|
494
|
+
if (typeof obj.error === "string") return obj.error;
|
|
495
|
+
if (typeof obj.message === "string") return obj.message;
|
|
496
|
+
return "pyx-memory returned success: false";
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/cli/commands/read-surface.ts
|
|
502
|
+
async function readSurface(opts) {
|
|
503
|
+
const provider = opts.keychain ?? getDefaultKeychain();
|
|
504
|
+
let credentials;
|
|
505
|
+
try {
|
|
506
|
+
credentials = await provider.read();
|
|
507
|
+
} catch (error) {
|
|
508
|
+
return {
|
|
509
|
+
ok: false,
|
|
510
|
+
exit: error instanceof KeychainError ? error.exit : EXIT.CRED_UNAVAILABLE,
|
|
511
|
+
state: "error",
|
|
512
|
+
endpoint: null,
|
|
513
|
+
keySource: null,
|
|
514
|
+
problem: error instanceof KeychainError ? [error.message, error.guidance].filter(Boolean).join(" ") : "The OS credential store could not be read."
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (!credentials) {
|
|
518
|
+
return {
|
|
519
|
+
ok: false,
|
|
520
|
+
exit: EXIT.NOT_LOGGED_IN,
|
|
521
|
+
state: "signed_out",
|
|
522
|
+
endpoint: null,
|
|
523
|
+
keySource: null,
|
|
524
|
+
problem: null
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (!isHttpUrl(credentials.endpoint)) {
|
|
528
|
+
return {
|
|
529
|
+
ok: false,
|
|
530
|
+
exit: EXIT.DOCTOR_FAIL,
|
|
531
|
+
state: "error",
|
|
532
|
+
endpoint: null,
|
|
533
|
+
keySource: "keychain",
|
|
534
|
+
problem: "The stored pyx-memory endpoint is invalid. Run: pyx-mem login"
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const http = createHttpClient(credentials, opts.fetchImpl ?? fetch);
|
|
538
|
+
const response = await http.requestJson({
|
|
539
|
+
...opts.request,
|
|
540
|
+
idempotencyKey: `cli-${opts.surface}-${randomUUID2()}`
|
|
541
|
+
});
|
|
542
|
+
if (!response.ok) {
|
|
543
|
+
const signedOut = response.status === 401;
|
|
544
|
+
return {
|
|
545
|
+
ok: false,
|
|
546
|
+
exit: signedOut ? EXIT.NOT_LOGGED_IN : EXIT.DOCTOR_FAIL,
|
|
547
|
+
state: signedOut ? "signed_out" : "error",
|
|
548
|
+
endpoint: credentials.endpoint,
|
|
549
|
+
keySource: "keychain",
|
|
550
|
+
problem: requestFailure(opts.surface, response.status)
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const parsed = opts.schema.safeParse(response.data);
|
|
554
|
+
if (!parsed.success) {
|
|
555
|
+
return {
|
|
556
|
+
ok: false,
|
|
557
|
+
exit: EXIT.DOCTOR_FAIL,
|
|
558
|
+
state: "error",
|
|
559
|
+
endpoint: credentials.endpoint,
|
|
560
|
+
keySource: "keychain",
|
|
561
|
+
problem: `pyx-memory returned an invalid ${opts.surface} response. Run: pyx-mem doctor`
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return { ok: true, endpoint: credentials.endpoint, data: parsed.data };
|
|
565
|
+
}
|
|
566
|
+
function requestFailure(surface, status) {
|
|
567
|
+
if (status === 401) {
|
|
568
|
+
return "Stored pyx-memory credentials were rejected. Run: pyx-mem login";
|
|
569
|
+
}
|
|
570
|
+
if (status === 403) {
|
|
571
|
+
return "pyx-memory denied access (HTTP 403). Check the key scope and project access.";
|
|
572
|
+
}
|
|
573
|
+
if (status === void 0) {
|
|
574
|
+
return "Unable to reach pyx-memory. Run: pyx-mem doctor";
|
|
575
|
+
}
|
|
576
|
+
if (status >= 500) {
|
|
577
|
+
return `pyx-memory is unavailable (HTTP ${status}). Run: pyx-mem doctor`;
|
|
578
|
+
}
|
|
579
|
+
return `pyx-memory ${surface} failed (HTTP ${status}). Run: pyx-mem doctor`;
|
|
580
|
+
}
|
|
581
|
+
function isHttpUrl(value) {
|
|
582
|
+
try {
|
|
583
|
+
const url = new URL(value);
|
|
584
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
585
|
+
} catch {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/cli/commands/insights.ts
|
|
591
|
+
var INSIGHTS_SCHEMA_VERSION = 1;
|
|
592
|
+
var metricsSchema = z.object({
|
|
593
|
+
activeEntries: z.number().int().nonnegative(),
|
|
594
|
+
retainedAdditions7d: z.number().int().nonnegative(),
|
|
595
|
+
appliedRecalls: z.number().int().nonnegative(),
|
|
596
|
+
reinforcedEntries: z.number().int().nonnegative(),
|
|
597
|
+
retrievedEntries: z.number().int().nonnegative(),
|
|
598
|
+
retrievals: z.number().int().nonnegative(),
|
|
599
|
+
lastAppliedAt: z.string().nullable(),
|
|
600
|
+
lastRetrievedAt: z.string().nullable()
|
|
601
|
+
});
|
|
602
|
+
var insightsSchema = z.object({
|
|
603
|
+
generatedAt: z.string(),
|
|
604
|
+
scope: z.literal("connected_memory"),
|
|
605
|
+
coverage: z.object({
|
|
606
|
+
retainedAdditions: z.object({
|
|
607
|
+
state: z.enum(["measured", "partial"]),
|
|
608
|
+
trackingSince: z.string().nullable(),
|
|
609
|
+
trackedEntries: z.number().int().nonnegative(),
|
|
610
|
+
totalEntries: z.number().int().nonnegative(),
|
|
611
|
+
reason: z.string().nullable()
|
|
612
|
+
}),
|
|
613
|
+
projectLabels: z.object({ state: z.literal("measured") }),
|
|
614
|
+
retrievalUsage: z.object({ state: z.literal("partial"), reason: z.string() }),
|
|
615
|
+
tokenEfficiency: z.object({ state: z.literal("not_instrumented"), reason: z.string() }),
|
|
616
|
+
contextAccuracy: z.object({ state: z.literal("not_instrumented"), reason: z.string() })
|
|
617
|
+
}),
|
|
618
|
+
summary: z.object({
|
|
619
|
+
activeEntries: z.number().int().nonnegative(),
|
|
620
|
+
observedProjectLabels: z.number().int().nonnegative(),
|
|
621
|
+
retainedAdditions7d: z.number().int().nonnegative().nullable(),
|
|
622
|
+
trackedEntries: z.number().int().nonnegative(),
|
|
623
|
+
appliedRecalls: z.number().int().nonnegative(),
|
|
624
|
+
reinforcedEntries: z.number().int().nonnegative(),
|
|
625
|
+
retrievedEntries: z.number().int().nonnegative(),
|
|
626
|
+
retrievals: z.number().int().nonnegative()
|
|
627
|
+
}),
|
|
628
|
+
projects: z.array(
|
|
629
|
+
metricsSchema.extend({
|
|
630
|
+
label: z.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS)
|
|
631
|
+
})
|
|
632
|
+
).max(TAXONOMY_MAX_PROJECTS),
|
|
633
|
+
other: metricsSchema.nullable()
|
|
634
|
+
});
|
|
635
|
+
var insightsResponseSchema = z.union([
|
|
636
|
+
insightsSchema,
|
|
637
|
+
z.object({ success: z.literal(true), data: insightsSchema }).transform(({ data }) => data)
|
|
638
|
+
]);
|
|
639
|
+
async function insightsCommand(opts = {}) {
|
|
640
|
+
const result = await readSurface({
|
|
641
|
+
surface: "insights",
|
|
642
|
+
request: { method: "GET", path: "/api/memory/insights" },
|
|
643
|
+
schema: insightsResponseSchema,
|
|
644
|
+
keychain: opts.keychain,
|
|
645
|
+
fetchImpl: opts.fetchImpl
|
|
646
|
+
});
|
|
647
|
+
if (!result.ok) {
|
|
648
|
+
emit(report(result.state, result.endpoint, result.keySource, result.problem));
|
|
649
|
+
return result.exit;
|
|
650
|
+
}
|
|
651
|
+
emit({
|
|
652
|
+
schemaVersion: INSIGHTS_SCHEMA_VERSION,
|
|
653
|
+
state: "ready",
|
|
654
|
+
endpoint: result.endpoint,
|
|
655
|
+
keySource: "keychain",
|
|
656
|
+
...result.data,
|
|
657
|
+
problem: null
|
|
658
|
+
});
|
|
659
|
+
return EXIT.OK;
|
|
660
|
+
}
|
|
661
|
+
function report(state, endpoint, keySource, problem) {
|
|
662
|
+
return {
|
|
663
|
+
schemaVersion: INSIGHTS_SCHEMA_VERSION,
|
|
664
|
+
state,
|
|
665
|
+
endpoint,
|
|
666
|
+
keySource,
|
|
667
|
+
generatedAt: null,
|
|
668
|
+
scope: "connected_memory",
|
|
669
|
+
coverage: null,
|
|
670
|
+
summary: null,
|
|
671
|
+
projects: [],
|
|
672
|
+
other: null,
|
|
673
|
+
problem
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function emit(value) {
|
|
677
|
+
process.stdout.write(`${JSON.stringify(value)}
|
|
678
|
+
`);
|
|
679
|
+
}
|
|
680
|
+
|
|
331
681
|
// src/cli/prompt.ts
|
|
332
682
|
async function readApiKeySecret() {
|
|
333
683
|
if (process.stdin.isTTY) {
|
|
@@ -411,7 +761,7 @@ async function loginCommand(opts = {}) {
|
|
|
411
761
|
const endpoint = normalizeEndpoint(
|
|
412
762
|
opts.endpoint ?? await readExistingEndpoint(provider) ?? DEFAULT_ENDPOINT
|
|
413
763
|
);
|
|
414
|
-
if (!
|
|
764
|
+
if (!isHttpUrl2(endpoint)) {
|
|
415
765
|
process.stderr.write(`Error: invalid --endpoint \`${endpoint}\` (expected http(s) URL).
|
|
416
766
|
`);
|
|
417
767
|
return EXIT.USAGE;
|
|
@@ -473,7 +823,7 @@ async function readExistingEndpoint(provider) {
|
|
|
473
823
|
function normalizeEndpoint(input) {
|
|
474
824
|
return input.replace(/\/+$/, "");
|
|
475
825
|
}
|
|
476
|
-
function
|
|
826
|
+
function isHttpUrl2(value) {
|
|
477
827
|
try {
|
|
478
828
|
const u = new URL(value);
|
|
479
829
|
return u.protocol === "http:" || u.protocol === "https:";
|
|
@@ -500,44 +850,6 @@ ${err.guidance}
|
|
|
500
850
|
}
|
|
501
851
|
}
|
|
502
852
|
|
|
503
|
-
// src/mcp/errors.ts
|
|
504
|
-
var RUN_LOGIN = "Run: pyx-mem login";
|
|
505
|
-
var RUN_DOCTOR = "Run: pyx-mem doctor";
|
|
506
|
-
function mcpText(text, isError = false) {
|
|
507
|
-
return { isError, content: [{ type: "text", text }] };
|
|
508
|
-
}
|
|
509
|
-
function mcpMissingCredentials() {
|
|
510
|
-
return mcpText(`pyx-memory credentials are not configured.
|
|
511
|
-
${RUN_LOGIN}`, true);
|
|
512
|
-
}
|
|
513
|
-
function mcpKeychainError(message, guidance) {
|
|
514
|
-
const lines = ["pyx-memory credentials could not be read from the OS credential store."];
|
|
515
|
-
if (message) lines.push(message);
|
|
516
|
-
if (guidance) lines.push(guidance);
|
|
517
|
-
lines.push(RUN_LOGIN);
|
|
518
|
-
return mcpText(lines.join("\n"), true);
|
|
519
|
-
}
|
|
520
|
-
function mcpAuthFailed(status) {
|
|
521
|
-
return mcpText(`Authentication failed (HTTP ${status}).
|
|
522
|
-
${RUN_LOGIN}`, true);
|
|
523
|
-
}
|
|
524
|
-
function mcpUnreachable(endpoint, reason) {
|
|
525
|
-
return mcpText(`Unable to reach pyx-memory at ${endpoint}: ${reason}.
|
|
526
|
-
${RUN_DOCTOR}`, true);
|
|
527
|
-
}
|
|
528
|
-
function mcpHttpError(status, body) {
|
|
529
|
-
return mcpText(`${status}: ${body}`, true);
|
|
530
|
-
}
|
|
531
|
-
function mcpServerError(status, body) {
|
|
532
|
-
return mcpText(`pyx-memory server error (HTTP ${status}): ${body || "no body"}`, true);
|
|
533
|
-
}
|
|
534
|
-
function mcpEnvelopeError(message) {
|
|
535
|
-
return mcpText(message, true);
|
|
536
|
-
}
|
|
537
|
-
function mcpJson(payload) {
|
|
538
|
-
return mcpText(JSON.stringify(payload));
|
|
539
|
-
}
|
|
540
|
-
|
|
541
853
|
// src/mcp/credentials.ts
|
|
542
854
|
function createReadCredentials(providerFactory) {
|
|
543
855
|
return async () => {
|
|
@@ -557,7 +869,7 @@ function createReadCredentials(providerFactory) {
|
|
|
557
869
|
}
|
|
558
870
|
|
|
559
871
|
// src/mcp/proxy-server.ts
|
|
560
|
-
import { createHash, randomUUID as
|
|
872
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
561
873
|
import { readFile, stat } from "fs/promises";
|
|
562
874
|
import { basename, isAbsolute, resolve } from "path";
|
|
563
875
|
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -571,130 +883,12 @@ import {
|
|
|
571
883
|
ListToolsRequestSchema
|
|
572
884
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
573
885
|
|
|
574
|
-
// src/mcp/http-client.ts
|
|
575
|
-
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
576
|
-
function validateIdempotencyKey(key) {
|
|
577
|
-
if (key.length === 0 || !key.isWellFormed() || new TextEncoder().encode(key).byteLength > 256) {
|
|
578
|
-
throw new Error("idempotencyKey must be 1-256 well-formed UTF-8 bytes");
|
|
579
|
-
}
|
|
580
|
-
return key;
|
|
581
|
-
}
|
|
582
|
-
function createHttpClient(credentials, fetchImpl = fetch) {
|
|
583
|
-
const baseUrl = credentials.endpoint.replace(/\/+$/, "");
|
|
584
|
-
function buildUrl(path, query) {
|
|
585
|
-
const url = new URL(`${baseUrl}${path}`);
|
|
586
|
-
if (query) {
|
|
587
|
-
for (const [k, v] of Object.entries(query)) {
|
|
588
|
-
if (v === void 0) continue;
|
|
589
|
-
url.searchParams.set(k, String(v));
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
return url.toString();
|
|
593
|
-
}
|
|
594
|
-
function buildHeaders(scope, contentType, idempotencyKey) {
|
|
595
|
-
const headers = new Headers();
|
|
596
|
-
headers.set("Authorization", `Bearer ${credentials.apiKey}`);
|
|
597
|
-
headers.set("Accept", "application/json");
|
|
598
|
-
if (contentType) headers.set("Content-Type", contentType);
|
|
599
|
-
if (scope?.tenantId) headers.set("X-Tenant-Id", scope.tenantId);
|
|
600
|
-
if (scope?.namespaceId) headers.set("X-Namespace-Id", scope.namespaceId);
|
|
601
|
-
if (scope?.userId) headers.set("X-User-Id", scope.userId);
|
|
602
|
-
if (scope?.teamId) headers.set("X-Team-Id", scope.teamId);
|
|
603
|
-
if (scope?.callerAccessLevel) headers.set("X-Caller-Access-Level", scope.callerAccessLevel);
|
|
604
|
-
if (idempotencyKey !== void 0) {
|
|
605
|
-
headers.set("Idempotency-Key", validateIdempotencyKey(idempotencyKey));
|
|
606
|
-
}
|
|
607
|
-
return headers;
|
|
608
|
-
}
|
|
609
|
-
async function send(url, method, headers, body) {
|
|
610
|
-
const ctl = new AbortController();
|
|
611
|
-
const timer = setTimeout(() => ctl.abort(), DEFAULT_TIMEOUT_MS);
|
|
612
|
-
let res;
|
|
613
|
-
try {
|
|
614
|
-
res = await fetchImpl(url, { method, headers, body, signal: ctl.signal });
|
|
615
|
-
} catch (err) {
|
|
616
|
-
return {
|
|
617
|
-
ok: false,
|
|
618
|
-
result: mcpUnreachable(baseUrl, err instanceof Error ? err.message : String(err))
|
|
619
|
-
};
|
|
620
|
-
} finally {
|
|
621
|
-
clearTimeout(timer);
|
|
622
|
-
}
|
|
623
|
-
if (res.status === 401 || res.status === 403) {
|
|
624
|
-
return { ok: false, status: res.status, result: mcpAuthFailed(res.status) };
|
|
625
|
-
}
|
|
626
|
-
const text = await res.text().catch(() => "");
|
|
627
|
-
return parseHttpResponse(res, text);
|
|
628
|
-
}
|
|
629
|
-
return {
|
|
630
|
-
async requestJson(input) {
|
|
631
|
-
const url = buildUrl(input.path, input.query);
|
|
632
|
-
const hasBody = input.body !== void 0;
|
|
633
|
-
const headers = buildHeaders(
|
|
634
|
-
input.scope,
|
|
635
|
-
hasBody ? "application/json" : void 0,
|
|
636
|
-
input.idempotencyKey
|
|
637
|
-
);
|
|
638
|
-
return send(url, input.method, headers, hasBody ? JSON.stringify(input.body) : void 0);
|
|
639
|
-
},
|
|
640
|
-
async requestMultipart(input) {
|
|
641
|
-
const url = buildUrl(input.path);
|
|
642
|
-
const headers = buildHeaders(input.scope, void 0, input.idempotencyKey);
|
|
643
|
-
const form = await input.formData();
|
|
644
|
-
return send(url, "POST", headers, form);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
function parseHttpResponse(res, text) {
|
|
649
|
-
if (res.status >= 500) {
|
|
650
|
-
return { ok: false, status: res.status, result: mcpServerError(res.status, text) };
|
|
651
|
-
}
|
|
652
|
-
const parsed = parseJsonText(text);
|
|
653
|
-
if (!parsed.ok) {
|
|
654
|
-
if (!res.ok) {
|
|
655
|
-
return {
|
|
656
|
-
ok: false,
|
|
657
|
-
status: res.status,
|
|
658
|
-
result: mcpHttpError(res.status, text || "no body")
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
return { ok: true, status: res.status, data: text };
|
|
662
|
-
}
|
|
663
|
-
if (!res.ok) {
|
|
664
|
-
const message = extractEnvelopeError(parsed.value) ?? text ?? `HTTP ${res.status}`;
|
|
665
|
-
return { ok: false, status: res.status, result: mcpHttpError(res.status, message) };
|
|
666
|
-
}
|
|
667
|
-
const envelopeError = extractEnvelopeError(parsed.value);
|
|
668
|
-
if (envelopeError) {
|
|
669
|
-
return { ok: false, status: res.status, result: mcpEnvelopeError(envelopeError) };
|
|
670
|
-
}
|
|
671
|
-
return { ok: true, status: res.status, data: parsed.value };
|
|
672
|
-
}
|
|
673
|
-
function parseJsonText(text) {
|
|
674
|
-
if (text.length === 0) return { ok: true, value: void 0 };
|
|
675
|
-
try {
|
|
676
|
-
return { ok: true, value: JSON.parse(text) };
|
|
677
|
-
} catch {
|
|
678
|
-
return { ok: false };
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
function extractEnvelopeError(parsed) {
|
|
682
|
-
if (parsed === null || typeof parsed !== "object") return null;
|
|
683
|
-
const obj = parsed;
|
|
684
|
-
if (obj.success === false) {
|
|
685
|
-
if (typeof obj.error === "string") return obj.error;
|
|
686
|
-
if (typeof obj.message === "string") return obj.message;
|
|
687
|
-
return "pyx-memory returned success: false";
|
|
688
|
-
}
|
|
689
|
-
return null;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
886
|
// src/mcp/tools/shared.ts
|
|
693
887
|
var LOCAL_UPLOAD_META_KEY = "pyx.dev/local-upload";
|
|
694
888
|
|
|
695
889
|
// src/mcp/proxy-server.ts
|
|
696
890
|
var REMOTE_MCP_PATH = "/mcp";
|
|
697
|
-
function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce =
|
|
891
|
+
function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID3()) {
|
|
698
892
|
const wrapped = async (input, init) => {
|
|
699
893
|
const upstream = input instanceof Request ? input : new Request(String(input), init);
|
|
700
894
|
const method = init?.method ?? upstream.method;
|
|
@@ -836,7 +1030,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
836
1030
|
return server;
|
|
837
1031
|
}
|
|
838
1032
|
async function runMcpProxyServer(opts) {
|
|
839
|
-
const version = opts.version ?? (true ? "1.
|
|
1033
|
+
const version = opts.version ?? (true ? "1.18.0" : "0.0.0-dev");
|
|
840
1034
|
const read = await opts.readCredentials();
|
|
841
1035
|
if (!read.ok) {
|
|
842
1036
|
const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
|
|
@@ -1541,28 +1735,27 @@ function resolveMcpMode(flags) {
|
|
|
1541
1735
|
}
|
|
1542
1736
|
|
|
1543
1737
|
// src/cli/commands/overview.ts
|
|
1544
|
-
import {
|
|
1545
|
-
import { z } from "zod";
|
|
1738
|
+
import { z as z2 } from "zod";
|
|
1546
1739
|
var OVERVIEW_SCHEMA_VERSION = 1;
|
|
1547
1740
|
var RECENT_ENTRY_LIMIT = 3;
|
|
1548
1741
|
var MAX_TOPIC_CHARS = 160;
|
|
1549
1742
|
var MAX_PROJECT_CHARS = 80;
|
|
1550
|
-
var entrySchema =
|
|
1551
|
-
id:
|
|
1552
|
-
metadata:
|
|
1743
|
+
var entrySchema = z2.object({
|
|
1744
|
+
id: z2.string().min(1),
|
|
1745
|
+
metadata: z2.record(z2.string(), z2.unknown())
|
|
1553
1746
|
}).passthrough();
|
|
1554
|
-
var hostedEntriesResponseSchema =
|
|
1555
|
-
memories:
|
|
1556
|
-
total:
|
|
1747
|
+
var hostedEntriesResponseSchema = z2.object({
|
|
1748
|
+
memories: z2.array(entrySchema),
|
|
1749
|
+
total: z2.number().int().nonnegative()
|
|
1557
1750
|
}).passthrough();
|
|
1558
|
-
var selfHostedEntriesResponseSchema =
|
|
1559
|
-
success:
|
|
1560
|
-
data:
|
|
1561
|
-
entries:
|
|
1562
|
-
totalCount:
|
|
1751
|
+
var selfHostedEntriesResponseSchema = z2.object({
|
|
1752
|
+
success: z2.literal(true),
|
|
1753
|
+
data: z2.object({
|
|
1754
|
+
entries: z2.array(entrySchema),
|
|
1755
|
+
totalCount: z2.number().int().nonnegative()
|
|
1563
1756
|
}).passthrough()
|
|
1564
1757
|
}).passthrough();
|
|
1565
|
-
var entriesResponseSchema =
|
|
1758
|
+
var entriesResponseSchema = z2.union([
|
|
1566
1759
|
hostedEntriesResponseSchema.transform(({ memories, total }) => ({
|
|
1567
1760
|
entries: memories,
|
|
1568
1761
|
totalCount: total
|
|
@@ -1570,65 +1763,25 @@ var entriesResponseSchema = z.union([
|
|
|
1570
1763
|
selfHostedEntriesResponseSchema.transform(({ data }) => data)
|
|
1571
1764
|
]);
|
|
1572
1765
|
async function overviewCommand(opts = {}) {
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
emit(report("signed_out", null, null, null));
|
|
1584
|
-
return EXIT.NOT_LOGGED_IN;
|
|
1585
|
-
}
|
|
1586
|
-
if (!isHttpUrl2(credentials.endpoint)) {
|
|
1587
|
-
emit(
|
|
1588
|
-
report(
|
|
1589
|
-
"error",
|
|
1590
|
-
null,
|
|
1591
|
-
"keychain",
|
|
1592
|
-
"The stored pyx-memory endpoint is invalid. Run: pyx-mem login"
|
|
1593
|
-
)
|
|
1594
|
-
);
|
|
1595
|
-
return EXIT.DOCTOR_FAIL;
|
|
1596
|
-
}
|
|
1597
|
-
const http = createHttpClient(credentials, opts.fetchImpl ?? fetch);
|
|
1598
|
-
const response = await http.requestJson({
|
|
1599
|
-
method: "GET",
|
|
1600
|
-
path: "/api/memory/entries",
|
|
1601
|
-
query: { status: "active", limit: RECENT_ENTRY_LIMIT },
|
|
1602
|
-
idempotencyKey: `cli-overview-${randomUUID3()}`
|
|
1766
|
+
const result = await readSurface({
|
|
1767
|
+
surface: "overview",
|
|
1768
|
+
request: {
|
|
1769
|
+
method: "GET",
|
|
1770
|
+
path: "/api/memory/entries",
|
|
1771
|
+
query: { status: "active", limit: RECENT_ENTRY_LIMIT }
|
|
1772
|
+
},
|
|
1773
|
+
schema: entriesResponseSchema,
|
|
1774
|
+
keychain: opts.keychain,
|
|
1775
|
+
fetchImpl: opts.fetchImpl
|
|
1603
1776
|
});
|
|
1604
|
-
if (!
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
)
|
|
1613
|
-
);
|
|
1614
|
-
return signedOut ? EXIT.NOT_LOGGED_IN : EXIT.DOCTOR_FAIL;
|
|
1615
|
-
}
|
|
1616
|
-
const parsed = entriesResponseSchema.safeParse(response.data);
|
|
1617
|
-
if (!parsed.success) {
|
|
1618
|
-
emit(
|
|
1619
|
-
report(
|
|
1620
|
-
"error",
|
|
1621
|
-
credentials.endpoint,
|
|
1622
|
-
"keychain",
|
|
1623
|
-
"pyx-memory returned an invalid overview response. Run: pyx-mem doctor"
|
|
1624
|
-
)
|
|
1625
|
-
);
|
|
1626
|
-
return EXIT.DOCTOR_FAIL;
|
|
1627
|
-
}
|
|
1628
|
-
emit({
|
|
1629
|
-
...report("ready", credentials.endpoint, "keychain", null),
|
|
1630
|
-
totalCount: parsed.data.totalCount,
|
|
1631
|
-
entries: parsed.data.entries.slice(0, RECENT_ENTRY_LIMIT).map((entry) => ({
|
|
1777
|
+
if (!result.ok) {
|
|
1778
|
+
emit2(report2(result.state, result.endpoint, result.keySource, result.problem));
|
|
1779
|
+
return result.exit;
|
|
1780
|
+
}
|
|
1781
|
+
emit2({
|
|
1782
|
+
...report2("ready", result.endpoint, "keychain", null),
|
|
1783
|
+
totalCount: result.data.totalCount,
|
|
1784
|
+
entries: result.data.entries.slice(0, RECENT_ENTRY_LIMIT).map((entry) => ({
|
|
1632
1785
|
id: entry.id,
|
|
1633
1786
|
topic: boundedLabel(entry.metadata.topic, "Untitled", MAX_TOPIC_CHARS),
|
|
1634
1787
|
project: boundedLabel(entry.metadata.project, "Unscoped", MAX_PROJECT_CHARS)
|
|
@@ -1636,7 +1789,7 @@ async function overviewCommand(opts = {}) {
|
|
|
1636
1789
|
});
|
|
1637
1790
|
return EXIT.OK;
|
|
1638
1791
|
}
|
|
1639
|
-
function
|
|
1792
|
+
function report2(state, endpoint, keySource, problem) {
|
|
1640
1793
|
return {
|
|
1641
1794
|
schemaVersion: OVERVIEW_SCHEMA_VERSION,
|
|
1642
1795
|
state,
|
|
@@ -1647,39 +1800,16 @@ function report(state, endpoint, keySource, problem) {
|
|
|
1647
1800
|
problem
|
|
1648
1801
|
};
|
|
1649
1802
|
}
|
|
1650
|
-
function
|
|
1803
|
+
function emit2(value) {
|
|
1651
1804
|
process.stdout.write(`${JSON.stringify(value)}
|
|
1652
1805
|
`);
|
|
1653
1806
|
}
|
|
1654
|
-
function overviewFailure(status) {
|
|
1655
|
-
if (status === 401) {
|
|
1656
|
-
return "Stored pyx-memory credentials were rejected. Run: pyx-mem login";
|
|
1657
|
-
}
|
|
1658
|
-
if (status === 403) {
|
|
1659
|
-
return "pyx-memory denied access (HTTP 403). Check the key scope and project access.";
|
|
1660
|
-
}
|
|
1661
|
-
if (status === void 0) {
|
|
1662
|
-
return "Unable to reach pyx-memory. Run: pyx-mem doctor";
|
|
1663
|
-
}
|
|
1664
|
-
if (status >= 500) {
|
|
1665
|
-
return `pyx-memory is unavailable (HTTP ${status}). Run: pyx-mem doctor`;
|
|
1666
|
-
}
|
|
1667
|
-
return `pyx-memory overview failed (HTTP ${status}). Run: pyx-mem doctor`;
|
|
1668
|
-
}
|
|
1669
1807
|
function boundedLabel(value, fallback, maxChars) {
|
|
1670
1808
|
if (typeof value !== "string" || value.trim().length === 0) return fallback;
|
|
1671
1809
|
const normalized = value.trim();
|
|
1672
1810
|
const chars = [...normalized];
|
|
1673
1811
|
return chars.length > maxChars ? `${chars.slice(0, maxChars).join("")}\u2026` : normalized;
|
|
1674
1812
|
}
|
|
1675
|
-
function isHttpUrl2(value) {
|
|
1676
|
-
try {
|
|
1677
|
-
const url = new URL(value);
|
|
1678
|
-
return url.protocol === "http:" || url.protocol === "https:";
|
|
1679
|
-
} catch {
|
|
1680
|
-
return false;
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
1813
|
|
|
1684
1814
|
// src/cli/commands/scaffold.ts
|
|
1685
1815
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -1861,10 +1991,10 @@ function scaffoldCommand(args = {}) {
|
|
|
1861
1991
|
// src/cli/commands/status.ts
|
|
1862
1992
|
async function statusCommand(opts = {}) {
|
|
1863
1993
|
const provider = opts.keychain ?? getDefaultKeychain();
|
|
1864
|
-
let
|
|
1994
|
+
let report4;
|
|
1865
1995
|
try {
|
|
1866
1996
|
const creds = await provider.read();
|
|
1867
|
-
|
|
1997
|
+
report4 = {
|
|
1868
1998
|
endpoint: creds?.endpoint ?? null,
|
|
1869
1999
|
keyPresent: creds !== null,
|
|
1870
2000
|
keySource: creds !== null ? "keychain" : null,
|
|
@@ -1875,17 +2005,17 @@ async function statusCommand(opts = {}) {
|
|
|
1875
2005
|
throw err;
|
|
1876
2006
|
}
|
|
1877
2007
|
if (opts.json) {
|
|
1878
|
-
process.stdout.write(`${JSON.stringify(
|
|
2008
|
+
process.stdout.write(`${JSON.stringify(report4)}
|
|
1879
2009
|
`);
|
|
1880
2010
|
} else {
|
|
1881
2011
|
const lines = [
|
|
1882
|
-
`endpoint: ${
|
|
1883
|
-
`credentials: ${
|
|
2012
|
+
`endpoint: ${report4.endpoint ?? "(not set)"}`,
|
|
2013
|
+
`credentials: ${report4.loggedIn ? "present (keychain)" : "not logged in \u2014 run: pyx-mem login"}`
|
|
1884
2014
|
];
|
|
1885
2015
|
process.stdout.write(`${lines.join("\n")}
|
|
1886
2016
|
`);
|
|
1887
2017
|
}
|
|
1888
|
-
return
|
|
2018
|
+
return report4.loggedIn ? EXIT.OK : EXIT.NOT_LOGGED_IN;
|
|
1889
2019
|
}
|
|
1890
2020
|
function emitError(err, json) {
|
|
1891
2021
|
if (json) {
|
|
@@ -1901,6 +2031,76 @@ ${err.guidance}
|
|
|
1901
2031
|
return err.exit;
|
|
1902
2032
|
}
|
|
1903
2033
|
|
|
2034
|
+
// src/cli/commands/taxonomy.ts
|
|
2035
|
+
import { z as z3 } from "zod";
|
|
2036
|
+
var TAXONOMY_SCHEMA_VERSION = 1;
|
|
2037
|
+
var taxonomyStateSchema = z3.object({
|
|
2038
|
+
clusters: z3.array(
|
|
2039
|
+
z3.object({
|
|
2040
|
+
size: z3.number().int().nonnegative(),
|
|
2041
|
+
currentName: z3.string().nullable(),
|
|
2042
|
+
categoryNodeId: z3.string().nullable(),
|
|
2043
|
+
projects: z3.array(
|
|
2044
|
+
z3.object({
|
|
2045
|
+
name: z3.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS),
|
|
2046
|
+
memories: z3.number().int().positive()
|
|
2047
|
+
})
|
|
2048
|
+
).max(TAXONOMY_MAX_PROJECTS),
|
|
2049
|
+
sampleTopics: z3.array(z3.string()).max(TAXONOMY_MAX_SAMPLE_TOPICS),
|
|
2050
|
+
topEntities: z3.array(
|
|
2051
|
+
z3.object({
|
|
2052
|
+
name: z3.string(),
|
|
2053
|
+
type: z3.string(),
|
|
2054
|
+
degree: z3.number().int().nonnegative()
|
|
2055
|
+
})
|
|
2056
|
+
).max(TAXONOMY_MAX_TOP_ENTITIES)
|
|
2057
|
+
})
|
|
2058
|
+
).max(TAXONOMY_MAX_CATEGORIES),
|
|
2059
|
+
totals: z3.object({
|
|
2060
|
+
clusters: z3.number().int().nonnegative(),
|
|
2061
|
+
unnamed: z3.number().int().nonnegative(),
|
|
2062
|
+
nodesConsidered: z3.number().int().nonnegative()
|
|
2063
|
+
})
|
|
2064
|
+
});
|
|
2065
|
+
var taxonomyResponseSchema = z3.union([
|
|
2066
|
+
taxonomyStateSchema,
|
|
2067
|
+
z3.object({ success: z3.literal(true), data: taxonomyStateSchema }).transform(({ data }) => data)
|
|
2068
|
+
]);
|
|
2069
|
+
async function taxonomyCommand(opts = {}) {
|
|
2070
|
+
const result = await readSurface({
|
|
2071
|
+
surface: "taxonomy",
|
|
2072
|
+
request: { method: "GET", path: "/api/memory/graph/taxonomy" },
|
|
2073
|
+
schema: taxonomyResponseSchema,
|
|
2074
|
+
keychain: opts.keychain,
|
|
2075
|
+
fetchImpl: opts.fetchImpl
|
|
2076
|
+
});
|
|
2077
|
+
if (!result.ok) {
|
|
2078
|
+
emit3(report3(result.state, result.endpoint, result.keySource, result.problem));
|
|
2079
|
+
return result.exit;
|
|
2080
|
+
}
|
|
2081
|
+
emit3({
|
|
2082
|
+
...report3("ready", result.endpoint, "keychain", null),
|
|
2083
|
+
clusters: result.data.clusters,
|
|
2084
|
+
totals: result.data.totals
|
|
2085
|
+
});
|
|
2086
|
+
return EXIT.OK;
|
|
2087
|
+
}
|
|
2088
|
+
function report3(state, endpoint, keySource, problem) {
|
|
2089
|
+
return {
|
|
2090
|
+
schemaVersion: TAXONOMY_SCHEMA_VERSION,
|
|
2091
|
+
state,
|
|
2092
|
+
endpoint,
|
|
2093
|
+
keySource,
|
|
2094
|
+
clusters: [],
|
|
2095
|
+
totals: { clusters: 0, unnamed: 0, nodesConsidered: 0 },
|
|
2096
|
+
problem
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
function emit3(value) {
|
|
2100
|
+
process.stdout.write(`${JSON.stringify(value)}
|
|
2101
|
+
`);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
1904
2104
|
// src/cli/help.ts
|
|
1905
2105
|
var HELP_TEXT = `pyx-mem \u2014 pyx-memory CLI
|
|
1906
2106
|
|
|
@@ -1911,6 +2111,8 @@ Commands:
|
|
|
1911
2111
|
login [--endpoint <url>] [--api-key <key>] Store endpoint and API key in OS credential store.
|
|
1912
2112
|
status [--json] Show endpoint, key presence, MCP config status.
|
|
1913
2113
|
overview --json Read memory count and three recent safe labels.
|
|
2114
|
+
insights --json Read body-free memory usage and coverage aggregates.
|
|
2115
|
+
taxonomy --json Read knowledge-graph cluster composition (read-only).
|
|
1914
2116
|
logout Delete stored pyx-memory credentials.
|
|
1915
2117
|
doctor [--json] Diagnose keychain, credentials, backend, MCP startup.
|
|
1916
2118
|
scaffold [--name <dir>] Generate Docker, env, SDK, and memory design-guide starter files.
|
|
@@ -2063,6 +2265,18 @@ async function main() {
|
|
|
2063
2265
|
return EXIT.USAGE;
|
|
2064
2266
|
}
|
|
2065
2267
|
return overviewCommand();
|
|
2268
|
+
case "insights":
|
|
2269
|
+
if (parsed.flags.json !== true) {
|
|
2270
|
+
process.stderr.write("Error: `pyx-mem insights` requires --json.\n");
|
|
2271
|
+
return EXIT.USAGE;
|
|
2272
|
+
}
|
|
2273
|
+
return insightsCommand();
|
|
2274
|
+
case "taxonomy":
|
|
2275
|
+
if (parsed.flags.json !== true) {
|
|
2276
|
+
process.stderr.write("Error: `pyx-mem taxonomy` requires --json.\n");
|
|
2277
|
+
return EXIT.USAGE;
|
|
2278
|
+
}
|
|
2279
|
+
return taxonomyCommand();
|
|
2066
2280
|
case "logout":
|
|
2067
2281
|
return logoutCommand();
|
|
2068
2282
|
case "doctor":
|