@pyxmate/memory 1.27.0 → 1.27.2

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.
@@ -0,0 +1,143 @@
1
+ import {
2
+ probeCredentials
3
+ } from "./chunk-S3CX323Z.mjs";
4
+ import {
5
+ KeychainError,
6
+ getDefaultKeychain
7
+ } from "./chunk-7TATCSSN.mjs";
8
+ import {
9
+ EXIT
10
+ } from "./chunk-4DXEQC6X.mjs";
11
+
12
+ // src/cli/commands/doctor.ts
13
+ async function doctorCommand(opts = {}) {
14
+ const provider = opts.keychain ?? getDefaultKeychain();
15
+ const fetcher = opts.fetchImpl ?? fetch;
16
+ const checks = [];
17
+ const accessCheck = await provider.check();
18
+ if (accessCheck.ok) {
19
+ checks.push({ id: "provider.import", status: "pass", message: "native keyring module loaded" });
20
+ checks.push({ id: "keychain.access", status: "pass", message: accessCheck.message });
21
+ } else if (accessCheck.code === "provider-import-failed") {
22
+ checks.push({
23
+ id: "provider.import",
24
+ status: "fail",
25
+ code: accessCheck.code,
26
+ message: accessCheck.message,
27
+ guidance: accessCheck.guidance
28
+ });
29
+ checks.push({
30
+ id: "keychain.access",
31
+ status: "skip",
32
+ message: "skipped \u2014 provider import failed"
33
+ });
34
+ } else {
35
+ checks.push({ id: "provider.import", status: "pass", message: "native keyring module loaded" });
36
+ checks.push({
37
+ id: "keychain.access",
38
+ status: "fail",
39
+ code: accessCheck.code,
40
+ message: accessCheck.message,
41
+ guidance: accessCheck.guidance
42
+ });
43
+ }
44
+ const accessOk = accessCheck.ok;
45
+ let creds = null;
46
+ if (accessOk) {
47
+ try {
48
+ creds = await provider.read();
49
+ if (creds) {
50
+ checks.push({
51
+ id: "credentials.present",
52
+ status: "pass",
53
+ message: "credentials present in keychain",
54
+ endpoint: creds.endpoint
55
+ });
56
+ } else {
57
+ checks.push({
58
+ id: "credentials.present",
59
+ status: "fail",
60
+ code: "not-logged-in",
61
+ message: "no pyx-memory credentials stored",
62
+ guidance: "Run: pyx-mem login"
63
+ });
64
+ }
65
+ } catch (err) {
66
+ if (err instanceof KeychainError) {
67
+ checks.push({
68
+ id: "credentials.present",
69
+ status: "fail",
70
+ code: err.code,
71
+ message: err.message,
72
+ guidance: err.guidance
73
+ });
74
+ } else {
75
+ throw err;
76
+ }
77
+ }
78
+ } else {
79
+ checks.push({
80
+ id: "credentials.present",
81
+ status: "skip",
82
+ message: "skipped \u2014 keychain access failed"
83
+ });
84
+ }
85
+ if (creds) {
86
+ const probe = await probeCredentials({
87
+ endpoint: creds.endpoint,
88
+ apiKey: creds.apiKey,
89
+ fetcher
90
+ });
91
+ if (probe.kind === "ok") {
92
+ checks.push({
93
+ id: "backend.reachable",
94
+ status: "pass",
95
+ message: `pyx-memory API reachable (HTTP ${probe.status})`,
96
+ endpoint: creds.endpoint
97
+ });
98
+ } else if (probe.kind === "invalid") {
99
+ checks.push({
100
+ id: "backend.reachable",
101
+ status: "fail",
102
+ code: "key-rejected",
103
+ message: `API key rejected (HTTP ${probe.status})`,
104
+ endpoint: creds.endpoint,
105
+ guidance: "Re-login with a valid key: pyx-mem login"
106
+ });
107
+ } else {
108
+ checks.push({
109
+ id: "backend.reachable",
110
+ status: "fail",
111
+ code: "unreachable",
112
+ message: `no pyx-memory API at this endpoint (${probe.reason})`,
113
+ endpoint: creds.endpoint,
114
+ guidance: "Verify the endpoint is the API backend, not a web UI: pyx-mem login --endpoint <API_URL>"
115
+ });
116
+ }
117
+ } else {
118
+ checks.push({
119
+ id: "backend.reachable",
120
+ status: "skip",
121
+ message: "skipped \u2014 no credentials to probe"
122
+ });
123
+ }
124
+ const ok = checks.every((c) => c.status !== "fail");
125
+ if (opts.json) {
126
+ process.stdout.write(`${JSON.stringify({ ok, checks })}
127
+ `);
128
+ } else {
129
+ for (const c of checks) {
130
+ const tag = c.status === "pass" ? "OK " : c.status === "skip" ? "SKIP" : "FAIL";
131
+ process.stdout.write(`[${tag}] ${c.id}: ${c.message}
132
+ `);
133
+ if (c.endpoint) process.stdout.write(` endpoint: ${c.endpoint}
134
+ `);
135
+ if (c.status === "fail" && c.guidance) process.stdout.write(` ${c.guidance}
136
+ `);
137
+ }
138
+ }
139
+ return ok ? EXIT.OK : EXIT.DOCTOR_FAIL;
140
+ }
141
+ export {
142
+ doctorCommand
143
+ };
@@ -0,0 +1,139 @@
1
+ import {
2
+ readSurface
3
+ } from "./chunk-UESIQ47D.mjs";
4
+ import "./chunk-THLUBOGD.mjs";
5
+ import "./chunk-7TATCSSN.mjs";
6
+ import {
7
+ MEMORY_PROJECT_LABEL_MAX_CHARS,
8
+ TAXONOMY_MAX_PROJECTS,
9
+ normalizeMemoryActorLabel
10
+ } from "./chunk-RWQTBXUC.mjs";
11
+ import "./chunk-HTQBKJTD.mjs";
12
+ import {
13
+ EXIT
14
+ } from "./chunk-4DXEQC6X.mjs";
15
+
16
+ // src/cli/commands/insights.ts
17
+ import { z } from "zod";
18
+ var INSIGHTS_SCHEMA_VERSION = 1;
19
+ var metricsSchema = z.object({
20
+ activeEntries: z.number().int().nonnegative(),
21
+ retainedAdditions7d: z.number().int().nonnegative(),
22
+ appliedRecalls: z.number().int().nonnegative(),
23
+ reinforcedEntries: z.number().int().nonnegative(),
24
+ retrievedEntries: z.number().int().nonnegative(),
25
+ retrievals: z.number().int().nonnegative(),
26
+ lastAppliedAt: z.string().nullable(),
27
+ lastRetrievedAt: z.string().nullable()
28
+ });
29
+ var insightsSchema = z.object({
30
+ generatedAt: z.string(),
31
+ scope: z.literal("connected_memory"),
32
+ coverage: z.object({
33
+ retainedAdditions: z.object({
34
+ state: z.enum(["measured", "partial"]),
35
+ trackingSince: z.string().nullable(),
36
+ trackedEntries: z.number().int().nonnegative(),
37
+ totalEntries: z.number().int().nonnegative(),
38
+ reason: z.string().nullable()
39
+ }),
40
+ projectLabels: z.object({ state: z.literal("measured") }),
41
+ retrievalUsage: z.object({ state: z.literal("partial"), reason: z.string() }),
42
+ tokenEfficiency: z.object({ state: z.literal("not_instrumented"), reason: z.string() }),
43
+ contextAccuracy: z.object({ state: z.literal("not_instrumented"), reason: z.string() })
44
+ }),
45
+ summary: z.object({
46
+ activeEntries: z.number().int().nonnegative(),
47
+ observedProjectLabels: z.number().int().nonnegative(),
48
+ retainedAdditions7d: z.number().int().nonnegative().nullable(),
49
+ trackedEntries: z.number().int().nonnegative(),
50
+ appliedRecalls: z.number().int().nonnegative(),
51
+ reinforcedEntries: z.number().int().nonnegative(),
52
+ retrievedEntries: z.number().int().nonnegative(),
53
+ retrievals: z.number().int().nonnegative()
54
+ }),
55
+ projects: z.array(
56
+ metricsSchema.extend({
57
+ label: z.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS)
58
+ })
59
+ ).max(TAXONOMY_MAX_PROJECTS),
60
+ other: metricsSchema.nullable(),
61
+ recentApplied: z.array(
62
+ z.object({
63
+ topic: z.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS).nullable(),
64
+ project: z.string().min(1).max(MEMORY_PROJECT_LABEL_MAX_CHARS).nullable(),
65
+ at: z.string()
66
+ })
67
+ ).max(5)
68
+ });
69
+ var actorSchema = z.string().refine((value) => normalizeMemoryActorLabel(value) === value).nullable().default(null);
70
+ var insightsWithProvenanceSchema = insightsSchema.extend({
71
+ recentApplied: z.array(
72
+ insightsSchema.shape.recentApplied.element.extend({
73
+ writtenBy: actorSchema,
74
+ writtenAt: z.iso.datetime({ offset: true }).nullable().default(null),
75
+ appliedBy: actorSchema
76
+ })
77
+ ).max(5)
78
+ });
79
+ async function insightsCommand(opts = {}) {
80
+ const schema = opts.withProvenance === true ? insightsWithProvenanceSchema : insightsSchema;
81
+ const result = await readSurface({
82
+ surface: "insights",
83
+ request: { method: "GET", path: "/api/memory/insights" },
84
+ schema: z.union([
85
+ schema,
86
+ z.object({ success: z.literal(true), data: schema }).transform(({ data }) => data)
87
+ ]),
88
+ minimumServiceVersion: "1.19.0",
89
+ keychain: opts.keychain,
90
+ fetchImpl: opts.fetchImpl
91
+ });
92
+ const withIdentity = opts.withIdentity === true;
93
+ if (!result.ok) {
94
+ emit(
95
+ report(result.state, result.endpoint, result.keySource, result.problem, {
96
+ withIdentity,
97
+ credentialFingerprint: result.credentialFingerprint
98
+ })
99
+ );
100
+ return result.exit;
101
+ }
102
+ emit({
103
+ schemaVersion: INSIGHTS_SCHEMA_VERSION,
104
+ state: "ready",
105
+ endpoint: result.endpoint,
106
+ keySource: "keychain",
107
+ ...identityField({ withIdentity, credentialFingerprint: result.credentialFingerprint }),
108
+ ...result.data,
109
+ problem: null
110
+ });
111
+ return EXIT.OK;
112
+ }
113
+ function identityField(identity) {
114
+ return identity.withIdentity ? { credentialFingerprint: identity.credentialFingerprint } : {};
115
+ }
116
+ function report(state, endpoint, keySource, problem, identity) {
117
+ return {
118
+ schemaVersion: INSIGHTS_SCHEMA_VERSION,
119
+ state,
120
+ endpoint,
121
+ keySource,
122
+ ...identityField(identity),
123
+ generatedAt: null,
124
+ scope: "connected_memory",
125
+ coverage: null,
126
+ summary: null,
127
+ projects: [],
128
+ other: null,
129
+ recentApplied: [],
130
+ problem
131
+ };
132
+ }
133
+ function emit(value) {
134
+ process.stdout.write(`${JSON.stringify(value)}
135
+ `);
136
+ }
137
+ export {
138
+ insightsCommand
139
+ };
@@ -0,0 +1,179 @@
1
+ import {
2
+ probeCredentials
3
+ } from "./chunk-S3CX323Z.mjs";
4
+ import {
5
+ BUSY,
6
+ KeychainError,
7
+ getLifecycleKeychain,
8
+ supersedeLogout,
9
+ withLifecycleLock
10
+ } from "./chunk-7TATCSSN.mjs";
11
+ import {
12
+ EXIT
13
+ } from "./chunk-4DXEQC6X.mjs";
14
+
15
+ // src/cli/prompt.ts
16
+ async function readApiKeySecret() {
17
+ if (process.stdin.isTTY) {
18
+ return promptMasked("API key: ");
19
+ }
20
+ return readSingleLineFromStdin();
21
+ }
22
+ async function promptMasked(label) {
23
+ const { stdin, stdout } = process;
24
+ stdout.write(label);
25
+ const hadRawMode = typeof stdin.setRawMode === "function";
26
+ if (!hadRawMode) {
27
+ const value = await readSingleLineFromStdin();
28
+ stdout.write(`${"\u2022".repeat(Math.min(value.length, 80))}
29
+ `);
30
+ return value;
31
+ }
32
+ return new Promise((resolve, reject) => {
33
+ const previousEncoding = stdin.readableEncoding;
34
+ stdin.setEncoding("utf8");
35
+ stdin.setRawMode(true);
36
+ stdin.resume();
37
+ let buf = "";
38
+ const cleanup = () => {
39
+ stdin.removeListener("data", onData);
40
+ stdin.removeListener("error", onError);
41
+ stdin.setRawMode(false);
42
+ stdin.pause();
43
+ if (previousEncoding) stdin.setEncoding(previousEncoding);
44
+ };
45
+ const onData = (chunk) => {
46
+ for (const ch of chunk) {
47
+ if (ch === "\n" || ch === "\r") {
48
+ cleanup();
49
+ stdout.write("\n");
50
+ resolve(buf.trim());
51
+ return;
52
+ }
53
+ if (ch === "") {
54
+ cleanup();
55
+ stdout.write("\n");
56
+ process.exit(130);
57
+ }
58
+ if (ch === "\x7F" || ch === "\b") {
59
+ if (buf.length > 0) {
60
+ buf = buf.slice(0, -1);
61
+ stdout.write("\b \b");
62
+ }
63
+ continue;
64
+ }
65
+ if (ch < " ") {
66
+ continue;
67
+ }
68
+ buf += ch;
69
+ stdout.write("\u2022");
70
+ }
71
+ };
72
+ const onError = (err) => {
73
+ cleanup();
74
+ reject(err instanceof Error ? err : new Error(String(err)));
75
+ };
76
+ stdin.on("data", onData);
77
+ stdin.on("error", onError);
78
+ });
79
+ }
80
+ async function readSingleLineFromStdin() {
81
+ let buf = "";
82
+ for await (const chunk of process.stdin) {
83
+ buf += chunk.toString("utf8");
84
+ const nl = buf.indexOf("\n");
85
+ if (nl >= 0) return buf.slice(0, nl).trim();
86
+ }
87
+ return buf.trim();
88
+ }
89
+
90
+ // src/cli/commands/login.ts
91
+ var DEFAULT_ENDPOINT = "https://memory.api.pyxmate.com";
92
+ async function loginCommand(opts = {}) {
93
+ const provider = opts.keychain ?? getLifecycleKeychain();
94
+ const fetcher = opts.fetchImpl ?? fetch;
95
+ const endpoint = normalizeEndpoint(
96
+ opts.endpoint ?? await readExistingEndpoint(provider) ?? DEFAULT_ENDPOINT
97
+ );
98
+ if (!isHttpUrl(endpoint)) {
99
+ process.stderr.write(`Error: invalid --endpoint \`${endpoint}\` (expected http(s) URL).
100
+ `);
101
+ return EXIT.USAGE;
102
+ }
103
+ let apiKey;
104
+ try {
105
+ apiKey = opts.apiKey?.trim() || await readApiKeySecret();
106
+ } catch (err) {
107
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
108
+ `);
109
+ return EXIT.USAGE;
110
+ }
111
+ if (!apiKey) {
112
+ process.stderr.write("Error: API key is empty.\n");
113
+ return EXIT.USAGE;
114
+ }
115
+ const probe = await probeCredentials({ endpoint, apiKey, fetcher });
116
+ if (probe.kind === "invalid") {
117
+ process.stderr.write(
118
+ `Error: API key was rejected by ${endpoint} (HTTP ${probe.status}).
119
+ No credentials were written.
120
+ `
121
+ );
122
+ return EXIT.NOT_LOGGED_IN;
123
+ }
124
+ try {
125
+ const written = await withLifecycleLock(provider, async () => {
126
+ await supersedeLogout(provider, endpoint, apiKey);
127
+ await provider.write({ schemaVersion: 1, endpoint, apiKey });
128
+ });
129
+ if (written === BUSY) {
130
+ process.stderr.write(
131
+ "Error: another pyx-mem sign-in or sign-out is in progress. Retry in a moment.\nNo credentials were written.\n"
132
+ );
133
+ return EXIT.BUSY;
134
+ }
135
+ } catch (err) {
136
+ if (err instanceof KeychainError) {
137
+ process.stderr.write(
138
+ `Error: ${err.message}
139
+ ${err.guidance}
140
+ No credentials were written. pyx-mem does not write plaintext token files.
141
+ `
142
+ );
143
+ return err.exit;
144
+ }
145
+ throw err;
146
+ }
147
+ if (probe.kind === "unreachable") {
148
+ process.stdout.write(
149
+ `Stored credentials for ${endpoint}. Warning: validation probe failed (${probe.reason}). Run: pyx-mem doctor
150
+ `
151
+ );
152
+ } else {
153
+ process.stdout.write(`Logged in to ${endpoint}. Run: pyx-mem doctor
154
+ `);
155
+ }
156
+ return EXIT.OK;
157
+ }
158
+ async function readExistingEndpoint(provider) {
159
+ try {
160
+ const creds = await provider.read();
161
+ return creds?.endpoint ?? null;
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+ function normalizeEndpoint(input) {
167
+ return input.replace(/\/+$/, "");
168
+ }
169
+ function isHttpUrl(value) {
170
+ try {
171
+ const u = new URL(value);
172
+ return u.protocol === "http:" || u.protocol === "https:";
173
+ } catch {
174
+ return false;
175
+ }
176
+ }
177
+ export {
178
+ loginCommand
179
+ };