@pyxmate/memory 1.17.19 → 1.18.1

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.
@@ -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-VORP6NHJ.mjs";
6
14
 
7
15
  // src/cli/exit-codes.ts
8
16
  var EXIT = {
@@ -328,6 +336,396 @@ 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
+ return failedSurfaceRequest(
544
+ opts.surface,
545
+ response.status,
546
+ credentials.endpoint,
547
+ opts.minimumServiceVersion,
548
+ http
549
+ );
550
+ }
551
+ const parsed = opts.schema.safeParse(response.data);
552
+ if (!parsed.success) {
553
+ return {
554
+ ok: false,
555
+ exit: EXIT.DOCTOR_FAIL,
556
+ state: "error",
557
+ endpoint: credentials.endpoint,
558
+ keySource: "keychain",
559
+ problem: `pyx-memory returned an invalid ${opts.surface} response. Run: pyx-mem doctor`
560
+ };
561
+ }
562
+ return { ok: true, endpoint: credentials.endpoint, data: parsed.data };
563
+ }
564
+ async function failedSurfaceRequest(surface, status, endpoint, minimumServiceVersion, http) {
565
+ if (status === 401) {
566
+ return {
567
+ ok: false,
568
+ exit: EXIT.NOT_LOGGED_IN,
569
+ state: "signed_out",
570
+ endpoint,
571
+ keySource: "keychain",
572
+ problem: requestFailure(surface, status)
573
+ };
574
+ }
575
+ const serviceVersion = minimumServiceVersion ? await probeServiceVersion(http) : null;
576
+ const serviceOutdated = serviceVersion !== null && minimumServiceVersion !== void 0 && isVersionOlder(serviceVersion, minimumServiceVersion);
577
+ return {
578
+ ok: false,
579
+ exit: EXIT.DOCTOR_FAIL,
580
+ state: serviceOutdated ? "service_outdated" : "error",
581
+ endpoint,
582
+ keySource: "keychain",
583
+ problem: serviceOutdated ? `pyx-memory ${surface} requires service ${minimumServiceVersion} or newer; the connected service reports ${serviceVersion}. Update the hosted pyx-memory service or dashboard integration, then retry.` : requestFailure(surface, status)
584
+ };
585
+ }
586
+ async function probeServiceVersion(http) {
587
+ const response = await http.requestJson({ method: "GET", path: "/status" });
588
+ if (!response.ok) return null;
589
+ const payload = plainObject(response.data);
590
+ const topology = plainObject(payload?.success === true ? plainObject(payload.data) : payload);
591
+ const version = payload?.version ?? plainObject(topology?.service)?.version;
592
+ return typeof version === "string" && parseVersion(version) ? version : null;
593
+ }
594
+ function plainObject(value) {
595
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
596
+ }
597
+ function isVersionOlder(actual, minimum) {
598
+ const left = parseVersion(actual);
599
+ const right = parseVersion(minimum);
600
+ if (!left || !right) return false;
601
+ if (left.core[0] !== right.core[0]) return left.core[0] < right.core[0];
602
+ if (left.core[1] !== right.core[1]) return left.core[1] < right.core[1];
603
+ if (left.core[2] !== right.core[2]) return left.core[2] < right.core[2];
604
+ return left.prerelease !== null && right.prerelease === null;
605
+ }
606
+ function parseVersion(value) {
607
+ if (value.length > 64) return null;
608
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
609
+ if (!match) return null;
610
+ const core = [Number(match[1]), Number(match[2]), Number(match[3])];
611
+ return core.every(Number.isSafeInteger) ? { core, prerelease: match[4] ?? null } : null;
612
+ }
613
+ function requestFailure(surface, status) {
614
+ if (status === 401) {
615
+ return "Stored pyx-memory credentials were rejected. Run: pyx-mem login";
616
+ }
617
+ if (status === 403) {
618
+ return "pyx-memory denied access (HTTP 403). Check the key scope and project access.";
619
+ }
620
+ if (status === void 0) {
621
+ return "Unable to reach pyx-memory. Run: pyx-mem doctor";
622
+ }
623
+ if (status >= 500) {
624
+ return `pyx-memory is unavailable (HTTP ${status}). Run: pyx-mem doctor`;
625
+ }
626
+ return `pyx-memory ${surface} failed (HTTP ${status}). Run: pyx-mem doctor`;
627
+ }
628
+ function isHttpUrl(value) {
629
+ try {
630
+ const url = new URL(value);
631
+ return url.protocol === "http:" || url.protocol === "https:";
632
+ } catch {
633
+ return false;
634
+ }
635
+ }
636
+
637
+ // src/cli/commands/insights.ts
638
+ var INSIGHTS_SCHEMA_VERSION = 1;
639
+ var metricsSchema = z.object({
640
+ activeEntries: z.number().int().nonnegative(),
641
+ retainedAdditions7d: z.number().int().nonnegative(),
642
+ appliedRecalls: z.number().int().nonnegative(),
643
+ reinforcedEntries: z.number().int().nonnegative(),
644
+ retrievedEntries: z.number().int().nonnegative(),
645
+ retrievals: z.number().int().nonnegative(),
646
+ lastAppliedAt: z.string().nullable(),
647
+ lastRetrievedAt: z.string().nullable()
648
+ });
649
+ var insightsSchema = z.object({
650
+ generatedAt: z.string(),
651
+ scope: z.literal("connected_memory"),
652
+ coverage: z.object({
653
+ retainedAdditions: z.object({
654
+ state: z.enum(["measured", "partial"]),
655
+ trackingSince: z.string().nullable(),
656
+ trackedEntries: z.number().int().nonnegative(),
657
+ totalEntries: z.number().int().nonnegative(),
658
+ reason: z.string().nullable()
659
+ }),
660
+ projectLabels: z.object({ state: z.literal("measured") }),
661
+ retrievalUsage: z.object({ state: z.literal("partial"), reason: z.string() }),
662
+ tokenEfficiency: z.object({ state: z.literal("not_instrumented"), reason: z.string() }),
663
+ contextAccuracy: z.object({ state: z.literal("not_instrumented"), reason: z.string() })
664
+ }),
665
+ summary: z.object({
666
+ activeEntries: z.number().int().nonnegative(),
667
+ observedProjectLabels: z.number().int().nonnegative(),
668
+ retainedAdditions7d: z.number().int().nonnegative().nullable(),
669
+ trackedEntries: z.number().int().nonnegative(),
670
+ appliedRecalls: z.number().int().nonnegative(),
671
+ reinforcedEntries: z.number().int().nonnegative(),
672
+ retrievedEntries: z.number().int().nonnegative(),
673
+ retrievals: z.number().int().nonnegative()
674
+ }),
675
+ projects: z.array(
676
+ metricsSchema.extend({
677
+ label: z.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS)
678
+ })
679
+ ).max(TAXONOMY_MAX_PROJECTS),
680
+ other: metricsSchema.nullable()
681
+ });
682
+ var insightsResponseSchema = z.union([
683
+ insightsSchema,
684
+ z.object({ success: z.literal(true), data: insightsSchema }).transform(({ data }) => data)
685
+ ]);
686
+ async function insightsCommand(opts = {}) {
687
+ const result = await readSurface({
688
+ surface: "insights",
689
+ request: { method: "GET", path: "/api/memory/insights" },
690
+ schema: insightsResponseSchema,
691
+ minimumServiceVersion: "1.18.1",
692
+ keychain: opts.keychain,
693
+ fetchImpl: opts.fetchImpl
694
+ });
695
+ if (!result.ok) {
696
+ emit(report(result.state, result.endpoint, result.keySource, result.problem));
697
+ return result.exit;
698
+ }
699
+ emit({
700
+ schemaVersion: INSIGHTS_SCHEMA_VERSION,
701
+ state: "ready",
702
+ endpoint: result.endpoint,
703
+ keySource: "keychain",
704
+ ...result.data,
705
+ problem: null
706
+ });
707
+ return EXIT.OK;
708
+ }
709
+ function report(state, endpoint, keySource, problem) {
710
+ return {
711
+ schemaVersion: INSIGHTS_SCHEMA_VERSION,
712
+ state,
713
+ endpoint,
714
+ keySource,
715
+ generatedAt: null,
716
+ scope: "connected_memory",
717
+ coverage: null,
718
+ summary: null,
719
+ projects: [],
720
+ other: null,
721
+ problem
722
+ };
723
+ }
724
+ function emit(value) {
725
+ process.stdout.write(`${JSON.stringify(value)}
726
+ `);
727
+ }
728
+
331
729
  // src/cli/prompt.ts
