@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.
@@ -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 (!isHttpUrl(endpoint)) {
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 isHttpUrl(value) {
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 randomUUID2 } from "crypto";
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 = randomUUID2()) {
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.17.19" : "0.0.0-dev");
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 { randomUUID as randomUUID3 } from "crypto";
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 = z.object({
1551
- id: z.string().min(1),
1552
- metadata: z.record(z.string(), z.unknown())
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 = z.object({
1555
- memories: z.array(entrySchema),
1556
- total: z.number().int().nonnegative()
1747
+ var hostedEntriesResponseSchema = z2.object({
1748
+ memories: z2.array(entrySchema),
1749
+ total: z2.number().int().nonnegative()
1557
1750
  }).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()
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 = z.union([
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 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()}`
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 (!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) => ({
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 report(state, endpoint, keySource, problem) {
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 emit(value) {
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 report2;
1994
+ let report4;
1865
1995
  try {
1866
1996
  const creds = await provider.read();
1867
- report2 = {
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(report2)}
2008
+ process.stdout.write(`${JSON.stringify(report4)}
1879
2009
  `);
1880
2010
  } else {
1881
2011
  const lines = [
1882
- `endpoint: ${report2.endpoint ?? "(not set)"}`,
1883
- `credentials: ${report2.loggedIn ? "present (keychain)" : "not logged in \u2014 run: pyx-mem login"}`
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 report2.loggedIn ? EXIT.OK : EXIT.NOT_LOGGED_IN;
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":