@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,1615 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { parse } from "smol-toml";
9
+ import {
10
+ setTomlField,
11
+ updateConfigFileAtomically,
12
+ } from "../lib/memorax-code-adapter-common/src/memorax-code-config-file.mjs";
13
+ import {
14
+ MEMORAX_DEFAULT_BASE_URL,
15
+ MEMORAX_DEFAULT_MEMORY_OUTPUT_LANGUAGE,
16
+ normalizeMemoraxBaseUrl,
17
+ normalizeMemoraxMemoryOutputLanguage,
18
+ } from "../lib/memorax-code-adapter-common/src/memorax-defaults.mjs";
19
+ import { writeSetupCompletionRecord } from "../lib/memorax-code-adapter-common/src/setup-completion.mjs";
20
+ import { stagePackagedClientHookRuntime } from "../lib/client-hook-runtime.mjs";
21
+ import { discoverDshProfiles } from "../lib/dsh-plugin-install.mjs";
22
+ import { ensureClaudeCommandEnv } from "../lib/resolve-claude-command.mjs";
23
+ import { ensureCodexCommandEnv } from "../lib/resolve-codex-command.mjs";
24
+ import { reconcileSetup } from "../lib/setup-reconcile.mjs";
25
+ import { detectSetupMemoryPreferences } from "../lib/setup-memory-preferences.mjs";
26
+ import { ensureTrialSetupCredential } from "../lib/trial-setup.mjs";
27
+ import { commandOnPath } from "../lib/vscode-extension-command.mjs";
28
+ import { resolveWindowsCliInvocation } from "../lib/windows-cli-invocation.mjs";
29
+
30
+ const PLUGIN_NAME = "memorax-code-codex-adapter";
31
+ const CLI_MARKETPLACE_NAME = "memorax-code";
32
+ const PERSONAL_MARKETPLACE_NAME = "personal";
33
+ const PREVIOUS_HOOKS_ENV = "MEMORAX_CODE_CODEX_PREVIOUS_HOOKS_JSON";
34
+ const HOOK_TRUST_SELECTION_ENV = "MEMORAX_CODE_CODEX_HOOK_TRUST_SELECTION_JSON";
35
+ const PREFIX = "[MemoraX Code Setup]:";
36
+ const BACKEND_PREFIX = "[MemoraX Code Backend]:";
37
+ const GREEN = "\x1b[32m";
38
+ const BLUE = "\x1b[34m";
39
+ const RED = "\x1b[31m";
40
+ const BOLD = "\x1b[1m";
41
+ const RESET = "\x1b[0m";
42
+ const DSH_OPTIONAL_ENV = "MEMORAX_CODE_DSH_ADAPTER_OPTIONAL";
43
+
44
+ const skipCodexPluginInstall = truthyEnv(process.env.MEMORAX_CODE_SKIP_CODEX_PLUGIN_INSTALL);
45
+ const skipClaudeAdapterInstall = truthyEnv(process.env.MEMORAX_CODE_SKIP_CLAUDE_ADAPTER_INSTALL);
46
+ const skipOpenCodeAdapterInstall = truthyEnv(process.env.MEMORAX_CODE_SKIP_OPENCODE_ADAPTER_INSTALL);
47
+ const updateMode = truthyEnv(process.env.MEMORAX_CODE_SETUP_UPDATE);
48
+ const setupMode = setupModeFromEnvironment(process.env.MEMORAX_CODE_SETUP_MODE);
49
+ if (!setupMode) {
50
+ logRed("Setup mode is invalid.");
51
+ process.exit(1);
52
+ }
53
+
54
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
55
+ const memoraxCodeBin = join(scriptDir, "memorax-code.mjs");
56
+ const memoraxCliBin = join(scriptDir, "memorax-cli.mjs");
57
+ const verbose = process.env.MEMORAX_CODE_SETUP_VERBOSE === "1";
58
+ const codexRuntime = ensureCodexCommandEnv();
59
+ const codexCommand = codexRuntime.command;
60
+ const claudeRuntime = ensureClaudeCommandEnv();
61
+ const claudeCommand = claudeRuntime.command;
62
+ const previousClients = readPersistedClientSelection();
63
+ const existingSetup = previousClients !== undefined;
64
+ const persistedDshSelection = readPersistedDshSelection();
65
+ let dshProfiles = [];
66
+ let dshProfilesVerified = true;
67
+ try {
68
+ dshProfiles = discoverDshProfiles();
69
+ } catch (error) {
70
+ dshProfilesVerified = false;
71
+ const code = typeof error?.code === "string" ? ` (${error.code})` : "";
72
+ logRed(`DeepSeek Harness Profile discovery could not be verified${code}; DSH setup was skipped, but other client setup will continue.`);
73
+ }
74
+ const dshSelected = dshProfilesVerified
75
+ && dshProfiles.length > 0
76
+ && persistedDshSelection !== false;
77
+ if (!canPromptOnStderr()) {
78
+ logRed("Setup requires an interactive terminal.");
79
+ log("Run `memorax-code setup` from a terminal, or use `memorax-code status` to inspect an existing setup.");
80
+ process.exit(1);
81
+ }
82
+ const scriptedAnswers = process.stdin.isTTY !== true
83
+ ? parseScriptedAnswers(readFileSync(0, "utf8"))
84
+ : undefined;
85
+ if (seedMissingMemoraxCodeConfig() === "failed") {
86
+ printPostinstallSummary("not-verified");
87
+ process.exit(1);
88
+ }
89
+ let stagedHookRuntime;
90
+ try {
91
+ stagedHookRuntime = await stagePackagedClientHookRuntime({
92
+ packageRoot: dirname(scriptDir),
93
+ memoraxCodeHome: memoraxCodeHome(),
94
+ });
95
+ process.env.MEMORAX_CODE_DEFER_CLIENT_HOOK_RUNTIME_ACTIVATION = "1";
96
+ } catch (error) {
97
+ logRed(`Client Hook runtime staging failed: ${error instanceof Error ? error.message : String(error)}`);
98
+ logRed("Backend and plugin mutation were skipped; the previously active runtime remains authoritative.");
99
+ printPostinstallSummary("not-verified");
100
+ process.exit(1);
101
+ }
102
+ runCommonPreflight();
103
+ const requestedClients = ["codex", "claude", "opencode"];
104
+ const codexPreflight = requestedClients.includes("codex") && !skipCodexPluginInstall
105
+ ? runCodexPreflight({
106
+ integrationSelected: !existingSetup || previousClients.includes("codex"),
107
+ })
108
+ : { ok: true, pluginCache: { marketplaceName: CLI_MARKETPLACE_NAME, versions: [] } };
109
+ const claudePreflight = requestedClients.includes("claude") && !skipClaudeAdapterInstall
110
+ ? runClaudePreflight({
111
+ integrationSelected: !existingSetup || previousClients.includes("claude"),
112
+ })
113
+ : { ok: true };
114
+ const opencodePreflight = requestedClients.includes("opencode") && !skipOpenCodeAdapterInstall
115
+ ? runOpenCodePreflight({
116
+ integrationSelected: !existingSetup || previousClients.includes("opencode"),
117
+ })
118
+ : { ok: true };
119
+ const detectedClients = requestedClients.filter((client) => {
120
+ if (client === "codex") return !skipCodexPluginInstall && codexPreflight.ok;
121
+ if (client === "claude") return !skipClaudeAdapterInstall && claudePreflight.ok;
122
+ return !skipOpenCodeAdapterInstall && opencodePreflight.ok;
123
+ });
124
+ const selectedClients = existingSetup
125
+ ? await chooseUpdateClients(previousClients, detectedClients, scriptedAnswers)
126
+ : detectedClients;
127
+ const installClients = detectedClients.filter((client) => selectedClients.includes(client));
128
+ if (existingSetup) {
129
+ log(clientSelectionMessage(selectedClients, { dshSelected }));
130
+ } else {
131
+ log(detectedClientMessage(installClients, dshSelected ? dshProfiles : []));
132
+ }
133
+ if (dshSelected) {
134
+ log(`DeepSeek Harness profiles: found (${dshProfiles.map((profile) => profile.name).join(", ")}); supported profiles will be reconciled by \`memorax-code start\`.`);
135
+ } else if (dshProfiles.length > 0) {
136
+ log("DeepSeek Harness profiles were detected, but the DSH integration is disabled by [clients].dsh.");
137
+ }
138
+ if (requestedClients.includes("codex") && !skipCodexPluginInstall && !codexPreflight.ok) {
139
+ log("Codex runtime was not detected; skipping its adapter setup.");
140
+ }
141
+ if (requestedClients.includes("claude") && !skipClaudeAdapterInstall && !claudePreflight.ok) {
142
+ log("Claude Code runtime was not detected; skipping its adapter setup.");
143
+ }
144
+ if (requestedClients.includes("opencode") && !skipOpenCodeAdapterInstall && !opencodePreflight.ok) {
145
+ log("OpenCode runtime or configuration was not detected; skipping its adapter setup.");
146
+ }
147
+ if (writeClientSelectionConfig(selectedClients) === "failed") {
148
+ printPostinstallSummary("not-verified");
149
+ process.exit(1);
150
+ }
151
+ const clientMode = clientModeFor(installClients, {
152
+ includeDsh: dshProfilesVerified && persistedDshSelection !== false,
153
+ });
154
+ let memoraxConfigResult = "skipped";
155
+ if (!updateMode) {
156
+ const existingMemoraxStatus = setupMode === "automatic"
157
+ ? readMemoraxInstallStatus()
158
+ : undefined;
159
+ if (existingMemoraxStatus?.configured) {
160
+ printMemoraxDisclosure();
161
+ logGreen("Reusing the existing MemoraX connection and memory preferences.");
162
+ memoraxConfigResult = "preserved";
163
+ } else {
164
+ memoraxConfigResult = await maybeConfigureMemoraxMemory(scriptedAnswers, {
165
+ existingAccount: setupMode === "existing-account",
166
+ });
167
+ }
168
+ }
169
+ if (!updateMode && memoraxConfigResult !== "configured" && memoraxConfigResult !== "preserved") {
170
+ printPostinstallSummary("not-verified");
171
+ process.exit(1);
172
+ }
173
+ if (memoraxConfigResult === "configured") {
174
+ if (seedMissingMemoraxCodeConfig() === "failed") {
175
+ printPostinstallSummary("not-verified");
176
+ process.exit(1);
177
+ }
178
+ }
179
+ const codexClientEnabled = installClients.includes("codex");
180
+ const claudeClientEnabled = installClients.includes("claude");
181
+ const opencodeClientEnabled = installClients.includes("opencode");
182
+ const codexClientNewlyEnabled = codexClientEnabled
183
+ && existingSetup
184
+ && !previousClients.includes("codex");
185
+ const codexPluginRequiresActivation = codexClientEnabled
186
+ && codexPreflight.pluginCache.versions.length === 0;
187
+ const codexHooksBeforeUpdate = codexClientEnabled
188
+ && existingSetup
189
+ && !codexClientNewlyEnabled
190
+ && !codexPluginRequiresActivation
191
+ ? inspectCodexPluginHooksForUpdate()
192
+ : undefined;
193
+ const result = codexClientEnabled
194
+ ? runNodeMemoraxCodeCommand(["codex-plugin", "install", "--json"], { print: verbose })
195
+ : { status: 0 };
196
+
197
+ if (codexClientEnabled && result.status !== 0) {
198
+ logRed("MemoraX Code Codex plugin registration failed. Run `memorax-code setup` again after correcting the reported problem.");
199
+ printPostinstallSummary("not-verified");
200
+ process.exit(1);
201
+ }
202
+
203
+ if (codexClientEnabled && result.status === 0) {
204
+ if (existingSetup && (codexClientNewlyEnabled || codexPluginRequiresActivation)) {
205
+ activateCodexPluginHooks();
206
+ } else if (existingSetup) {
207
+ await maybeTrustUpdatedCodexPluginHooks(scriptedAnswers, codexHooksBeforeUpdate);
208
+ } else {
209
+ activateCodexPluginHooks();
210
+ }
211
+ }
212
+ const skipCodexAdapter = !codexClientEnabled;
213
+ const skipClaudeAdapter = !claudeClientEnabled;
214
+ const skipOpenCodeAdapter = !opencodeClientEnabled;
215
+ const codexSkipReason = setupClientSkipReason({
216
+ explicitlySkipped: skipCodexPluginInstall,
217
+ selected: selectedClients.includes("codex"),
218
+ enabled: codexClientEnabled,
219
+ });
220
+ const claudeSkipReason = setupClientSkipReason({
221
+ explicitlySkipped: skipClaudeAdapterInstall,
222
+ selected: selectedClients.includes("claude"),
223
+ enabled: claudeClientEnabled,
224
+ });
225
+ const opencodeSkipReason = setupClientSkipReason({
226
+ explicitlySkipped: skipOpenCodeAdapterInstall,
227
+ selected: selectedClients.includes("opencode"),
228
+ enabled: opencodeClientEnabled,
229
+ });
230
+
231
+ const backendAndAdapters = await startBackendAndCheck({
232
+ skipCodexAdapter,
233
+ clientMode,
234
+ codexSkipReason,
235
+ skipClaudeAdapter,
236
+ claudeAdapterRequired: !skipClaudeAdapter,
237
+ claudeSkipReason,
238
+ skipOpenCodeAdapter,
239
+ opencodeAdapterRequired: !skipOpenCodeAdapter,
240
+ opencodeSkipReason,
241
+ });
242
+ const backendAndAdaptersStatus = backendAndAdapters.status;
243
+ if (backendAndAdaptersStatus === "enabled") {
244
+ logGreen(`Client Hook runtime ${stagedHookRuntime.generationId} activated.`);
245
+ }
246
+ if (backendAndAdaptersStatus === "enabled") {
247
+ printNextSteps({
248
+ codexAdapterEnabled: !skipCodexAdapter,
249
+ claudeAdapterEnabled: !skipClaudeAdapter,
250
+ dshAdapterEnabled: backendAndAdapters.dshAdapterEnabled,
251
+ opencodeAdapterEnabled: !skipOpenCodeAdapter,
252
+ });
253
+ printCommonCommands({
254
+ codexAdapterEnabled: !skipCodexAdapter,
255
+ claudeAdapterEnabled: !skipClaudeAdapter,
256
+ opencodeAdapterEnabled: !skipOpenCodeAdapter,
257
+ });
258
+ }
259
+ printPostinstallSummary(
260
+ backendAndAdaptersStatus,
261
+ backendAndAdapters.dshAdapterUnavailable,
262
+ );
263
+ if (backendAndAdaptersStatus === "enabled") {
264
+ log("View local memory activity: http://127.0.0.1:8787/memory-viewer");
265
+ }
266
+ if (backendAndAdaptersStatus !== "enabled") process.exit(1);
267
+ if (!updateMode && readMemoraxInstallStatus()?.configured !== true) {
268
+ logRed("Setup could not verify a ready MemoraX connection after Backend reconciliation.");
269
+ process.exit(1);
270
+ }
271
+ try {
272
+ const completion = writeSetupCompletionRecord({
273
+ memoraxCodeHome: memoraxCodeHome(),
274
+ completedAt: new Date().toISOString(),
275
+ completedByVersion: packageVersion(),
276
+ });
277
+ if (completion.durability === "uncertain") {
278
+ logRed("Setup completed, but durable persistence of the completion record could not be confirmed.");
279
+ }
280
+ } catch (error) {
281
+ logRed(`Setup completion could not be recorded: ${error instanceof Error ? error.message : String(error)}`);
282
+ process.exit(1);
283
+ }
284
+ logGreen("Setup completed successfully.");
285
+ process.exit(0);
286
+
287
+ async function maybeConfigureMemoraxMemory(scriptedAnswers, {
288
+ existingAccount = false,
289
+ showDisclosure = true,
290
+ } = {}) {
291
+ if (showDisclosure) printMemoraxDisclosure();
292
+ const detectedPreferences = detectSetupMemoryPreferences();
293
+ if (scriptedAnswers) {
294
+ return await configureMemoraxMemoryFromAnswers(scriptedAnswers, detectedPreferences, {
295
+ existingAccount,
296
+ });
297
+ }
298
+ let rl = createInterface({ input: process.stdin, output: process.stderr });
299
+ try {
300
+ const userId = existingAccount
301
+ ? await questionExistingAccountUserId(rl, detectedPreferences.userId)
302
+ : detectedPreferences.userId
303
+ ?? (await rl.question(`${PREFIX} Username (used for your memories): `)).trim();
304
+ const outputLanguage = detectedPreferences.outputLanguage
305
+ ?? await questionPreferredLanguage(rl);
306
+ printDetectedMemoryPreferences(detectedPreferences, { includeUserId: !existingAccount });
307
+ if (existingAccount) {
308
+ rl.close();
309
+ rl = undefined;
310
+ const apiKey = (await questionMasked(`${PREFIX} MemoraX API key: `)).trim();
311
+ return await writeMemoraxConfigFromInput({
312
+ userId,
313
+ endpoint: memoraxInstallEndpoint(),
314
+ outputLanguage,
315
+ existingAccount,
316
+ apiKey,
317
+ });
318
+ }
319
+ return await writeMemoraxConfigFromInput({
320
+ userId,
321
+ endpoint: memoraxInstallEndpoint(),
322
+ outputLanguage,
323
+ existingAccount,
324
+ });
325
+ } finally {
326
+ rl?.close();
327
+ }
328
+ }
329
+
330
+ async function chooseUpdateClients(previousClients, detectedClients, scriptedAnswers) {
331
+ const selected = new Set(previousClients);
332
+ const availableDisabledClients = detectedClients.filter((client) => !selected.has(client));
333
+ if (availableDisabledClients.length === 0) return [...previousClients];
334
+
335
+ let rl;
336
+ try {
337
+ for (const client of availableDisabledClients) {
338
+ const label = clientLabel(client);
339
+ const question = `${label} runtime is available, but its integration is disabled in [clients]. Enable it now? [Y/n]`;
340
+ let answer;
341
+ if (scriptedAnswers) {
342
+ log(question);
343
+ answer = String(scriptedAnswers.shift() ?? "").trim();
344
+ } else {
345
+ rl ??= createInterface({ input: process.stdin, output: process.stderr });
346
+ answer = (await rl.question(`${PREFIX} ${question} `)).trim();
347
+ }
348
+ if (/^n(?:o)?$/i.test(answer)) {
349
+ log(`Keeping the ${label} integration disabled.`);
350
+ } else {
351
+ selected.add(client);
352
+ logGreen(`Enabling the ${label} integration.`);
353
+ }
354
+ }
355
+ } finally {
356
+ rl?.close();
357
+ }
358
+ return ["codex", "claude", "opencode"].filter((client) => selected.has(client));
359
+ }
360
+
361
+ async function configureMemoraxMemoryFromAnswers(answers, detectedPreferences, {
362
+ existingAccount = false,
363
+ } = {}) {
364
+ const userIdAnswer = existingAccount
365
+ ? String(answers.shift() ?? "").trim()
366
+ : undefined;
367
+ const userId = existingAccount
368
+ ? userIdAnswer || detectedPreferences.userId
369
+ : detectedPreferences.userId ?? String(answers.shift() ?? "").trim();
370
+ if (existingAccount) {
371
+ log(`Username: ${userIdAnswer ? "<provided>" : detectedPreferences.userId ? "<default>" : "<missing>"}`);
372
+ } else if (!detectedPreferences.userId) {
373
+ log(`Username: ${userId ? "<provided>" : "<missing>"}`);
374
+ }
375
+ const outputLanguageAnswer = detectedPreferences.outputLanguage
376
+ ? undefined
377
+ : String(answers.shift() ?? "").trim();
378
+ const outputLanguage = detectedPreferences.outputLanguage
379
+ ?? preferredLanguage(outputLanguageAnswer);
380
+ if (!detectedPreferences.outputLanguage) {
381
+ log(`Preferred language [ZH/en] (used for Memory extraction): ${outputLanguageAnswer ? "<provided>" : "<default>"}`);
382
+ }
383
+ printDetectedMemoryPreferences(detectedPreferences, { includeUserId: !existingAccount });
384
+ const apiKey = existingAccount
385
+ ? String(answers.shift() ?? "").trim()
386
+ : undefined;
387
+ if (existingAccount) log(`MemoraX API key: ${apiKey ? "<provided>" : "<missing>"}`);
388
+ return await writeMemoraxConfigFromInput({
389
+ userId,
390
+ endpoint: memoraxInstallEndpoint(),
391
+ outputLanguage,
392
+ existingAccount,
393
+ apiKey,
394
+ });
395
+ }
396
+
397
+ function printDetectedMemoryPreferences(preferences, { includeUserId = true } = {}) {
398
+ if (includeUserId && preferences.userId) {
399
+ log(`Username: ${preferences.userId} (detected from the system account).`);
400
+ }
401
+ if (preferences.outputLanguage) {
402
+ log(`Preferred language: ${preferences.outputLanguage} (detected from system settings).`);
403
+ }
404
+ }
405
+
406
+ function printMemoraxDisclosure() {
407
+ log("MemoraX Code requires MemoraX for its core remote-memory functionality.");
408
+ log("After connection, trusted repository sessions automatically send selected user prompts and final assistant answers to MemoraX after replies.");
409
+ log("Newly generated configuration enables automatic writeback. Existing configuration is never enabled implicitly; disable it with `[memory.writeback] enabled = false` or `MEMORAX_CODE_MEMORAX_WRITEBACK_ENABLED=false`.");
410
+ }
411
+
412
+ async function writeMemoraxConfigFromInput({
413
+ userId,
414
+ endpoint,
415
+ outputLanguage,
416
+ existingAccount,
417
+ apiKey,
418
+ }) {
419
+ if (!userId) {
420
+ logRed("MemoraX config was not written because username was empty.");
421
+ return "failed";
422
+ }
423
+ if (!outputLanguage) {
424
+ logRed("MemoraX config was not written because preferred language must be zh or en.");
425
+ return "failed";
426
+ }
427
+ if (existingAccount) {
428
+ if (!apiKey) {
429
+ logRed("MemoraX config was not written because API key was empty.");
430
+ return "failed";
431
+ }
432
+ if (writeMemoraxConfig({ userId, endpoint, outputLanguage, apiKey }) === "failed") {
433
+ logRed("MemoraX config was not written because the existing config could not be safely updated.");
434
+ return "failed";
435
+ }
436
+ logGreen("Existing MemoraX account connection configured.");
437
+ logGreen(`MemoraX config written to ${memoraxCodeConfigPath()}.`);
438
+ log("MemoraX network access will be checked by the first workspace-scoped memory request from a trusted workspace.");
439
+ return "configured";
440
+ }
441
+ log("Creating or restoring a secure MemoraX credential...");
442
+ let trialCredential;
443
+ try {
444
+ trialCredential = await ensureTrialSetupCredential({
445
+ memoraxCodeHome: memoraxCodeHome(),
446
+ env: process.env,
447
+ });
448
+ } catch (error) {
449
+ const reason = typeof error?.reason === "string" ? error.reason : "credential_unavailable";
450
+ logRed(`Secure MemoraX credential setup failed (${reason}).`);
451
+ return "failed";
452
+ }
453
+ if (writeMemoraxConfig({
454
+ userId,
455
+ endpoint,
456
+ outputLanguage,
457
+ apiKey: trialCredential.apiKey,
458
+ }) === "failed") {
459
+ logRed("MemoraX config was not written because the existing config could not be safely updated.");
460
+ return "failed";
461
+ }
462
+ logGreen("Secure MemoraX credential is ready.");
463
+ logGreen(`MemoraX config written to ${memoraxCodeConfigPath()}.`);
464
+ log("MemoraX network access will be checked by the first workspace-scoped memory request from a trusted workspace.");
465
+ return "configured";
466
+ }
467
+
468
+ async function questionPreferredLanguage(rl) {
469
+ while (true) {
470
+ const answer = await rl.question(`${PREFIX} Preferred language [ZH/en] (used for Memory extraction): `);
471
+ const language = preferredLanguage(answer);
472
+ if (language) return language;
473
+ logRed("Preferred language must be zh or en.");
474
+ }
475
+ }
476
+
477
+ async function questionExistingAccountUserId(rl, detectedUserId) {
478
+ const defaultLabel = detectedUserId ? ` [${detectedUserId}]` : "";
479
+ const answer = await rl.question(`${PREFIX} Username from your existing MemoraX Code setup${defaultLabel}: `);
480
+ return answer.trim() || detectedUserId;
481
+ }
482
+
483
+ async function questionMasked(prompt) {
484
+ if (process.stdin.isTTY !== true || typeof process.stdin.setRawMode !== "function") {
485
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
486
+ try {
487
+ return await rl.question(prompt);
488
+ } finally {
489
+ rl.close();
490
+ }
491
+ }
492
+ process.stderr.write(prompt);
493
+ const wasRaw = process.stdin.isRaw === true;
494
+ process.stdin.setRawMode(true);
495
+ process.stdin.resume();
496
+ return await new Promise((resolve, reject) => {
497
+ let answer = "";
498
+ const cleanup = () => {
499
+ process.stdin.off("data", onData);
500
+ process.stdin.setRawMode(wasRaw);
501
+ if (!wasRaw) process.stdin.pause();
502
+ };
503
+ const finish = () => {
504
+ cleanup();
505
+ process.stderr.write("\n");
506
+ resolve(answer);
507
+ };
508
+ const onData = (chunk) => {
509
+ for (const char of String(chunk)) {
510
+ if (char === "\u0003") {
511
+ cleanup();
512
+ process.stderr.write("\n");
513
+ reject(new Error("Interrupted"));
514
+ return;
515
+ }
516
+ if (char === "\r" || char === "\n") {
517
+ finish();
518
+ return;
519
+ }
520
+ if (char === "\u0008" || char === "\u007f") {
521
+ if (answer.length > 0) {
522
+ answer = answer.slice(0, -1);
523
+ process.stderr.write("\b \b");
524
+ }
525
+ continue;
526
+ }
527
+ if (char >= " " && char !== "\u007f") {
528
+ answer += char;
529
+ process.stderr.write("*");
530
+ }
531
+ }
532
+ };
533
+ process.stdin.on("data", onData);
534
+ });
535
+ }
536
+
537
+ function preferredLanguage(value) {
538
+ const trimmed = String(value ?? "").trim();
539
+ return trimmed
540
+ ? normalizeMemoraxMemoryOutputLanguage(trimmed)
541
+ : MEMORAX_DEFAULT_MEMORY_OUTPUT_LANGUAGE;
542
+ }
543
+
544
+ function memoraxInstallEndpoint() {
545
+ return normalizeMemoraxBaseUrl(process.env.MEMORAX_CODE_MEMORAX_ENDPOINT) || MEMORAX_DEFAULT_BASE_URL;
546
+ }
547
+
548
+ function isRecord(value) {
549
+ return typeof value === "object" && value !== null && !Array.isArray(value);
550
+ }
551
+
552
+ function inspectCodexPluginHooksForUpdate() {
553
+ const result = runNodeMemoraxCodeCommand(codexPluginHookArgs("hooks"), { print: false });
554
+ const report = parseCodexPluginHookReport(result, "codex-plugin-hooks");
555
+ return report?.hooks;
556
+ }
557
+
558
+ async function maybeTrustUpdatedCodexPluginHooks(scriptedAnswers, previousHooks) {
559
+ if (!previousHooks) {
560
+ warnUpdatedHookTrustSkipped("Existing Codex hooks could not be inspected before the plugin cache refresh.");
561
+ return "skipped";
562
+ }
563
+ const checkEnv = hookTrustCommandEnv(PREVIOUS_HOOKS_ENV, previousHooks);
564
+ const checked = runNodeMemoraxCodeCommand(codexPluginHookArgs("trust-hooks", ["--check"]), {
565
+ env: checkEnv,
566
+ print: false,
567
+ });
568
+ const report = parseCodexPluginHookReport(checked, "codex-plugin-trust-hooks");
569
+ if (!report) {
570
+ warnUpdatedHookTrustSkipped("Updated Codex hooks could not be inspected after the plugin cache refresh.");
571
+ return "skipped";
572
+ }
573
+ if (report.requiresFullReview) {
574
+ warnUpdatedHookTrustSkipped("The MemoraX Code Codex plugin marketplace identity changed during the update, so incremental Hook authorization was not applied.");
575
+ return "skipped";
576
+ }
577
+ if (report.hooks.length === 0) return "unchanged";
578
+
579
+ printUpdatedCodexHooks(report.hooks, previousHooks);
580
+ if (!canPromptForUpdate()) {
581
+ warnUpdatedHookTrustSkipped("This update is running without an interactive terminal, so the new or changed Hooks remain untrusted.");
582
+ return "skipped";
583
+ }
584
+ const answer = await updatedHookTrustAnswer(scriptedAnswers);
585
+ if (answer === undefined) {
586
+ warnUpdatedHookTrustSkipped("No Hook authorization response was received, so the new or changed Hooks remain untrusted.");
587
+ return "skipped";
588
+ }
589
+ const normalizedAnswer = answer.trim();
590
+ if (normalizedAnswer && !/^y(?:es)?$/i.test(normalizedAnswer)) {
591
+ warnUpdatedHookTrustSkipped("Hook authorization was declined, so the new or changed Hooks remain untrusted.");
592
+ return "skipped";
593
+ }
594
+
595
+ const trustEnv = hookTrustCommandEnv(HOOK_TRUST_SELECTION_ENV, report.hooks);
596
+ const trusted = runNodeMemoraxCodeCommand(codexPluginHookArgs("trust-hooks", ["--yes"]), {
597
+ env: trustEnv,
598
+ print: verbose,
599
+ });
600
+ if (trusted.status === 0) {
601
+ logGreen(`Trusted ${report.hooks.length} new or changed MemoraX Code Codex Hook${report.hooks.length === 1 ? "" : "s"}.`);
602
+ return "trusted";
603
+ }
604
+ warnUpdatedHookTrustSkipped("The reviewed Hooks changed again or could not be written to Codex config.");
605
+ return "failed";
606
+ }
607
+
608
+ function parseCodexPluginHookReport(result, action) {
609
+ if (result.status !== 0) return undefined;
610
+ try {
611
+ const report = JSON.parse(String(result.stdout ?? ""));
612
+ if (!report || report.ok !== true || report.action !== action || !Array.isArray(report.hooks)) return undefined;
613
+ if (!report.hooks.every((hook) => hook
614
+ && typeof hook === "object"
615
+ && typeof hook.key === "string"
616
+ && typeof hook.currentHash === "string"
617
+ && hook.handlerType === "command"
618
+ && typeof hook.eventName === "string"
619
+ && hook.eventName.length > 0
620
+ && typeof hook.command === "string"
621
+ && hook.command.length > 0
622
+ && isCodexHookTrustStatus(hook.trustStatus))) return undefined;
623
+ if (action === "codex-plugin-trust-hooks"
624
+ && (typeof report.requiresFullReview !== "boolean"
625
+ || !report.hooks.every((hook) => hook.trustStatus === "untrusted" || hook.trustStatus === "modified"))) {
626
+ return undefined;
627
+ }
628
+ return report;
629
+ } catch {
630
+ return undefined;
631
+ }
632
+ }
633
+
634
+ function isCodexHookTrustStatus(value) {
635
+ return value === "managed" || value === "untrusted" || value === "trusted" || value === "modified";
636
+ }
637
+
638
+ function codexPluginHookArgs(subcommand, extraArgs = []) {
639
+ const args = ["codex-plugin", subcommand, ...extraArgs];
640
+ const codexCommand = stringOption(process.env.MEMORAX_CODE_CODEX_COMMAND);
641
+ if (codexCommand) args.push("--codex-command", codexCommand);
642
+ const workspace = codexHookInspectionWorkspace();
643
+ if (workspace) args.push("--workspace", workspace);
644
+ args.push("--json");
645
+ return args;
646
+ }
647
+
648
+ function codexHookInspectionWorkspace() {
649
+ for (const candidate of [process.env.INIT_CWD, process.env.HOME, homedir(), process.cwd()]) {
650
+ if (candidate && existsSync(candidate)) return candidate;
651
+ }
652
+ return undefined;
653
+ }
654
+
655
+ function hookTrustCommandEnv(name, hooks) {
656
+ const env = { ...process.env };
657
+ delete env[PREVIOUS_HOOKS_ENV];
658
+ delete env[HOOK_TRUST_SELECTION_ENV];
659
+ env[name] = JSON.stringify(hooks);
660
+ return env;
661
+ }
662
+
663
+ function printUpdatedCodexHooks(hooks, previousHooks) {
664
+ const previousByKey = new Map(previousHooks.map((hook) => [hook.key, hook.currentHash]));
665
+ log("This MemoraX Code update includes new or changed Codex Hooks that require authorization:");
666
+ for (const hook of hooks) {
667
+ const change = previousByKey.has(hook.key) ? "changed" : "new";
668
+ const label = hook.statusMessage ? ` (${hook.statusMessage})` : "";
669
+ log(`- ${change}: ${hook.key}${label}`);
670
+ if (hook.eventName) log(` event: ${hook.eventName}`);
671
+ if (hook.command) log(` command: ${hook.command}`);
672
+ }
673
+ }
674
+
675
+ async function updatedHookTrustAnswer(scriptedAnswers) {
676
+ if (scriptedAnswers) {
677
+ log("Trust these new or changed Codex Hooks? [Y/n]");
678
+ return scriptedAnswers.shift();
679
+ }
680
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
681
+ try {
682
+ return await rl.question(`${PREFIX} Trust these new or changed Codex Hooks? [Y/n] `);
683
+ } finally {
684
+ rl.close();
685
+ }
686
+ }
687
+
688
+ function parseScriptedAnswers(input) {
689
+ if (input.length === 0) return [];
690
+ const answers = input.split(/\r?\n/);
691
+ if (answers.at(-1) === "") answers.pop();
692
+ return answers;
693
+ }
694
+
695
+ function warnUpdatedHookTrustSkipped(message) {
696
+ logRed(message);
697
+ logRed("Review and authorize the current MemoraX Code Codex Hooks with `memorax-code codex-plugin trust-hooks`.");
698
+ }
699
+
700
+ function activateCodexPluginHooks() {
701
+ const activated = runNodeMemoraxCodeCommand(["codex-plugin", "activate", "--yes"], { print: verbose });
702
+ if (activated.status === 0) {
703
+ logGreen("MemoraX Code Codex Adapter hooks activated and trusted.");
704
+ return "activated";
705
+ }
706
+ logRed("Codex hook activation failed; run `memorax-code codex-plugin activate --yes` after installation.");
707
+ return "failed";
708
+ }
709
+
710
+ function canPromptForUpdate() {
711
+ return canPromptOnStderr();
712
+ }
713
+
714
+ function canPromptOnStderr() {
715
+ return process.env.MEMORAX_CODE_SETUP_ASSUME_INTERACTIVE === "1"
716
+ || (process.stdin.isTTY === true && process.stderr.isTTY === true);
717
+ }
718
+
719
+ function truthyEnv(value) {
720
+ return ["1", "true", "yes"].includes(String(value ?? "").toLowerCase());
721
+ }
722
+
723
+ function setupModeFromEnvironment(value) {
724
+ const mode = String(value ?? "").trim();
725
+ if (!mode || mode === "automatic") return "automatic";
726
+ if (mode === "existing-account" || mode === "reconfigure") return mode;
727
+ return undefined;
728
+ }
729
+
730
+ function memoraxCodeHome() {
731
+ return process.env.MEMORAX_CODE_HOME || join(homedir(), ".memorax-code");
732
+ }
733
+
734
+ function memoraxCodeConfigPath() {
735
+ return join(memoraxCodeHome(), "config.toml");
736
+ }
737
+
738
+ function readPersistedClientSelection() {
739
+ const path = memoraxCodeConfigPath();
740
+ if (!existsSync(path)) return undefined;
741
+ try {
742
+ const clients = parse(readFileSync(path, "utf8"))?.clients;
743
+ if (!clients || typeof clients !== "object" || typeof clients.codex !== "boolean" || typeof clients.claude !== "boolean") return undefined;
744
+ return [
745
+ clients.codex ? "codex" : undefined,
746
+ clients.claude ? "claude" : undefined,
747
+ clients.opencode === true ? "opencode" : undefined,
748
+ ].filter(Boolean);
749
+ } catch {
750
+ return undefined;
751
+ }
752
+ }
753
+
754
+ function readPersistedDshSelection() {
755
+ const path = memoraxCodeConfigPath();
756
+ if (!existsSync(path)) return undefined;
757
+ try {
758
+ const value = parse(readFileSync(path, "utf8"))?.clients?.dsh;
759
+ return typeof value === "boolean" ? value : undefined;
760
+ } catch {
761
+ return undefined;
762
+ }
763
+ }
764
+
765
+ function writeClientSelectionConfig(clients) {
766
+ const path = memoraxCodeConfigPath();
767
+ return updateConfigFileAtomically({
768
+ path,
769
+ defaultText: setManagedClientSelection(defaultMemoraxCodeConfig(), clients),
770
+ transform: (text) => setManagedClientSelection(text, clients),
771
+ parseToml: parse,
772
+ warn: (message) => log(message),
773
+ });
774
+ }
775
+
776
+ function setManagedClientSelection(text, clients) {
777
+ const withOpenCode = setTomlField(text, "clients", "opencode", String(clients.includes("opencode")));
778
+ const withClaude = setTomlField(withOpenCode, "clients", "claude", String(clients.includes("claude")));
779
+ return setTomlField(withClaude, "clients", "codex", String(clients.includes("codex")));
780
+ }
781
+
782
+ function writeMemoraxConfig({ userId, endpoint, outputLanguage, apiKey }) {
783
+ const path = memoraxCodeConfigPath();
784
+ const fields = [
785
+ {
786
+ key: "endpoint",
787
+ line: `endpoint = "${tomlString(endpoint || MEMORAX_DEFAULT_BASE_URL)}" # MemoraX service URL.`,
788
+ },
789
+ {
790
+ key: "user_id",
791
+ line: `user_id = "${tomlString(userId)}" # Stable username; requests derive a workspace-scoped namespace.`,
792
+ },
793
+ ];
794
+ const addFields = [{
795
+ key: "output_language",
796
+ line: `output_language = "${outputLanguage}" # Language for newly generated MemoraX memories.`,
797
+ }];
798
+ const applyFields = (text) => {
799
+ const withSelectedApiKey = apiKey
800
+ ? setTomlSectionFields(text, "memorax", [{
801
+ key: "api_key",
802
+ line: `api_key = "${tomlString(apiKey)}" # MemoraX API key used by the local Backend.`,
803
+ }])
804
+ : setTomlField(text, "memorax", "api_key", undefined);
805
+ return setTomlSectionFields(
806
+ setTomlSectionFields(withSelectedApiKey, "memorax", fields),
807
+ "memory.add",
808
+ addFields,
809
+ );
810
+ };
811
+ return updateConfigFileAtomically({
812
+ path,
813
+ defaultText: applyFields(defaultMemoraxCodeConfig()),
814
+ transform: applyFields,
815
+ parseToml: parse,
816
+ warn: (message) => log(message),
817
+ });
818
+ }
819
+
820
+ function defaultMemoraxCodeConfig() {
821
+ return [
822
+ "# MemoraX Code local config.",
823
+ "# This file is read from $MEMORAX_CODE_HOME/config.toml.",
824
+ "# Environment variables still override values written here.",
825
+ "# See docs/configuration.md for advanced tuning fields and effective defaults.",
826
+ "",
827
+ "[clients]",
828
+ "codex = true # Manage the Codex adapter.",
829
+ "claude = true # Manage the Claude adapter.",
830
+ "dsh = true # Manage the DeepSeek Harness adapter when Profiles exist.",
831
+ "opencode = true # Manage the OpenCode adapter.",
832
+ "",
833
+ "# MemoraX remote-memory connection.",
834
+ "[memorax]",
835
+ `# endpoint = "${MEMORAX_DEFAULT_BASE_URL}" # MemoraX service URL.`,
836
+ '# user_id = "" # Stable username; requests derive a workspace-scoped namespace.',
837
+ "",
838
+ "# Automatic Hook retrieval is opt-in.",
839
+ "[memory.retrieval]",
840
+ "enabled = false # Auto-inject retrieved memories into supported client prompts.",
841
+ "",
842
+ "# Automatic writeback sends selected prompts and final answers to MemoraX.",
843
+ "[memory.writeback]",
844
+ "enabled = true # Allow supported client sessions to write memories after replies.",
845
+ "",
846
+ "# Preferred language for newly generated MemoraX memories.",
847
+ "[memory.add]",
848
+ `output_language = "${MEMORAX_DEFAULT_MEMORY_OUTPUT_LANGUAGE}" # Language for newly generated MemoraX memories.`,
849
+ "",
850
+ "# Controls how often supported native client sessions see the MemoraX Code skill reminder.",
851
+ "[memory.skill_reminder]",
852
+ "interval_turns = 5 # Show the MemoraX Code skill reminder every N native client turns, starting on the first turn.",
853
+ "",
854
+ "# Relevant repo reads batch generated repo-memory updates by commit count or elapsed time.",
855
+ "[memory.repo_update]",
856
+ 'policy = "adaptive" # every-commit / commit-count / daily / pull-request / pull-request-or-daily / adaptive.',
857
+ "commit_threshold = 5 # Pending local commits needed by commit-count and adaptive.",
858
+ "cooldown_hours = 24 # Pending-commit age used by daily, pull-request-or-daily, and adaptive.",
859
+ "",
860
+ "# Local traces may contain prompts, responses, recalled memories, and local paths.",
861
+ "[trace.codex]",
862
+ "enabled = true # Enable local Codex session memory trace collection.",
863
+ "capture_content = true # Store content in local Codex trace events.",
864
+ "",
865
+ "[trace.claude]",
866
+ "enabled = true # Enable local Claude session memory trace collection.",
867
+ "capture_content = true # Store content in local Claude trace events.",
868
+ "",
869
+ "[trace.dsh]",
870
+ "enabled = true # Enable local DSH session memory trace collection.",
871
+ "capture_content = true # Store content in local DSH trace events.",
872
+ "",
873
+ "[trace.opencode]",
874
+ "enabled = true # Enable local OpenCode session memory trace collection.",
875
+ "capture_content = true # Store content in local OpenCode trace events.",
876
+ "",
877
+ ].join("\n");
878
+ }
879
+
880
+ function seedMissingMemoraxCodeConfig() {
881
+ const path = memoraxCodeConfigPath();
882
+ return updateConfigFileAtomically({
883
+ path,
884
+ defaultText: defaultMemoraxCodeConfig(),
885
+ transform: (text) => text,
886
+ parseToml: parse,
887
+ warn: (message) => log(message),
888
+ });
889
+ }
890
+
891
+ function setTomlSectionFields(text, section, fields) {
892
+ const source = String(text ?? "");
893
+ const newline = source.includes("\r\n") ? "\r\n" : "\n";
894
+ const lines = source.split(/\r?\n/);
895
+ const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
896
+ const header = new RegExp(`^\\s*\\[(?:${escaped}|"${escaped}"|'${escaped}')\\]\\s*(?:#.*)?$`);
897
+ const start = lines.findIndex((line) => header.test(line));
898
+ if (start === -1) {
899
+ const separator = source.length === 0
900
+ ? ""
901
+ : source.endsWith(`${newline}${newline}`)
902
+ ? ""
903
+ : source.endsWith(newline)
904
+ ? newline
905
+ : `${newline}${newline}`;
906
+ return `${source}${separator}[${section}]${newline}${fields.map((field) => field.line).join(newline)}${newline}`;
907
+ }
908
+
909
+ const nextHeader = lines.findIndex((line, index) => index > start && /^\s*\[/.test(line));
910
+ const end = nextHeader === -1 ? lines.length : nextHeader;
911
+ const keys = fields
912
+ .map(({ key }) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
913
+ .flatMap((key) => [key, `"${key}"`, `'${key}'`])
914
+ .join("|");
915
+ const assignment = new RegExp(`^\\s*#?\\s*(?:${keys})\\s*=`);
916
+ const preservedBody = lines
917
+ .slice(start + 1, end)
918
+ .filter((line) => !assignment.test(line));
919
+ lines.splice(
920
+ start + 1,
921
+ end - start - 1,
922
+ ...fields.map((field) => field.line),
923
+ ...preservedBody,
924
+ );
925
+ return lines.join(newline);
926
+ }
927
+
928
+ function tomlString(value) {
929
+ return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
930
+ }
931
+
932
+ function runCommonPreflight() {
933
+ log("Checking local setup state...");
934
+ if (existingSetup) log("Existing setup detected; preserving configured client intent while checking availability.");
935
+ const memoraxCodeVersion = runNodeMemoraxCodeCommand(["--version"], { print: false });
936
+ log(`MemoraX Code backend package: ${commandSummary(memoraxCodeVersion) ?? packageVersionSummary()}`);
937
+ if (skipCodexPluginInstall) {
938
+ log("Codex plugin registration is disabled for this setup; other client setup can still continue.");
939
+ }
940
+ if (skipClaudeAdapterInstall) {
941
+ log("Claude Code adapter setup is disabled for this setup; other client setup can still continue.");
942
+ }
943
+ if (skipOpenCodeAdapterInstall) {
944
+ log("OpenCode adapter setup is disabled for this setup; other client setup can still continue.");
945
+ }
946
+ return {};
947
+ }
948
+
949
+ function runCodexPreflight({ integrationSelected = true } = {}) {
950
+ const version = runExternalCommand(codexCommand, ["--version"], { print: false, timeout: 10_000 });
951
+ const runtimeLabel = codexRuntime.source === "app-bundled"
952
+ ? "Codex App runtime"
953
+ : codexRuntime.source === "vscode-bundled"
954
+ ? "Codex VS Code runtime"
955
+ : "Codex CLI";
956
+ log(`${runtimeLabel}: ${commandSummary(version) ?? "not runnable"}`);
957
+ if (version.status !== 0) {
958
+ return { ok: false, pluginCache: { marketplaceName: CLI_MARKETPLACE_NAME, versions: [] } };
959
+ }
960
+ const pluginCache = installedPluginCache();
961
+ log(`Existing Codex plugin cache: ${pluginCache.versions.length > 0 ? `found (${pluginCache.versions.join(", ")})` : "not installed"}`);
962
+ log(`Codex client process: ${codexClientRunning() ? "running" : "not detected"}`);
963
+ log(integrationSelected
964
+ ? "Keeping Codex provider config unchanged and enabling the shared memory hook integration."
965
+ : "Keeping Codex provider config unchanged while checking whether to enable its integration.");
966
+ return { ok: true, pluginCache };
967
+ }
968
+
969
+ function runClaudePreflight({ integrationSelected = true } = {}) {
970
+ const version = runExternalCommand(claudeCommand, ["--version"], { print: false, timeout: 10_000 });
971
+ const runtimeLabel = claudeRuntime.source === "app-bundled"
972
+ ? "Claude Code App runtime"
973
+ : claudeRuntime.source === "vscode-bundled"
974
+ ? "Claude VS Code runtime"
975
+ : "Claude CLI";
976
+ log(`${runtimeLabel}: ${commandSummary(version) ?? "not runnable"}`);
977
+ if (version.status !== 0) return { ok: false };
978
+ log(integrationSelected
979
+ ? "Keeping Claude Code provider config unchanged and enabling the shared memory Hook integration."
980
+ : "Keeping Claude Code provider config unchanged while checking whether to enable its integration.");
981
+ return { ok: true };
982
+ }
983
+
984
+ function runOpenCodePreflight({ integrationSelected = true } = {}) {
985
+ const explicitConfigDir = stringOption(process.env.OPENCODE_CONFIG_DIR);
986
+ const configHome = stringOption(process.env.XDG_CONFIG_HOME) ?? join(homedir(), ".config");
987
+ const configDir = resolve(explicitConfigDir ?? join(configHome, "opencode"));
988
+ const configDetected = explicitConfigDir !== undefined || existsSync(configDir);
989
+ const cliDetected = commandOnPath(
990
+ "opencode",
991
+ process.env.PATH,
992
+ process.platform,
993
+ process.env.PATHEXT,
994
+ );
995
+ const detected = configDetected || cliDetected;
996
+ log(`OpenCode configuration: ${configDetected ? `found (${configDir})` : "not detected"}`);
997
+ log(`OpenCode CLI: ${cliDetected ? "found in PATH" : "not detected"}`);
998
+ if (!detected) return { ok: false };
999
+ log(integrationSelected
1000
+ ? "Keeping OpenCode provider config unchanged and enabling the shared memory plugin integration."
1001
+ : "Keeping OpenCode provider config unchanged while checking whether to enable its integration.");
1002
+ return { ok: true };
1003
+ }
1004
+
1005
+ function installedPluginCache() {
1006
+ for (const marketplaceName of [CLI_MARKETPLACE_NAME, PERSONAL_MARKETPLACE_NAME]) {
1007
+ const versions = installedPluginCacheVersions(marketplaceName);
1008
+ if (versions.length > 0) {
1009
+ return { marketplaceName, versions };
1010
+ }
1011
+ }
1012
+ return { marketplaceName: CLI_MARKETPLACE_NAME, versions: [] };
1013
+ }
1014
+
1015
+ function installedPluginCacheVersions(marketplaceName) {
1016
+ const cacheRoot = join(codexHome(), "plugins", "cache", marketplaceName, PLUGIN_NAME);
1017
+ if (!existsSync(cacheRoot)) return [];
1018
+ try {
1019
+ return readdirSync(cacheRoot, { withFileTypes: true })
1020
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
1021
+ .map((entry) => entry.name)
1022
+ .sort();
1023
+ } catch {
1024
+ return [];
1025
+ }
1026
+ }
1027
+
1028
+ async function startBackendAndCheck({
1029
+ skipCodexAdapter = false,
1030
+ clientMode = "all",
1031
+ codexSkipReason,
1032
+ skipClaudeAdapter = false,
1033
+ claudeAdapterRequired = !skipClaudeAdapter,
1034
+ claudeSkipReason,
1035
+ skipOpenCodeAdapter = false,
1036
+ opencodeAdapterRequired = !skipOpenCodeAdapter,
1037
+ opencodeSkipReason,
1038
+ } = {}) {
1039
+ const adapterFlags = clientLifecycleFlags({ clientMode });
1040
+ const startArgs = ["start", ...adapterFlags];
1041
+ const statusArgs = ["status", ...adapterFlags];
1042
+ const optionalDshEnv = { [DSH_OPTIONAL_ENV]: "1" };
1043
+ let statusResult;
1044
+ const result = await reconcileSetup({
1045
+ start: () => runMemoraxCodeCommand(startArgs, {
1046
+ ...pendingClientHookRuntimeEnv(),
1047
+ ...optionalDshEnv,
1048
+ }),
1049
+ stop: () => runMemoraxCodeCommand(["stop", ...adapterFlags]),
1050
+ status: () => {
1051
+ statusResult = runMemoraxCodeCommand(statusArgs, optionalDshEnv);
1052
+ return statusResult;
1053
+ },
1054
+ isReady: (checked) => memoraxCodeEnabled(checked, {
1055
+ codexAdapterRequired: !skipCodexAdapter,
1056
+ claudeAdapterRequired,
1057
+ opencodeAdapterRequired,
1058
+ }),
1059
+ onEvent: (event) => {
1060
+ if (event.type === "start" && event.attempt === 1) {
1061
+ logGreen("Starting backend with `memorax-code start`...");
1062
+ } else if (event.type === "start-failed" && event.attempt === 1) {
1063
+ logRed("Backend start failed during setup.");
1064
+ } else if (event.type === "stop") {
1065
+ logRed("Attempting automatic recovery: `memorax-code stop` then `memorax-code start`...");
1066
+ } else if (event.type === "diagnostic-status") {
1067
+ logRed("Automatic recovery did not start the backend.");
1068
+ logRed("Diagnostic: running `memorax-code status` so the failure is visible.");
1069
+ } else if (event.type === "start-succeeded") {
1070
+ logGreen(event.recovered
1071
+ ? "Backend start completed after automatic recovery."
1072
+ : "Backend start completed.");
1073
+ } else if (event.type === "status") {
1074
+ log("Checking backend status with `memorax-code status`...");
1075
+ } else if (event.type === "status-failed") {
1076
+ logRed("Backend status check failed during setup.");
1077
+ printFailureSuggestions();
1078
+ } else if (event.type === "status-succeeded") {
1079
+ logGreen("Backend status check completed.");
1080
+ } else if (event.type === "complete") {
1081
+ printReconcileFailure(event, {
1082
+ skipCodexAdapter,
1083
+ codexSkipReason,
1084
+ skipClaudeAdapter,
1085
+ claudeSkipReason,
1086
+ skipOpenCodeAdapter,
1087
+ opencodeSkipReason,
1088
+ });
1089
+ }
1090
+ },
1091
+ });
1092
+ return {
1093
+ status: result.status,
1094
+ dshAdapterEnabled: result.status === "enabled" && statusResult
1095
+ ? dshAdapterReady(statusResult)
1096
+ : false,
1097
+ dshAdapterUnavailable: result.status === "enabled" && statusResult
1098
+ ? dshAdapterUnavailable(statusResult)
1099
+ : false,
1100
+ };
1101
+ }
1102
+
1103
+ function printReconcileFailure(result, {
1104
+ skipCodexAdapter,
1105
+ codexSkipReason,
1106
+ skipClaudeAdapter,
1107
+ claudeSkipReason,
1108
+ skipOpenCodeAdapter,
1109
+ opencodeSkipReason,
1110
+ }) {
1111
+ if (result.reason === "hook-runtime-activation-failed") {
1112
+ logRed("Client Hook runtime activation failed; automatic lifecycle recovery was skipped.");
1113
+ logRed("The previously active runtime remains authoritative.");
1114
+ } else if (result.reason === "lifecycle-lock-timeout") {
1115
+ logRed("Automatic stop/start recovery is skipped because another MemoraX Code lifecycle command still owns the Backend authority.");
1116
+ printLifecycleLockFailureSuggestions();
1117
+ } else if (result.reason === "runtime-authority-failed") {
1118
+ logRed("Automatic stop/start recovery is skipped because persisted Backend runtime authority requires explicit repair.");
1119
+ printRuntimeAuthorityFailureSuggestions(result.code);
1120
+ } else if (result.reason === "start-failed-after-recovery") {
1121
+ printFailureSuggestions();
1122
+ } else if (result.reason === "not-ready") {
1123
+ printUnavailableDiagnostics({
1124
+ codexSkipReason,
1125
+ claudeSkipReason,
1126
+ opencodeSkipReason,
1127
+ });
1128
+ }
1129
+ }
1130
+
1131
+ function dshAdapterReady(statusResult) {
1132
+ const output = `${statusResult.stdout ?? ""}\n${statusResult.stderr ?? ""}`;
1133
+ return /\bDSH adapter:\s*ok\b[^\r\n]*\bintegration=plugin\b/im.test(stripAnsi(output));
1134
+ }
1135
+
1136
+ function dshAdapterUnavailable(statusResult) {
1137
+ const output = `${statusResult.stdout ?? ""}\n${statusResult.stderr ?? ""}`;
1138
+ return /\bDSH adapter:\s*unavailable\b/im.test(stripAnsi(output));
1139
+ }
1140
+
1141
+ function pendingClientHookRuntimeEnv() {
1142
+ return {
1143
+ MEMORAX_CODE_PENDING_CLIENT_HOOK_RUNTIME_V1: JSON.stringify({
1144
+ version: 1,
1145
+ memoraxCodeHome: resolve(memoraxCodeHome()),
1146
+ generation: {
1147
+ version: stagedHookRuntime.version,
1148
+ runtimeAbi: stagedHookRuntime.runtimeAbi,
1149
+ generationId: stagedHookRuntime.generationId,
1150
+ packageVersion: stagedHookRuntime.packageVersion,
1151
+ contentDigest: stagedHookRuntime.contentDigest,
1152
+ createdAt: stagedHookRuntime.createdAt,
1153
+ },
1154
+ }),
1155
+ };
1156
+ }
1157
+
1158
+ function clientLifecycleFlags({ clientMode = "all" } = {}) {
1159
+ return ["--clients", clientMode];
1160
+ }
1161
+
1162
+ function clientModeFor(clients, { includeDsh = false } = {}) {
1163
+ const selected = ["codex", "claude", "dsh", "opencode"].filter((client) => (
1164
+ client === "dsh" ? includeDsh : clients.includes(client)
1165
+ ));
1166
+ if (selected.length === 4) return "all";
1167
+ return selected.length > 0 ? selected.join(",") : "none";
1168
+ }
1169
+
1170
+ function setupClientSkipReason({ explicitlySkipped, selected, enabled }) {
1171
+ if (explicitlySkipped) return "setup-skip";
1172
+ if (selected && !enabled) return "client-not-detected";
1173
+ return enabled ? undefined : "client-selection";
1174
+ }
1175
+
1176
+ function clientSelectionMessage(clients, { dshSelected = false } = {}) {
1177
+ if (dshSelected) {
1178
+ const labels = [
1179
+ clients.includes("codex") ? "Codex" : undefined,
1180
+ clients.includes("claude") ? "Claude Code" : undefined,
1181
+ "DeepSeek Harness",
1182
+ clients.includes("opencode") ? "OpenCode" : undefined,
1183
+ ].filter(Boolean);
1184
+ return `Configuring MemoraX Code for ${joinedLabels(labels)}.`;
1185
+ }
1186
+ const hasCodex = clients.includes("codex");
1187
+ const hasClaude = clients.includes("claude");
1188
+ const hasOpenCode = clients.includes("opencode");
1189
+ if (hasCodex && hasClaude && hasOpenCode) return "Configuring MemoraX Code for Codex, Claude Code, and OpenCode.";
1190
+ if (hasCodex && hasClaude) return "Configuring MemoraX Code for Codex and Claude Code.";
1191
+ if (hasCodex && hasOpenCode) return "Configuring MemoraX Code for Codex and OpenCode.";
1192
+ if (hasClaude && hasOpenCode) return "Configuring MemoraX Code for Claude Code and OpenCode.";
1193
+ if (hasCodex) return "Configuring MemoraX Code for Codex only.";
1194
+ if (hasClaude) return "Configuring MemoraX Code for Claude Code only.";
1195
+ if (hasOpenCode) return "Configuring MemoraX Code for OpenCode only.";
1196
+ return "Skipping client adapter setup for this setup.";
1197
+ }
1198
+
1199
+ function clientLabel(client) {
1200
+ if (client === "codex") return "Codex";
1201
+ if (client === "claude") return "Claude Code";
1202
+ return "OpenCode";
1203
+ }
1204
+
1205
+ function detectedClientMessage(clients, dshProfiles = []) {
1206
+ if (clients.length === 0 && dshProfiles.length === 0) {
1207
+ return "No supported client runtime was detected; starting the shared Backend without client adapters.";
1208
+ }
1209
+ if (clients.length === 0) {
1210
+ return "Detected existing DeepSeek Harness profiles; configuring the native DSH plugin and shared Backend.";
1211
+ }
1212
+ return `Detected supported client runtimes. ${clientSelectionMessage(clients)}`;
1213
+ }
1214
+
1215
+ function joinedLabels(labels) {
1216
+ if (labels.length < 2) return labels[0] ?? "";
1217
+ if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
1218
+ return `${labels.slice(0, -1).join(", ")}, and ${labels.at(-1)}`;
1219
+ }
1220
+
1221
+ function codexHome() {
1222
+ return process.env.CODEX_HOME || join(homedir(), ".codex");
1223
+ }
1224
+
1225
+ function stringOption(value) {
1226
+ if (typeof value !== "string") return undefined;
1227
+ const trimmed = value.trim();
1228
+ return trimmed ? trimmed : undefined;
1229
+ }
1230
+
1231
+ function runMemoraxCodeCommand(args, extraEnv = {}) {
1232
+ return runNodeMemoraxCodeCommand(args, {
1233
+ env: { ...process.env, ...extraEnv, MEMORAX_CODE_BACKEND_SUPPRESS_GUIDANCE: "1" },
1234
+ outputPrefix: BACKEND_PREFIX,
1235
+ });
1236
+ }
1237
+
1238
+ function runNodeMemoraxCodeCommand(args, {
1239
+ env = process.env,
1240
+ print = true,
1241
+ outputPrefix = PREFIX,
1242
+ timeout,
1243
+ } = {}) {
1244
+ return runNodeCliCommand(memoraxCodeBin, args, {
1245
+ env,
1246
+ print,
1247
+ outputPrefix,
1248
+ timeout,
1249
+ });
1250
+ }
1251
+
1252
+ function runNodeMemoraxCliCommand(args, {
1253
+ env = process.env,
1254
+ print = true,
1255
+ outputPrefix = PREFIX,
1256
+ timeout,
1257
+ } = {}) {
1258
+ return runNodeCliCommand(memoraxCliBin, args, {
1259
+ env,
1260
+ print,
1261
+ outputPrefix,
1262
+ timeout,
1263
+ });
1264
+ }
1265
+
1266
+ function runNodeCliCommand(bin, args, options) {
1267
+ return runCommand(process.execPath, [bin, ...args], options);
1268
+ }
1269
+
1270
+ function runExternalCommand(command, args, { env = process.env, print = true, outputPrefix = PREFIX, timeout } = {}) {
1271
+ return runCommand(command, args, { env, print, outputPrefix, timeout });
1272
+ }
1273
+
1274
+ function runCommand(command, args, { env = process.env, print = true, outputPrefix = PREFIX, timeout } = {}) {
1275
+ let invocation;
1276
+ try {
1277
+ invocation = resolveWindowsCliInvocation(command, args, { env });
1278
+ } catch (error) {
1279
+ const message = error instanceof Error ? error.message : String(error);
1280
+ if (print) logRed(message);
1281
+ return { status: 1, stdout: "", stderr: message, error };
1282
+ }
1283
+ const result = spawnSync(invocation.command, invocation.args, {
1284
+ env,
1285
+ encoding: "utf8",
1286
+ stdio: ["ignore", "pipe", "pipe"],
1287
+ ...(timeout ? { timeout } : {}),
1288
+ });
1289
+ if (print) {
1290
+ printCommandOutput(result.stdout, outputPrefix);
1291
+ printCommandOutput(result.stderr, outputPrefix);
1292
+ }
1293
+ if (result.error && print) logRed(`Failed to run \`${[command, ...args].join(" ")}\`: ${result.error.message}`);
1294
+ return result;
1295
+ }
1296
+
1297
+ function printCommandOutput(output, outputPrefix) {
1298
+ for (const line of String(output ?? "").split(/\r?\n/)) {
1299
+ if (!line) continue;
1300
+ if (hasMemoraxCodePrefix(line)) console.warn(line);
1301
+ else logWithPrefix(outputPrefix, line);
1302
+ }
1303
+ }
1304
+
1305
+ function hasMemoraxCodePrefix(line) {
1306
+ return /^\[MemoraX Code (?:Install|Backend)\]: /.test(stripAnsi(line));
1307
+ }
1308
+
1309
+ function stripAnsi(value) {
1310
+ return String(value).replace(/\x1b\[[0-9;]*m/g, "");
1311
+ }
1312
+
1313
+ function firstOutputLine(result) {
1314
+ const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
1315
+ return output || undefined;
1316
+ }
1317
+
1318
+ function commandSummary(result) {
1319
+ if (result.status !== 0) return undefined;
1320
+ return firstOutputLine(result);
1321
+ }
1322
+
1323
+ function packageVersionSummary() {
1324
+ const version = packageVersion();
1325
+ return version === "unknown"
1326
+ ? "installed, version unavailable"
1327
+ : `installed (${version}), command version unavailable`;
1328
+ }
1329
+
1330
+ function packageVersion() {
1331
+ try {
1332
+ const pkg = JSON.parse(readFileSync(join(dirname(scriptDir), "package.json"), "utf8"));
1333
+ if (typeof pkg.version === "string" && pkg.version.trim()) return pkg.version.trim();
1334
+ } catch {
1335
+ // Ignore package metadata errors; this is a best-effort install preflight.
1336
+ }
1337
+ return "unknown";
1338
+ }
1339
+
1340
+ function codexClientRunning() {
1341
+ const result = process.platform === "win32"
1342
+ ? runExternalCommand("tasklist.exe", ["/FO", "CSV", "/NH"], { print: false })
1343
+ : runExternalCommand("ps", ["-axo", "comm=,args="], { print: false });
1344
+ if (result.status !== 0) return false;
1345
+ return String(result.stdout ?? "").split(/\r?\n/).some((line) => {
1346
+ const lower = line.toLowerCase();
1347
+ if (!lower.includes("codex")) return false;
1348
+ if (lower.includes("memorax-code-setup")) return false;
1349
+ return process.platform === "win32"
1350
+ ? /"codex(?:\.exe)?"/i.test(line)
1351
+ : lower.includes("codex.app") || /(^|[/\s])codex(\s|$)/i.test(line);
1352
+ });
1353
+ }
1354
+
1355
+ function printNextSteps({
1356
+ codexAdapterEnabled = true,
1357
+ claudeAdapterEnabled = true,
1358
+ dshAdapterEnabled = false,
1359
+ opencodeAdapterEnabled = true,
1360
+ } = {}) {
1361
+ const clientText = enabledClientText({
1362
+ codexAdapterEnabled,
1363
+ claudeAdapterEnabled,
1364
+ opencodeAdapterEnabled,
1365
+ });
1366
+ if (clientText && existingSetup) {
1367
+ logGreen(`${bold("The new Hook runtime is active")}; existing sessions with the stable shell select it on their next user prompt.`);
1368
+ log(`Restart or refresh ${clientText} only if its plugin shell was installed, changed, or newly enabled, or if MemoraX Code is not active on the next prompt.`);
1369
+ } else if (clientText) {
1370
+ logGreen(`${bold(`Restart or refresh ${clientText}`)} before opening a new MemoraX Code session.`);
1371
+ }
1372
+ if (dshAdapterEnabled) {
1373
+ logGreen(`${bold("Restart or refresh DeepSeek Harness")} so its Profiles load the installed MemoraX Code plugin.`);
1374
+ } else if (!clientText) {
1375
+ logGreen(`${bold("MemoraX Code backend is running")}; client adapters were skipped for this install.`);
1376
+ }
1377
+ if (codexAdapterEnabled && !existingSetup) {
1378
+ logGreen(`After restart, ${bold("enable the MemoraX Code Codex Adapter plugin")} from Codex Plugins or CLI \`/plugins\` if it is not already enabled.`);
1379
+ }
1380
+ const statusCommands = statusCommandText({
1381
+ codexAdapterEnabled,
1382
+ claudeAdapterEnabled,
1383
+ opencodeAdapterEnabled,
1384
+ });
1385
+ if (clientText || !dshAdapterEnabled) {
1386
+ log(`If MemoraX Code is not active ${existingSetup ? "on the next prompt" : "in new sessions"}, run ${statusCommands}.`);
1387
+ }
1388
+ if (dshAdapterEnabled) {
1389
+ log("If MemoraX Code is not active after restarting or refreshing DeepSeek Harness, run `memorax-code status`.");
1390
+ }
1391
+ }
1392
+
1393
+ function enabledClientText({
1394
+ codexAdapterEnabled = true,
1395
+ claudeAdapterEnabled = true,
1396
+ opencodeAdapterEnabled = true,
1397
+ } = {}) {
1398
+ const labels = [
1399
+ codexAdapterEnabled ? "Codex" : undefined,
1400
+ claudeAdapterEnabled ? "Claude Code" : undefined,
1401
+ opencodeAdapterEnabled ? "OpenCode" : undefined,
1402
+ ].filter(Boolean);
1403
+ if (labels.length < 2) return labels[0] ?? "";
1404
+ if (labels.length === 2) return `${labels[0]} or ${labels[1]}`;
1405
+ return `${labels.slice(0, -1).join(", ")}, or ${labels.at(-1)}`;
1406
+ }
1407
+
1408
+ function statusCommandText({
1409
+ codexAdapterEnabled = true,
1410
+ claudeAdapterEnabled = true,
1411
+ opencodeAdapterEnabled = true,
1412
+ } = {}) {
1413
+ const commands = ["`memorax-code status`"];
1414
+ if (codexAdapterEnabled) commands.push("`memorax-code-codex status`");
1415
+ if (claudeAdapterEnabled) commands.push("`memorax-code-claude status`");
1416
+ if (opencodeAdapterEnabled) commands.push("`memorax-code-opencode status`");
1417
+ if (commands.length === 1) return commands[0];
1418
+ if (commands.length === 2) return `${commands[0]} and ${commands[1]}`;
1419
+ return `${commands.slice(0, -1).join(", ")}, and ${commands.at(-1)}`;
1420
+ }
1421
+
1422
+ function printPostinstallSummary(backendAndAdaptersStatus, degraded = false) {
1423
+ log("Package: Installed");
1424
+ const backendStatusLabel = backendAndAdaptersStatus === "enabled"
1425
+ ? blueBold(degraded ? "Enabled (degraded)" : "Enabled")
1426
+ : backendAndAdaptersStatus === "unavailable"
1427
+ ? redBold("Unavailable")
1428
+ : "Not verified";
1429
+ log(`Backend and selected adapters: ${backendStatusLabel}`);
1430
+ const memoraxStatus = readMemoraxInstallStatus();
1431
+ if (!memoraxStatus) {
1432
+ logRed("MemoraX memory: Status unavailable");
1433
+ log("Run `memorax-cli status` after installation to inspect the effective configuration.");
1434
+ return;
1435
+ }
1436
+ if (!memoraxStatus.configured) {
1437
+ log("MemoraX memory: Not configured");
1438
+ log("Package installed, MemoraX not configured. Run `memorax-code setup` from a terminal to finish setup.");
1439
+ return;
1440
+ }
1441
+ log(`MemoraX memory: ${blueBold("Configured")}`);
1442
+ if (memoraxStatus.writebackEnabled) {
1443
+ log(`Automatic writeback: ${blueBold("Enabled")}`);
1444
+ } else if (!memoraxStatus.globalWritebackEnabled) {
1445
+ log("Automatic writeback: Disabled by the global kill switch");
1446
+ } else {
1447
+ log("Automatic writeback: Disabled by effective configuration");
1448
+ }
1449
+ }
1450
+
1451
+ function readMemoraxInstallStatus() {
1452
+ const result = runNodeMemoraxCliCommand(
1453
+ ["status", "--json", "--config-only"],
1454
+ { print: false, timeout: 10_000 },
1455
+ );
1456
+ if (result.error || result.signal || (result.status !== 0 && result.status !== 1)) {
1457
+ return undefined;
1458
+ }
1459
+ try {
1460
+ const report = JSON.parse(String(result.stdout ?? ""));
1461
+ if (!isRecord(report)
1462
+ || report.action !== "memory.status"
1463
+ || report.provider !== "memory.memorax"
1464
+ || typeof report.ok !== "boolean"
1465
+ || !isRecord(report.config)
1466
+ || typeof report.config.configured !== "boolean") {
1467
+ return undefined;
1468
+ }
1469
+ const configuredResult = result.status === 0
1470
+ && report.ok === true
1471
+ && report.config.configured === true;
1472
+ const unconfiguredResult = result.status === 1
1473
+ && report.ok === false
1474
+ && report.config.configured === false;
1475
+ if (!configuredResult && !unconfiguredResult) {
1476
+ return undefined;
1477
+ }
1478
+ if (unconfiguredResult) {
1479
+ return { configured: false };
1480
+ }
1481
+ if (!isRecord(report.config.writeback)
1482
+ || typeof report.config.writeback.globalEnabled !== "boolean"
1483
+ || typeof report.config.writeback.writebackEnabled !== "boolean") {
1484
+ return undefined;
1485
+ }
1486
+ return {
1487
+ configured: true,
1488
+ globalWritebackEnabled: report.config.writeback.globalEnabled,
1489
+ writebackEnabled: report.config.writeback.writebackEnabled,
1490
+ };
1491
+ } catch {
1492
+ return undefined;
1493
+ }
1494
+ }
1495
+
1496
+ function printUnavailableDiagnostics({ codexSkipReason, claudeSkipReason, opencodeSkipReason } = {}) {
1497
+ logRed("MemoraX Code is not enabled for new client sessions.");
1498
+ logRed("Check `memorax-code status`, `memorax-code-codex status`, `memorax-code-claude status`, and `memorax-code-opencode status` for Backend and adapter details.");
1499
+ logRed("If Codex, Claude Code, OpenCode, or DeepSeek Harness is open, restart or refresh it after fixing the reported status.");
1500
+ if (codexSkipReason) printCodexSkippedDiagnostics(codexSkipReason);
1501
+ if (claudeSkipReason) printClaudeSkippedDiagnostics(claudeSkipReason);
1502
+ if (opencodeSkipReason) printOpenCodeSkippedDiagnostics(opencodeSkipReason);
1503
+ printCommonCommands({
1504
+ codexAdapterEnabled: !codexSkipReason,
1505
+ claudeAdapterEnabled: !claudeSkipReason,
1506
+ opencodeAdapterEnabled: !opencodeSkipReason,
1507
+ });
1508
+ }
1509
+
1510
+ function printCodexSkippedDiagnostics(reason) {
1511
+ logRed("Codex plugin registration was skipped for this setup, so MemoraX Code left the Codex plugin unchanged.");
1512
+ log("Claude Code can still use MemoraX Code when `memorax-code-claude status` reports the Hook integration is enabled.");
1513
+ log("Run `memorax-code start` after installing the Codex plugin, then restart or refresh Codex.");
1514
+ }
1515
+
1516
+ function printClaudeSkippedDiagnostics() {
1517
+ logRed("Claude Code adapter setup was skipped for this setup, so MemoraX Code left the Claude integration unchanged.");
1518
+ log("Codex can still use MemoraX Code when `memorax-code-codex status` reports the adapter is enabled.");
1519
+ log("Run `memorax-code start` after installing Claude Code, then restart or refresh Claude Code.");
1520
+ }
1521
+
1522
+ function printOpenCodeSkippedDiagnostics() {
1523
+ logRed("OpenCode adapter setup was skipped for this setup, so MemoraX Code left the OpenCode integration unchanged.");
1524
+ log("Run `memorax-code start --clients opencode` after installing OpenCode, then restart or refresh OpenCode.");
1525
+ }
1526
+
1527
+ function printFailureSuggestions() {
1528
+ logRed("Suggested recovery: run `memorax-code stop`, then `memorax-code start`, then `memorax-code status`.");
1529
+ logRed("If the Backend port is busy, stop the process using 127.0.0.1:8787 and retry `memorax-code start`.");
1530
+ logRed("If client sessions still bypass MemoraX Code, restart or refresh the affected client and verify the relevant adapter status.");
1531
+ printCommonCommands();
1532
+ }
1533
+
1534
+ function printRuntimeAuthorityFailureSuggestions(code) {
1535
+ if (code.startsWith("BACKEND_CONNECTION_AUTHORITY_")) {
1536
+ logRed("Confirm the intended local Backend bind, then run `memorax-code start --host 127.0.0.1 --port <intended-port>`.");
1537
+ logRed("The connection authority is replaced only after that Backend starts successfully; then run `memorax-code status`.");
1538
+ } else if (code.startsWith("BACKEND_TOKEN_RECORD_")) {
1539
+ logRed("Inspect `memorax-code status`; after safely stopping the managed Backend, run `memorax-code token --rotate`, then `memorax-code start`.");
1540
+ } else {
1541
+ logRed("Inspect `memorax-code status` and the reported backend.pid.json path; confirm process ownership before repairing or removing that state file.");
1542
+ }
1543
+ printCommonCommands();
1544
+ }
1545
+
1546
+ function printLifecycleLockFailureSuggestions() {
1547
+ logRed("Let the existing lifecycle command finish, then run `memorax-code status`.");
1548
+ logRed("If MemoraX Code is not ready, retry `memorax-code start`; do not stop a Backend started by the concurrent owner.");
1549
+ printCommonCommands();
1550
+ }
1551
+
1552
+ function printCommonCommands({
1553
+ codexAdapterEnabled = true,
1554
+ claudeAdapterEnabled = true,
1555
+ opencodeAdapterEnabled = true,
1556
+ } = {}) {
1557
+ log("Common commands:");
1558
+ log("- `memorax-code status`: check the local backend and adapter state.");
1559
+ log("- `memorax-cli status`: check required MemoraX configuration and effective memory switches.");
1560
+ log("- `memorax-code start`: start or refresh the local memory backend and client integrations.");
1561
+ log("- `memorax-code stop`: stop the local memory backend and disable managed client integrations.");
1562
+ if (codexAdapterEnabled) log("- `memorax-code-codex sessions`: verify recent native Codex session registration.");
1563
+ if (claudeAdapterEnabled) log("- `memorax-code-claude sessions`: verify recent native Claude Code session registration.");
1564
+ if (opencodeAdapterEnabled) log("- `memorax-code-opencode doctor`: verify the managed OpenCode plugin, runtime evidence, and Backend health.");
1565
+ }
1566
+
1567
+ function memoraxCodeEnabled(statusResult, {
1568
+ codexAdapterRequired = true,
1569
+ claudeAdapterRequired = true,
1570
+ opencodeAdapterRequired = true,
1571
+ } = {}) {
1572
+ const output = `${statusResult.stdout ?? ""}\n${statusResult.stderr ?? ""}`;
1573
+ const normalized = stripAnsi(output);
1574
+ const backendOk = /MemoraX Code Backend status:\s*Enabled\b/im.test(normalized)
1575
+ || /MemoraX Code status:\s*Enabled\b/im.test(normalized)
1576
+ || /memorax-code:\s*ok\b/im.test(normalized);
1577
+ const serviceOk = /Backend status:\s*Enabled\b/im.test(normalized)
1578
+ || /Backend:\s*(?:running|ok)\b/im.test(normalized);
1579
+ const codexAdapterOk = /Codex adapter:\s*ok\b/im.test(normalized);
1580
+ const claudeAdapterOk = /Claude adapter:\s*ok\b/im.test(normalized);
1581
+ const opencodeAdapterOk = /OpenCode adapter:\s*ok\b/im.test(normalized);
1582
+ return backendOk
1583
+ && serviceOk
1584
+ && (!codexAdapterRequired || codexAdapterOk)
1585
+ && (!claudeAdapterRequired || claudeAdapterOk)
1586
+ && (!opencodeAdapterRequired || opencodeAdapterOk);
1587
+ }
1588
+
1589
+ function log(message) {
1590
+ logWithPrefix(PREFIX, message);
1591
+ }
1592
+
1593
+ function logGreen(message) {
1594
+ console.warn(`${GREEN}${PREFIX} ${message}${RESET}`);
1595
+ }
1596
+
1597
+ function logRed(message) {
1598
+ console.warn(`${RED}${PREFIX} ${message}${RESET}`);
1599
+ }
1600
+
1601
+ function logWithPrefix(prefix, message) {
1602
+ console.warn(`${prefix} ${message}`);
1603
+ }
1604
+
1605
+ function bold(message) {
1606
+ return `${BOLD}${message}\x1b[22m`;
1607
+ }
1608
+
1609
+ function blueBold(message) {
1610
+ return `${BLUE}${bold(message)}${RESET}`;
1611
+ }
1612
+
1613
+ function redBold(message) {
1614
+ return `${RED}${bold(message)}${RESET}`;
1615
+ }