332
730
  async function readApiKeySecret() {
333
731
  if (process.stdin.isTTY) {
@@ -411,7 +809,7 @@ async function loginCommand(opts = {}) {
411
809
  const endpoint = normalizeEndpoint(
412
810
  opts.endpoint ?? await readExistingEndpoint(provider) ?? DEFAULT_ENDPOINT
413
811
  );
414
- if (!isHttpUrl(endpoint)) {
812
+ if (!isHttpUrl2(endpoint)) {
415
813
  process.stderr.write(`Error: invalid --endpoint \`${endpoint}\` (expected http(s) URL).
416
814
  `);
417
815
  return EXIT.USAGE;
@@ -473,7 +871,7 @@ async function readExistingEndpoint(provider) {
473
871
  function normalizeEndpoint(input) {
474
872
  return input.replace(/\/+$/, "");
475
873
  }
476
- function isHttpUrl(value) {
874
+ function isHttpUrl2(value) {
477
875
  try {
478
876
  const u = new URL(value);
479
877
  return u.protocol === "http:" || u.protocol === "https:";
@@ -500,44 +898,6 @@ ${err.guidance}
500
898
  }
501
899
  }
502
900
 
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
901
  // src/mcp/credentials.ts
542
902
  function createReadCredentials(providerFactory) {
543
903
  return async () => {
@@ -557,7 +917,7 @@ function createReadCredentials(providerFactory) {
557
917
  }
558
918
 
559
919
  // src/mcp/proxy-server.ts
560
- import { createHash, randomUUID as randomUUID2 } from "crypto";
920
+ import { createHash, randomUUID as randomUUID3 } from "crypto";
561
921
  import { readFile, stat } from "fs/promises";
562
922
  import { basename, isAbsolute, resolve } from "path";
563
923
  import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
@@ -571,130 +931,12 @@ import {
571
931
  ListToolsRequestSchema
572
932
  } from "@modelcontextprotocol/sdk/types.js";
573
933
 
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
934
  // src/mcp/tools/shared.ts
693
935
  var LOCAL_UPLOAD_META_KEY = "pyx.dev/local-upload";
694
936
 
695
937
  // src/mcp/proxy-server.ts
696
938
  var REMOTE_MCP_PATH = "/mcp";
697
- function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID2()) {
939
+ function createMcpIdempotentFetch(fetchImpl = fetch, sessionNonce = randomUUID3()) {
698
940
  const wrapped = async (input, init) => {
699
941
  const upstream = input instanceof Request ? input : new Request(String(input), init);
700
942
  const method = init?.method ?? upstream.method;
@@ -836,7 +1078,7 @@ function createProxyServer(client, version, uploadLocalFile) {
836
1078
  return server;
837
1079
  }
838
1080
  async function runMcpProxyServer(opts) {
839
- const version = opts.version ?? (true ? "1.17.19" : "0.0.0-dev");
1081
+ const version = opts.version ?? (true ? "1.18.1" : "0.0.0-dev");
840
1082
  const read = await opts.readCredentials();
841
1083
  if (!read.ok) {
842
1084
  const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
@@ -1541,28 +1783,27 @@ function resolveMcpMode(flags) {
1541
1783
  }
1542
1784
 
1543
1785
  // src/cli/commands/overview.ts
1544
- import { randomUUID as randomUUID3 } from "crypto";
1545
- import { z } from "zod";
1786
+ import { z as z2 } from "zod";
1546
1787
  var OVERVIEW_SCHEMA_VERSION = 1;
1547
1788
  var RECENT_ENTRY_LIMIT = 3;
1548
1789
  var MAX_TOPIC_CHARS = 160;
1549
1790
  var MAX_PROJECT_CHARS = 80;
1550
- var entrySchema = z.object({
1551
- id: z.string().min(1),
1552
- metadata: z.record(z.string(), z.unknown())
1791
+ var entrySchema = z2.object({
1792
+ id: z2.string().min(1),
1793
+ metadata: z2.record(z2.string(), z2.unknown())
1553
1794
  }).passthrough();
1554
- var hostedEntriesResponseSchema = z.object({
1555
- memories: z.array(entrySchema),
1556
- total: z.number().int().nonnegative()
1795
+ var hostedEntriesResponseSchema = z2.object({
1796
+ memories: z2.array(entrySchema),
1797
+ total: z2.number().int().nonnegative()
1557
1798
  }).passthrough();
1558
- var selfHostedEntriesResponseSchema = z.object({
1559
- success: z.literal(true),
1560
- data: z.object({
1561
- entries: z.array(entrySchema),
1562
- totalCount: z.number().int().nonnegative()
1799
+ var selfHostedEntriesResponseSchema = z2.object({
1800
+ success: z2.literal(true),
1801
+ data: z2.object({
1802
+ entries: z2.array(entrySchema),
1803
+ totalCount: z2.number().int().nonnegative()
1563
1804
  }).passthrough()
1564
1805
  }).passthrough();
1565
- var entriesResponseSchema = z.union([
1806
+ var entriesResponseSchema = z2.union([
1566
1807
  hostedEntriesResponseSchema.transform(({ memories, total }) => ({
1567
1808
  entries: memories,
1568
1809
  totalCount: total
@@ -1570,65 +1811,25 @@ var entriesResponseSchema = z.union([
1570
1811
  selfHostedEntriesResponseSchema.transform(({ data }) => data)
1571
1812
  ]);
1572
1813
  async function overviewCommand(opts = {}) {
1573
- const provider = opts.keychain ?? getDefaultKeychain();
1574
- let credentials;
1575
- try {
1576
- credentials = await provider.read();
1577
- } catch (error) {
1578
- const detail = error instanceof KeychainError ? [error.message, error.guidance].filter(Boolean).join(" ") : "The OS credential store could not be read.";
1579
- emit(report("error", null, null, detail));
1580
- return error instanceof KeychainError ? error.exit : EXIT.CRED_UNAVAILABLE;
1581
- }
1582
- if (!credentials) {
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()}`
1814
+ const result = await readSurface({
1815
+ surface: "overview",
1816
+ request: {
1817
+ method: "GET",
1818
+ path: "/api/memory/entries",
1819
+ query: { status: "active", limit: RECENT_ENTRY_LIMIT }
1820
+ },
1821
+ schema: entriesResponseSchema,
1822
+ keychain: opts.keychain,
1823
+ fetchImpl: opts.fetchImpl
1603
1824
  });
1604
- if (!response.ok) {
1605
- const signedOut = response.status === 401;
1606
- emit(
1607
- report(
1608
- signedOut ? "signed_out" : "error",
1609
- credentials.endpoint,
1610
- "keychain",
1611
- overviewFailure(response.status)
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) => ({
1825
+ if (!result.ok) {
1826
+ emit2(report2(result.state, result.endpoint, result.keySource, result.problem));
1827
+ return result.exit;
1828
+ }
1829
+ emit2({
1830
+ ...report2("ready", result.endpoint, "keychain", null),
1831
+ totalCount: result.data.totalCount,
1832
+ entries: result.data.entries.slice(0, RECENT_ENTRY_LIMIT).map((entry) => ({
1632
1833
  id: entry.id,
1633
1834
  topic: boundedLabel(entry.metadata.topic, "Untitled", MAX_TOPIC_CHARS),
1634
1835
  project: boundedLabel(entry.metadata.project, "Unscoped", MAX_PROJECT_CHARS)
@@ -1636,7 +1837,7 @@ async function overviewCommand(opts = {}) {
1636
1837
  });
1637
1838
  return EXIT.OK;
1638
1839
  }
1639
- function report(state, endpoint, keySource, problem) {
1840
+ function report2(state, endpoint, keySource, problem) {
1640
1841
  return {
1641
1842
  schemaVersion: OVERVIEW_SCHEMA_VERSION,
1642
1843
  state,
@@ -1647,39 +1848,16 @@ function report(state, endpoint, keySource, problem) {
1647
1848
  problem
1648
1849
  };
1649
1850
  }
1650
- function emit(value) {
1851
+ function emit2(value) {
1651
1852
  process.stdout.write(`${JSON.stringify(value)}
1652
1853
  `);
1653
1854
  }
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
1855
  function boundedLabel(value, fallback, maxChars) {
1670
1856
  if (typeof value !== "string" || value.trim().length === 0) return fallback;
1671
1857
  const normalized = value.trim();
1672
1858
  const chars = [...normalized];
1673
1859
  return chars.length > maxChars ? `${chars.slice(0, maxChars).join("")}\u2026` : normalized;
1674
1860
  }
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
1861
 
1684
1862
  // src/cli/commands/scaffold.ts
1685
1863
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
@@ -1861,10 +2039,10 @@ function scaffoldCommand(args = {}) {
1861
2039
  // src/cli/commands/status.ts
1862
2040
  async function statusCommand(opts = {}) {
1863
2041
  const provider = opts.keychain ?? getDefaultKeychain();
1864
- let report2;
2042
+ let report4;
1865
2043
  try {
1866
2044
  const creds = await provider.read();
1867
- report2 = {
2045
+ report4 = {
1868
2046
  endpoint: creds?.endpoint ?? null,
1869
2047
  keyPresent: creds !== null,
1870
2048
  keySource: creds !== null ? "keychain" : null,
@@ -1875,17 +2053,17 @@ async function statusCommand(opts = {}) {
1875
2053
  throw err;
1876
2054
  }
1877
2055
  if (opts.json) {
1878
- process.stdout.write(`${JSON.stringify(report2)}
2056
+ process.stdout.write(`${JSON.stringify(report4)}
1879
2057
  `);
1880
2058
  } else {
1881
2059
  const lines = [
1882
- `endpoint: ${report2.endpoint ?? "(not set)"}`,
1883
- `credentials: ${report2.loggedIn ? "present (keychain)" : "not logged in \u2014 run: pyx-mem login"}`
2060
+ `endpoint: ${report4.endpoint ?? "(not set)"}`,
2061
+ `credentials: ${report4.loggedIn ? "present (keychain)" : "not logged in \u2014 run: pyx-mem login"}`
1884
2062
  ];
1885
2063
  process.stdout.write(`${lines.join("\n")}
1886
2064
  `);
1887
2065
  }
1888
- return report2.loggedIn ? EXIT.OK : EXIT.NOT_LOGGED_IN;
2066
+ return report4.loggedIn ? EXIT.OK : EXIT.NOT_LOGGED_IN;
1889
2067
  }
1890
2068
  function emitError(err, json) {
1891
2069
  if (json) {
@@ -1901,6 +2079,77 @@ ${err.guidance}
1901
2079
  return err.exit;
1902
2080
  }
1903
2081
 
2082
+ // src/cli/commands/taxonomy.ts
2083
+ import { z as z3 } from "zod";
2084
+ var TAXONOMY_SCHEMA_VERSION = 1;
2085
+ var taxonomyStateSchema = z3.object({
2086
+ clusters: z3.array(
2087
+ z3.object({
2088
+ size: z3.number().int().nonnegative(),
2089
+ currentName: z3.string().nullable(),
2090
+ categoryNodeId: z3.string().nullable(),
2091
+ projects: z3.array(
2092
+ z3.object({
2093
+ name: z3.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS),
2094
+ memories: z3.number().int().positive()
2095
+ })
2096
+ ).max(TAXONOMY_MAX_PROJECTS),
2097
+ sampleTopics: z3.array(z3.string()).max(TAXONOMY_MAX_SAMPLE_TOPICS),
2098
+ topEntities: z3.array(
2099
+ z3.object({
2100
+ name: z3.string(),
2101
+ type: z3.string(),
2102
+ degree: z3.number().int().nonnegative()
2103
+ })
2104
+ ).max(TAXONOMY_MAX_TOP_ENTITIES)
2105
+ })
2106
+ ).max(TAXONOMY_MAX_CATEGORIES),
2107
+ totals: z3.object({
2108
+ clusters: z3.number().int().nonnegative(),
2109
+ unnamed: z3.number().int().nonnegative(),
2110
+ nodesConsidered: z3.number().int().nonnegative()
2111
+ })
2112
+ });
2113
+ var taxonomyResponseSchema = z3.union([
2114
+ taxonomyStateSchema,
2115
+ z3.object({ success: z3.literal(true), data: taxonomyStateSchema }).transform(({ data }) => data)
2116
+ ]);
2117
+ async function taxonomyCommand(opts = {}) {
2118
+ const result = await readSurface({
2119
+ surface: "taxonomy",
2120
+ request: { method: "GET", path: "/api/memory/graph/taxonomy" },
2121
+ schema: taxonomyResponseSchema,
2122
+ minimumServiceVersion: "1.18.1",
2123
+ keychain: opts.keychain,
2124
+ fetchImpl: opts.fetchImpl
2125
+ });
2126
+ if (!result.ok) {
2127
+ emit3(report3(result.state, result.endpoint, result.keySource, result.problem));
2128
+ return result.exit;
2129
+ }
2130
+ emit3({
2131
+ ...report3("ready", result.endpoint, "keychain", null),
2132
+ clusters: result.data.clusters,
2133
+ totals: result.data.totals
2134
+ });
2135
+ return EXIT.OK;
2136
+ }
2137
+ function report3(state, endpoint, keySource, problem) {
2138
+ return {
2139
+ schemaVersion: TAXONOMY_SCHEMA_VERSION,
2140
+ state,
2141
+ endpoint,
2142
+ keySource,
2143
+ clusters: [],
2144
+ totals: { clusters: 0, unnamed: 0, nodesConsidered: 0 },
2145
+ problem
2146
+ };
2147
+ }
2148
+ function emit3(value) {
2149
+ process.stdout.write(`${JSON.stringify(value)}
2150
+ `);
2151
+ }
2152
+
1904
2153
  // src/cli/help.ts
1905
2154
  var HELP_TEXT = `pyx-mem \u2014 pyx-memory CLI
1906
2155
 
@@ -1911,6 +2160,8 @@ Commands:
1911
2160
  login [--endpoint <url>] [--api-key <key>] Store endpoint and API key in OS credential store.
1912
2161
  status [--json] Show endpoint, key presence, MCP config status.
1913
2162
  overview --json Read memory count and three recent safe labels.
2163
+ insights --json Read body-free memory usage and coverage aggregates.
2164
+ taxonomy --json Read knowledge-graph cluster composition (read-only).
1914
2165
  logout Delete stored pyx-memory credentials.
1915
2166
  doctor [--json] Diagnose keychain, credentials, backend, MCP startup.
1916
2167
  scaffold [--name <dir>] Generate Docker, env, SDK, and memory design-guide starter files.
@@ -2063,6 +2314,18 @@ async function main() {
2063
2314
  return EXIT.USAGE;
2064
2315
  }
2065
2316
  return overviewCommand();
2317
+ case "insights":
2318
+ if (parsed.flags.json !== true) {
2319
+ process.stderr.write("Error: `pyx-mem insights` requires --json.\n");
2320
+ return EXIT.USAGE;
2321
+ }
2322
+ return insightsCommand();
2323
+ case "taxonomy":
2324
+ if (parsed.flags.json !== true) {
2325
+ process.stderr.write("Error: `pyx-mem taxonomy` requires --json.\n");
2326
+ return EXIT.USAGE;
2327
+ }
2328
+ return taxonomyCommand();
2066
2329
  case "logout":
2067
2330
  return logoutCommand();
2068
2331
  case "doctor":