@memorax/memorax-code 0.1.4 → 0.1.6

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.
Files changed (87) hide show
  1. package/README.md +8 -19
  2. package/bin/memorax-code-npm-preinstall.mjs +28 -39
  3. package/bin/memorax-code-plugin-postinstall.mjs +23 -1537
  4. package/bin/memorax-code-setup.mjs +1615 -0
  5. package/bin/memorax-code.mjs +289 -5
  6. package/docs/configuration.md +66 -17
  7. package/docs/troubleshooting.md +105 -7
  8. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
  9. package/lib/memorax-code-adapter-common/src/config-utils.mjs +36 -3
  10. package/lib/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
  11. package/lib/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
  12. package/lib/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
  13. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
  14. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
  15. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
  16. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
  17. package/lib/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
  18. package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
  19. package/lib/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
  20. package/lib/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
  21. package/lib/memorax-code-backend/dist/clients/claude/memory-hook-runtime.js +21 -2
  22. package/lib/memorax-code-backend/dist/clients/codex/memory-hook-runtime.js +23 -3
  23. package/lib/memorax-code-backend/dist/clients/opencode/memory-hook-runtime.js +19 -1
  24. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +17 -16
  25. package/lib/memorax-code-backend/dist/lifecycle/backend/process.js +21 -0
  26. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +81 -22
  27. package/lib/memorax-code-backend/dist/memorax-cli.js +3 -0
  28. package/lib/memorax-code-backend/dist/memory/automatic-retrieval.js +8 -2
  29. package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +13 -1
  30. package/lib/memorax-code-backend/dist/memory/cli.js +10 -0
  31. package/lib/memorax-code-backend/dist/memory/quota-notice.js +208 -0
  32. package/lib/memorax-code-backend/dist/memory/service.js +11 -0
  33. package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +4 -2
  34. package/lib/memorax-code-backend/dist/provider/memorax/http.js +10 -3
  35. package/lib/memorax-code-backend/dist/provider/memorax/quota.js +26 -0
  36. package/lib/memorax-code-backend/package.json +2 -2
  37. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  38. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  39. package/lib/memorax-code-claude-adapter/package.json +1 -1
  40. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
  41. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  42. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  43. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  44. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  45. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +36 -3
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
  55. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  59. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
  60. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  61. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  62. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  63. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  64. package/lib/memorax-code-codex-adapter/package.json +1 -1
  65. package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +2 -0
  66. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  67. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  68. package/lib/memorax-code-dsh-adapter/hooks/repo-memory-job.mjs +5 -2
  69. package/lib/memorax-code-dsh-adapter/package.json +1 -1
  70. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  71. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  72. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +158 -7
  73. package/lib/memorax-code-dsh-adapter/src/runtime-state.mjs +21 -0
  74. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  75. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  76. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  77. package/lib/memorax-code-opencode-adapter/src/plugin.mjs +20 -0
  78. package/lib/node-version.mjs +1 -1
  79. package/lib/package-transition.mjs +311 -0
  80. package/lib/resolve-codex-command.mjs +32 -6
  81. package/lib/setup-memory-preferences.mjs +113 -0
  82. package/lib/setup-reconcile.mjs +128 -0
  83. package/lib/trial-plugin-mark.mjs +171 -0
  84. package/lib/trial-provision-client.mjs +307 -0
  85. package/lib/trial-provision-flow.mjs +215 -0
  86. package/lib/trial-setup.mjs +53 -0
  87. package/package.json +2 -2
@@ -0,0 +1,307 @@
1
+ const DEFAULT_SERVICE_BASE_URL = "https://platform.memorax.net";
2
+ const PROVISION_PATH = "/account/api/v1/trial/provision";
3
+ const DEFAULT_TIMEOUT_MS = 15_000;
4
+ const DEFAULT_MAX_RESPONSE_BYTES = 16_384;
5
+ const MAX_RETRY_AFTER_MS = 120_000;
6
+ const MARK_ID_PATTERN = /^mk_[0-9a-f]{64}$/;
7
+ const API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{43}$/;
8
+ const MACHINE_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
9
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
10
+ const ERROR_REASONS = new Set([
11
+ "invalid_options",
12
+ "invalid_service_url",
13
+ "tls_unsafe",
14
+ "invalid_request",
15
+ "aborted",
16
+ "timeout",
17
+ "transport",
18
+ "response_too_large",
19
+ "invalid_response",
20
+ "response_contract",
21
+ "rate_limit_exceeded",
22
+ "server_error",
23
+ "server_rejected",
24
+ ]);
25
+
26
+ export class TrialProvisionClientError extends Error {
27
+ constructor(reason, fields = {}) {
28
+ const safeReason = ERROR_REASONS.has(reason) ? reason : "invalid_response";
29
+ super(`Trial provision request failed (${safeReason})`);
30
+ this.name = "TrialProvisionClientError";
31
+ this.code = "TRIAL_PROVISION_CLIENT_FAILED";
32
+ this.reason = safeReason;
33
+ if (Number.isInteger(fields.httpStatus)) this.httpStatus = fields.httpStatus;
34
+ if (Number.isSafeInteger(fields.retryAfterMs)
35
+ && fields.retryAfterMs >= 0
36
+ && fields.retryAfterMs <= MAX_RETRY_AFTER_MS) {
37
+ this.retryAfterMs = fields.retryAfterMs;
38
+ }
39
+ }
40
+ }
41
+
42
+ export function createTrialProvisionClient(options = {}) {
43
+ const serviceBaseUrl = validateServiceBaseUrl(
44
+ options.serviceBaseUrl ?? DEFAULT_SERVICE_BASE_URL,
45
+ );
46
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
47
+ if (typeof fetchImpl !== "function") throw clientError("invalid_options");
48
+ const env = isRecord(options.env) ? options.env : process.env;
49
+ if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0"
50
+ || env.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
51
+ throw clientError("tls_unsafe");
52
+ }
53
+ const timeoutMs = positiveInteger(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 120_000);
54
+ const maxResponseBytes = positiveInteger(
55
+ options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES,
56
+ DEFAULT_MAX_RESPONSE_BYTES,
57
+ );
58
+
59
+ return Object.freeze({
60
+ async provision(request, requestOptions = {}) {
61
+ const snapshot = validateRequest(request);
62
+ const body = JSON.stringify({
63
+ mark_id: snapshot.markId,
64
+ mark_version: snapshot.markVersion,
65
+ app_salt: snapshot.appSalt,
66
+ machine_id: snapshot.machineId,
67
+ hostname: snapshot.hostname,
68
+ platform: snapshot.platform,
69
+ arch: snapshot.arch,
70
+ mac_hash: snapshot.macHash,
71
+ });
72
+ const response = await postJson(
73
+ `${serviceBaseUrl}${PROVISION_PATH}`,
74
+ body,
75
+ {
76
+ fetchImpl,
77
+ maxResponseBytes,
78
+ signal: requestOptions.signal,
79
+ timeoutMs,
80
+ },
81
+ );
82
+ return mapProvisionResponse(response, snapshot.markId);
83
+ },
84
+ });
85
+ }
86
+
87
+ async function postJson(url, body, options) {
88
+ const externalSignal = options.signal;
89
+ if (externalSignal?.aborted) throw clientError("aborted");
90
+ const controller = new AbortController();
91
+ let timedOut = false;
92
+ const onAbort = () => controller.abort();
93
+ externalSignal?.addEventListener?.("abort", onAbort, { once: true });
94
+ const timer = setTimeout(() => {
95
+ timedOut = true;
96
+ controller.abort();
97
+ }, options.timeoutMs);
98
+ timer.unref?.();
99
+
100
+ try {
101
+ const response = await options.fetchImpl(url, {
102
+ method: "POST",
103
+ headers: {
104
+ Accept: "application/json",
105
+ "Content-Type": "application/json",
106
+ },
107
+ body,
108
+ cache: "no-store",
109
+ credentials: "omit",
110
+ redirect: "error",
111
+ signal: controller.signal,
112
+ });
113
+ return await readResponse(response, options.maxResponseBytes);
114
+ } catch (error) {
115
+ if (error instanceof TrialProvisionClientError) throw error;
116
+ if (timedOut) throw clientError("timeout");
117
+ if (externalSignal?.aborted) throw clientError("aborted");
118
+ throw clientError("transport");
119
+ } finally {
120
+ clearTimeout(timer);
121
+ externalSignal?.removeEventListener?.("abort", onAbort);
122
+ }
123
+ }
124
+
125
+ async function readResponse(response, maxResponseBytes) {
126
+ const status = response?.status;
127
+ if (!Number.isInteger(status) || status < 100 || status > 599) {
128
+ throw clientError("invalid_response");
129
+ }
130
+ const contentLength = response.headers?.get?.("content-length");
131
+ if (/^[0-9]+$/.test(contentLength ?? "")
132
+ && Number(contentLength) > maxResponseBytes) {
133
+ throw clientError("response_too_large", { httpStatus: status });
134
+ }
135
+
136
+ const text = await readResponseBody(response, maxResponseBytes, status);
137
+
138
+ let body;
139
+ try {
140
+ body = JSON.parse(text);
141
+ } catch {
142
+ if (status !== 200) throw responseError(status, response);
143
+ throw clientError("invalid_response", { httpStatus: status });
144
+ }
145
+ if (status !== 200) {
146
+ throw responseError(status, response, body);
147
+ }
148
+ if (!isRecord(body)) throw clientError("invalid_response", { httpStatus: status });
149
+ if (body.success !== true) throw responseError(status, response, body);
150
+ return body;
151
+ }
152
+
153
+ async function readResponseBody(response, maxResponseBytes, status) {
154
+ if (response.body == null) return "";
155
+ const reader = response.body.getReader?.();
156
+ if (!reader || typeof reader.read !== "function") {
157
+ throw clientError("invalid_response", { httpStatus: status });
158
+ }
159
+ const chunks = [];
160
+ let byteLength = 0;
161
+ try {
162
+ while (true) {
163
+ const { done, value } = await reader.read();
164
+ if (done) break;
165
+ if (!(value instanceof Uint8Array)) {
166
+ throw clientError("invalid_response", { httpStatus: status });
167
+ }
168
+ byteLength += value.byteLength;
169
+ if (byteLength > maxResponseBytes) {
170
+ await reader.cancel().catch(() => undefined);
171
+ throw clientError("response_too_large", { httpStatus: status });
172
+ }
173
+ chunks.push(Buffer.from(value));
174
+ }
175
+ } finally {
176
+ reader.releaseLock?.();
177
+ }
178
+ return Buffer.concat(chunks, byteLength).toString("utf8");
179
+ }
180
+
181
+ function responseError(status, response, body) {
182
+ const retryAfterMs = retryAfterDelay(response.headers?.get?.("retry-after"), body);
183
+ if (status === 429) {
184
+ return clientError("rate_limit_exceeded", { httpStatus: status, retryAfterMs });
185
+ }
186
+ if (status >= 500) {
187
+ return clientError("server_error", { httpStatus: status, retryAfterMs });
188
+ }
189
+ return clientError("server_rejected", { httpStatus: status, retryAfterMs });
190
+ }
191
+
192
+ function mapProvisionResponse(envelope, expectedMarkId) {
193
+ const data = envelope.data;
194
+ const accountId = decimalPublicId(data?.account_id);
195
+ const projectId = decimalPublicId(data?.project_id);
196
+ const apiKey = typeof data?.api_key === "string" && API_KEY_PATTERN.test(data.api_key)
197
+ ? data.api_key
198
+ : undefined;
199
+ const keyPrefix = typeof data?.key_prefix === "string" ? data.key_prefix.trim() : "";
200
+ if (!accountId
201
+ || !projectId
202
+ || data?.mark_id !== expectedMarkId
203
+ || !apiKey
204
+ || !keyPrefix
205
+ || !apiKey.startsWith(keyPrefix)
206
+ || typeof data?.created !== "boolean") {
207
+ throw clientError("response_contract", { httpStatus: 200 });
208
+ }
209
+ return Object.freeze({
210
+ accountId,
211
+ projectId,
212
+ apiKey,
213
+ created: data.created,
214
+ });
215
+ }
216
+
217
+ function validateRequest(request) {
218
+ const snapshot = {
219
+ markId: request?.markId,
220
+ markVersion: request?.markVersion,
221
+ appSalt: request?.appSalt,
222
+ machineId: request?.machineId,
223
+ hostname: request?.hostname,
224
+ platform: request?.platform,
225
+ arch: request?.arch,
226
+ macHash: request?.macHash,
227
+ };
228
+ if (!MARK_ID_PATTERN.test(snapshot.markId ?? "")
229
+ || snapshot.markVersion !== 1
230
+ || snapshot.appSalt !== "memorax-plugin-v1"
231
+ || !MACHINE_ID_PATTERN.test(snapshot.machineId ?? "")
232
+ || !validText(snapshot.hostname, 120)
233
+ || !["windows", "linux", "macos"].includes(snapshot.platform)
234
+ || !["x86_64", "arm64"].includes(snapshot.arch)
235
+ || !SHA256_PATTERN.test(snapshot.macHash ?? "")) {
236
+ throw clientError("invalid_request");
237
+ }
238
+ return snapshot;
239
+ }
240
+
241
+ function validateServiceBaseUrl(value) {
242
+ try {
243
+ const url = new URL(value);
244
+ if (typeof value !== "string"
245
+ || value !== value.trim()
246
+ || url.protocol !== "https:"
247
+ || !url.hostname
248
+ || url.username
249
+ || url.password
250
+ || url.search
251
+ || url.hash
252
+ || !["", "/"].includes(url.pathname)) {
253
+ throw new Error();
254
+ }
255
+ return url.origin;
256
+ } catch {
257
+ throw clientError("invalid_service_url");
258
+ }
259
+ }
260
+
261
+ function retryAfterDelay(header, body) {
262
+ const headerText = typeof header === "string" ? header.trim() : "";
263
+ let headerDelay;
264
+ if (/^\d+$/.test(headerText)) {
265
+ headerDelay = Number(headerText) * 1000;
266
+ } else if (headerText) {
267
+ const retryAt = Date.parse(headerText);
268
+ if (Number.isFinite(retryAt)) headerDelay = Math.max(0, retryAt - Date.now());
269
+ }
270
+ if (Number.isSafeInteger(headerDelay) && headerDelay <= MAX_RETRY_AFTER_MS) {
271
+ return headerDelay;
272
+ }
273
+ const bodySeconds = isRecord(body) && isRecord(body.error)
274
+ ? body.error.retry_after_seconds
275
+ : undefined;
276
+ const delay = Number.isSafeInteger(bodySeconds) && bodySeconds >= 0
277
+ ? bodySeconds * 1000
278
+ : undefined;
279
+ return Number.isSafeInteger(delay) && delay <= MAX_RETRY_AFTER_MS ? delay : undefined;
280
+ }
281
+
282
+ function validText(value, maximumLength) {
283
+ return typeof value === "string"
284
+ && value.length > 0
285
+ && value.length <= maximumLength
286
+ && value === value.trim()
287
+ && !value.includes("\0");
288
+ }
289
+
290
+ function decimalPublicId(value) {
291
+ return typeof value === "string" && /^\d+$/.test(value) ? value : undefined;
292
+ }
293
+
294
+ function positiveInteger(value, maximum) {
295
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
296
+ throw clientError("invalid_options");
297
+ }
298
+ return value;
299
+ }
300
+
301
+ function clientError(reason, fields) {
302
+ return new TrialProvisionClientError(reason, fields);
303
+ }
304
+
305
+ function isRecord(value) {
306
+ return typeof value === "object" && value !== null && !Array.isArray(value);
307
+ }
@@ -0,0 +1,215 @@
1
+ import {
2
+ TrialProvisionClientError,
3
+ } from "./trial-provision-client.mjs";
4
+ import { generateTrialPluginIdentity } from "./trial-plugin-mark.mjs";
5
+
6
+ export {
7
+ generateTrialPluginIdentity,
8
+ generateTrialMarkId,
9
+ } from "./trial-plugin-mark.mjs";
10
+
11
+ const RETRY_DELAYS_MS = Object.freeze([1_000, 2_000, 4_000]);
12
+ const ERROR_REASONS = new Set([
13
+ "invalid_options",
14
+ "invalid_credential_state",
15
+ "identity_generation_failed",
16
+ "credential_failure",
17
+ "response_state_mismatch",
18
+ "client_failure",
19
+ "retry_failed",
20
+ ]);
21
+ const MARK_ID_PATTERN = /^mk_[0-9a-f]{64}$/;
22
+ const API_KEY_PATTERN = /^sk_[A-Za-z0-9_-]{43}$/;
23
+
24
+ export class TrialProvisionFlowError extends Error {
25
+ constructor(reason) {
26
+ const safeReason = ERROR_REASONS.has(reason) ? reason : "client_failure";
27
+ super(`Trial provision flow failed (${safeReason})`);
28
+ this.name = "TrialProvisionFlowError";
29
+ this.code = "TRIAL_PROVISION_FLOW_FAILED";
30
+ this.reason = safeReason;
31
+ }
32
+ }
33
+
34
+ export async function ensureTrialCredentialReady(options = {}) {
35
+ const context = validateOptions(options);
36
+ try {
37
+ return await context.credentialPort.withProvisionLock(
38
+ () => runTrialCredentialFlow(context),
39
+ { timeoutMs: 30_000 },
40
+ );
41
+ } catch (error) {
42
+ if (error instanceof TrialProvisionFlowError
43
+ || error instanceof TrialProvisionClientError) {
44
+ throw error;
45
+ }
46
+ throw flowError("credential_failure");
47
+ }
48
+ }
49
+
50
+ async function runTrialCredentialFlow(context) {
51
+ let record = await context.credentialPort.load();
52
+ if (record === null) record = await createCredential(context);
53
+ assertCredentialRecord(record);
54
+ if (record.state === "ready") return readyResult(record, false);
55
+
56
+ const response = await provisionWithRetry(record, context);
57
+ validateProvisionResponse(response);
58
+ const ready = await context.credentialPort.complete(record, {
59
+ accountId: response.accountId,
60
+ projectId: response.projectId,
61
+ apiKey: response.apiKey,
62
+ });
63
+ return readyResult(ready, true);
64
+ }
65
+
66
+ async function createCredential(context) {
67
+ let seed;
68
+ try {
69
+ const identity = context.generatePluginIdentity();
70
+ seed = context.recordPort.createInitial({
71
+ markId: identity.markId,
72
+ markVersion: identity.markVersion,
73
+ appSalt: identity.appSalt,
74
+ machineId: identity.machineId,
75
+ hostname: identity.hostname,
76
+ platform: identity.platform,
77
+ arch: identity.arch,
78
+ macHash: identity.macHash,
79
+ });
80
+ } catch {
81
+ throw flowError("identity_generation_failed");
82
+ }
83
+ const result = await context.credentialPort.createIfAbsent(seed);
84
+ if (!isRecord(result) || !Object.hasOwn(result, "record")) {
85
+ throw flowError("credential_failure");
86
+ }
87
+ return result.record;
88
+ }
89
+
90
+ async function provisionWithRetry(record, context) {
91
+ const request = Object.freeze({
92
+ markId: record.mark_id,
93
+ markVersion: record.mark_version,
94
+ appSalt: record.app_salt,
95
+ machineId: record.machine_id,
96
+ hostname: record.hostname,
97
+ platform: record.platform,
98
+ arch: record.arch,
99
+ macHash: record.mac_hash,
100
+ });
101
+
102
+ for (let attempt = 0; ; attempt += 1) {
103
+ try {
104
+ return await context.client.provision(request);
105
+ } catch (error) {
106
+ if (!(error instanceof TrialProvisionClientError)) {
107
+ throw flowError("client_failure");
108
+ }
109
+ const delay = retryDelay(error, attempt);
110
+ if (delay === undefined) throw error;
111
+ try {
112
+ await context.sleep(delay);
113
+ } catch {
114
+ throw flowError("retry_failed");
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ function retryDelay(error, attempt) {
121
+ if (attempt >= RETRY_DELAYS_MS.length) return undefined;
122
+ if (error.reason === "rate_limit_exceeded") return error.retryAfterMs;
123
+ if (["transport", "timeout", "server_error"].includes(error.reason)) {
124
+ return RETRY_DELAYS_MS[attempt];
125
+ }
126
+ if (["invalid_response", "response_contract"].includes(error.reason)
127
+ && error.httpStatus === 200
128
+ && attempt === 0) {
129
+ return 0;
130
+ }
131
+ return undefined;
132
+ }
133
+
134
+ function validateProvisionResponse(response) {
135
+ if (!isRecord(response)
136
+ || typeof response.accountId !== "string"
137
+ || typeof response.projectId !== "string"
138
+ || typeof response.apiKey !== "string"
139
+ || !API_KEY_PATTERN.test(response.apiKey)
140
+ || typeof response.created !== "boolean") {
141
+ throw flowError("response_state_mismatch");
142
+ }
143
+ }
144
+
145
+ function assertCredentialRecord(record) {
146
+ if (!isRecord(record)
147
+ || !["provisioning", "ready"].includes(record.state)
148
+ || typeof record.mark_id !== "string"
149
+ || !MARK_ID_PATTERN.test(record.mark_id)
150
+ || (record.state === "provisioning" && record.api_key !== null)
151
+ || (record.state === "ready"
152
+ && (typeof record.api_key !== "string"
153
+ || !API_KEY_PATTERN.test(record.api_key)
154
+ || typeof record.account_id !== "string"
155
+ || typeof record.project_id !== "string"))) {
156
+ throw flowError("invalid_credential_state");
157
+ }
158
+ }
159
+
160
+ function readyResult(record, provisioned) {
161
+ assertCredentialRecord(record);
162
+ if (record.state !== "ready") throw flowError("credential_failure");
163
+ return Object.freeze({
164
+ status: "ready",
165
+ provisioned,
166
+ markId: record.mark_id,
167
+ accountId: record.account_id,
168
+ projectId: record.project_id,
169
+ apiKey: record.api_key,
170
+ });
171
+ }
172
+
173
+ function validateOptions(options) {
174
+ const credentialPort = options.credentialPort;
175
+ const recordPort = options.recordPort;
176
+ const client = options.client;
177
+ if (!hasFunctions(credentialPort, [
178
+ "load",
179
+ "createIfAbsent",
180
+ "complete",
181
+ "withProvisionLock",
182
+ ])
183
+ || !hasFunctions(recordPort, ["createInitial"])
184
+ || !hasFunctions(client, ["provision"])) {
185
+ throw flowError("invalid_options");
186
+ }
187
+ const generatePluginIdentity = options.generatePluginIdentity ?? generateTrialPluginIdentity;
188
+ const sleep = options.sleep ?? defaultSleep;
189
+ if (typeof generatePluginIdentity !== "function" || typeof sleep !== "function") {
190
+ throw flowError("invalid_options");
191
+ }
192
+ return {
193
+ credentialPort,
194
+ recordPort,
195
+ client,
196
+ generatePluginIdentity,
197
+ sleep,
198
+ };
199
+ }
200
+
201
+ function defaultSleep(delayMs) {
202
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
203
+ }
204
+
205
+ function hasFunctions(value, names) {
206
+ return isRecord(value) && names.every((name) => typeof value[name] === "function");
207
+ }
208
+
209
+ function flowError(reason) {
210
+ return new TrialProvisionFlowError(reason);
211
+ }
212
+
213
+ function isRecord(value) {
214
+ return typeof value === "object" && value !== null && !Array.isArray(value);
215
+ }
@@ -0,0 +1,53 @@
1
+ import { createTrialProvisionClient } from "./trial-provision-client.mjs";
2
+ import { ensureTrialCredentialReady } from "./trial-provision-flow.mjs";
3
+
4
+ export async function ensureTrialSetupCredential(options = {}) {
5
+ const env = options.env ?? process.env;
6
+ const credentialApis = options.credentialApis ?? await loadCredentialApis();
7
+ const credentialPort = trialCredentialPort(options, credentialApis, env);
8
+ const recordPort = options.recordPort ?? {
9
+ createInitial: credentialApis.createInitialTrialCredentialRecord,
10
+ };
11
+ const client = options.client ?? createTrialProvisionClient({
12
+ ...options.provisionClientOptions,
13
+ env,
14
+ });
15
+ return await ensureTrialCredentialReady({
16
+ ...options.flowOptions,
17
+ credentialPort,
18
+ recordPort,
19
+ client,
20
+ });
21
+ }
22
+
23
+ export async function loadReadyTrialSetupCredential(options = {}) {
24
+ const env = options.env ?? process.env;
25
+ const credentialApis = options.credentialApis ?? await loadCredentialApis();
26
+ const record = await trialCredentialPort(options, credentialApis, env).load();
27
+ if (record?.state !== "ready") return undefined;
28
+ return Object.freeze({
29
+ status: "ready",
30
+ provisioned: false,
31
+ markId: record.mark_id,
32
+ accountId: record.account_id,
33
+ projectId: record.project_id,
34
+ apiKey: record.api_key,
35
+ });
36
+ }
37
+
38
+ function trialCredentialPort(options, credentialApis, env) {
39
+ return options.credentialPort
40
+ ?? credentialApis.createTrialCredentialStorePort({
41
+ ...options.credentialStoreOptions,
42
+ memoraxCodeHome: options.memoraxCodeHome,
43
+ env,
44
+ });
45
+ }
46
+
47
+ async function loadCredentialApis() {
48
+ const [recordApi, storeApi] = await Promise.all([
49
+ import("./memorax-code-adapter-common/src/credentials/trial-credential-record.mjs"),
50
+ import("./memorax-code-adapter-common/src/credentials/trial-credential-store.mjs"),
51
+ ]);
52
+ return { ...recordApi, ...storeApi };
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax/memorax-code",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Persistent coding memory for Codex, Claude Code, DeepSeek Harness, and OpenCode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "engines": {
40
- "node": ">=24"
40
+ "node": ">=20"
41
41
  },
42
42
  "dependencies": {
43
43
  "smol-toml": "^1.7.0"