open-memex 0.7.0-alpha.2 → 0.7.0-alpha.3

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.
package/AGENTS.md CHANGED
@@ -102,7 +102,7 @@ The published `open-memex` bin points at `dist/cli.js` (compiled at publish time
102
102
  From a source checkout, `npm run cli` / `npm run mcp` still run `src/` directly
103
103
  with type-stripping — no build step needed for development.
104
104
 
105
- There is **no `npm test`** and no CI. Verification loop is: `npm run typecheck` + `smoke-pure.ts` + (if touching sqlite) `npm run cli -- reindex` against a scratch `MY_O_MEMORY_HOME`.
105
+ There is **no `npm test`** and no CI. Verification loop is: `npm run typecheck` + `smoke-pure.ts` + (if touching sqlite) `npm run cli -- reindex` against a scratch `OPEN_MEMEX_HOME`.
106
106
 
107
107
  To load the plugin in opencode locally, `~/.config/opencode/opencode.jsonc` must have:
108
108
  ```
@@ -115,7 +115,7 @@ Restart opencode after any change — the plugin is not hot-reloaded.
115
115
  Storage root:
116
116
  - Windows: `%APPDATA%\open-memex\`
117
117
  - Linux/macOS: `$XDG_DATA_HOME/open-memex/` (fallback `~/.local/share/open-memex/`)
118
- - Override with `MY_O_MEMORY_HOME` (use this for tests to avoid clobbering real data).
118
+ - Override with `OPEN_MEMEX_HOME` (use this for tests to avoid clobbering real data). The pre-rename `MY_O_MEMORY_HOME` is still honored as a fallback (D62).
119
119
 
120
120
  Layout: `memories/<scope_key>/<id>.md` (YAML frontmatter + body) + `index.db` (SQLite FTS5).
121
121
 
package/CHANGELOG.md CHANGED
@@ -9,6 +9,10 @@ the `@alpha` tag are development snapshots and are not listed individually.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## Environment variable names (D62)
13
+
14
+ - The override variables now answer to `OPEN_MEMEX_HOME` (storage root) and `OPEN_MEMEX_CONFIG` (config path). The pre-rename `MY_O_MEMORY_HOME` / `MY_O_MEMORY_CONFIG` names keep working as fallbacks — nothing to migrate, the data root itself does not move. `doctor` notes when the storage root came from the legacy variable.
15
+
12
16
  ## Proactive capture: checkpoint proposals and aliases (D61)
13
17
 
14
18
  - At task checkpoints the agent now proposes instead of waiting to be asked: when a task wraps up, it offers 1–3 short candidate memories distilled from the session (decisions and their reasons, conventions, gotchas, approaches tried and abandoned) and saves only the ones you approve — approved inferences are marked `source: "inference"`. One memorability rubric and one search discipline (break the question into concepts, try several phrasings, check both scopes) are written into every guidance surface — the bundled skill, the AGENTS.md memory-hygiene footer, the MCP handshake, and the tool descriptions — so behavior is consistent across editors. Nothing is auto-drafted in the background; that posture waits on memory visibility.
package/README.md CHANGED
@@ -578,8 +578,9 @@ branches and PRs like any other file.
578
578
 
579
579
  Settings live in `~/.config/opencode/open-memex.jsonc` — the `opencode` in the
580
580
  path is historical; this one file is shared by every client. Override the config
581
- path with `MY_O_MEMORY_CONFIG` and the storage root with `MY_O_MEMORY_HOME`
582
- (legacy environment names from before the rename, still honored).
581
+ path with `OPEN_MEMEX_CONFIG` and the storage root with `OPEN_MEMEX_HOME`
582
+ (the pre-rename `MY_O_MEMORY_CONFIG` / `MY_O_MEMORY_HOME` names are still
583
+ honored as fallbacks).
583
584
 
584
585
  Defaults:
585
586
 
package/README.zh-CN.md CHANGED
@@ -391,7 +391,7 @@ npm install -g open-memex@latest # 或 @alpha
391
391
 
392
392
  ## 配置
393
393
 
394
- 设置放在 `~/.config/opencode/open-memex.jsonc`——路径里的 `opencode` 是历史名;这一个文件由所有客户端共享。用 `MY_O_MEMORY_CONFIG` 覆盖配置路径,用 `MY_O_MEMORY_HOME` 覆盖存储根目录(改名之前留下的旧环境变量名,仍然有效)。
394
+ 设置放在 `~/.config/opencode/open-memex.jsonc`——路径里的 `opencode` 是历史名;这一个文件由所有客户端共享。用 `OPEN_MEMEX_CONFIG` 覆盖配置路径,用 `OPEN_MEMEX_HOME` 覆盖存储根目录(改名之前的旧变量名 `MY_O_MEMORY_CONFIG` / `MY_O_MEMORY_HOME` 仍然有效,作为回退)。
395
395
 
396
396
  默认值:
397
397
 
package/dist/config.js CHANGED
@@ -46,9 +46,13 @@ function stripJsonComments(raw) {
46
46
  out = out.replace(/,(\s*[}\]])/g, "$1");
47
47
  return out;
48
48
  }
49
- /** Where `config set` / interactive `init` persist. Respects MY_O_MEMORY_CONFIG. */
49
+ /**
50
+ * Where `config set` / interactive `init` persist. Respects OPEN_MEMEX_CONFIG
51
+ * (D62); MY_O_MEMORY_CONFIG is the pre-rename fallback, still honored.
52
+ */
50
53
  export function configFilePath() {
51
- return (process.env.MY_O_MEMORY_CONFIG ??
54
+ return (process.env.OPEN_MEMEX_CONFIG ??
55
+ process.env.MY_O_MEMORY_CONFIG ??
52
56
  path.join(os.homedir(), ".config", "opencode", "open-memex.jsonc"));
53
57
  }
54
58
  function toBool(v) {
@@ -111,6 +115,7 @@ export function saveConfig(patch) {
111
115
  /** Path of the config file in effect, or null when using built-in defaults. */
112
116
  export function configSource() {
113
117
  const candidates = [
118
+ process.env.OPEN_MEMEX_CONFIG,
114
119
  process.env.MY_O_MEMORY_CONFIG,
115
120
  path.join(os.homedir(), ".config", "opencode", "open-memex.jsonc"),
116
121
  path.join(os.homedir(), ".config", "opencode", "open-memex.json"),
package/dist/doctor.js CHANGED
@@ -8,7 +8,7 @@ import path from "node:path";
8
8
  import { spawn, execFileSync } from "node:child_process";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { loadConfig, configSource, DEFAULT_CONFIG } from "./config.js";
11
- import { paths } from "./paths.js";
11
+ import { paths, homeOverride } from "./paths.js";
12
12
  import { resolveCwdScope } from "./scope.js";
13
13
  import { db, backendName } from "./store/db.js";
14
14
  import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.js";
@@ -54,7 +54,13 @@ function storageCheck() {
54
54
  try {
55
55
  const p = paths();
56
56
  fs.accessSync(p.memories, fs.constants.W_OK);
57
- return { name: "storage", ok: true, detail: `${p.root} (writable)` };
57
+ // D62: when the root came from the legacy env var, say so (still ok —
58
+ // the fallback is supported, doctor just names the current one).
59
+ const via = homeOverride()?.via;
60
+ const note = via === "MY_O_MEMORY_HOME"
61
+ ? " (via legacy MY_O_MEMORY_HOME — OPEN_MEMEX_HOME is the current name)"
62
+ : "";
63
+ return { name: "storage", ok: true, detail: `${p.root} (writable)${note}` };
58
64
  }
59
65
  catch (err) {
60
66
  return { name: "storage", ok: false, detail: String(err) };
@@ -559,9 +565,11 @@ export async function runDoctor() {
559
565
  function legacyCheck() {
560
566
  const name = "legacy my-o-memory";
561
567
  const found = [];
562
- const envHome = process.env.MY_O_MEMORY_HOME;
568
+ // D62: the override may arrive via either env var name; check the value
569
+ // in effect, not just the legacy one.
570
+ const envHome = homeOverride()?.dir;
563
571
  if (envHome && envHome.includes(LEGACY_PACKAGE_NAME))
564
- found.push(`MY_O_MEMORY_HOME points at a pre-rename dir (${envHome}) — unset it, then merge old data with \`open-memex migrate --to-v2\``);
572
+ found.push(`memory home points at a pre-rename dir (${envHome}) — unset it, then merge old data with \`open-memex migrate --to-v2\``);
565
573
  const root = paths().root;
566
574
  const legacyDir = path.join(path.dirname(root), LEGACY_PACKAGE_NAME);
567
575
  if (fs.existsSync(legacyDir) && fs.statSync(legacyDir).isDirectory())
package/dist/paths.js CHANGED
@@ -2,9 +2,23 @@ import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import { execFileSync } from "node:child_process";
5
- function dataRoot() {
5
+ /**
6
+ * D62: the data-root override env var. `OPEN_MEMEX_HOME` is the current
7
+ * name; `MY_O_MEMORY_HOME` (pre-rename package name) is still honored as a
8
+ * fallback so existing setups and scripts keep working. New name wins
9
+ * when both are set.
10
+ */
11
+ export function homeOverride() {
12
+ if (process.env.OPEN_MEMEX_HOME)
13
+ return { dir: path.resolve(process.env.OPEN_MEMEX_HOME), via: "OPEN_MEMEX_HOME" };
6
14
  if (process.env.MY_O_MEMORY_HOME)
7
- return path.resolve(process.env.MY_O_MEMORY_HOME);
15
+ return { dir: path.resolve(process.env.MY_O_MEMORY_HOME), via: "MY_O_MEMORY_HOME" };
16
+ return null;
17
+ }
18
+ function dataRoot() {
19
+ const override = homeOverride();
20
+ if (override)
21
+ return override.dir;
8
22
  if (process.platform === "win32") {
9
23
  const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
10
24
  return path.join(appData, "open-memex");
package/docs/V2-DESIGN.md CHANGED
@@ -1304,13 +1304,24 @@ requirement: personal data never touches third-party services). Benchmarks to tr
1304
1304
  distillation over host transcripts stays its own group; it will
1305
1305
  reuse this rubric.
1306
1306
 
1307
+ - **D62 — environment variable names grow current-name aliases.** The
1308
+ data-root and config-path overrides predate the rename and kept the
1309
+ old package's name (`MY_O_MEMORY_HOME`, `MY_O_MEMORY_CONFIG`) — a
1310
+ stale-looking name is a trap for users and agents scripting against
1311
+ the current one. `OPEN_MEMEX_HOME` / `OPEN_MEMEX_CONFIG` are now the
1312
+ preferred names; the legacy names still work as fallbacks (new name
1313
+ wins when both are set), so existing setups and scripts never break.
1314
+ The data root itself does not move — only the variable names change.
1315
+ `doctor`'s storage check notes when the root arrived via the legacy
1316
+ variable, and its pre-rename-dir leftovers check reads whichever
1317
+ variable is in effect.
1318
+
1307
1319
  ## Open Questions
1308
1320
 
1309
1321
  _All resolved — see D10 (rename), D11 (type/role split), D12 (explicit pull)._
1322
+ - **Checkpoint proactivity for capture (open, 2026-10-02):** D53 retired checkpoint *polling* — the server pushes draft counts instead. But agent proactivity at task checkpoints genuinely helps knowledge capture (§3.5 distillation), so the open question is the degree: proactive enough to catch distillable moments, without nagging the user or burning tokens at every checkpoint. Candidate directions (server-pushed hints in tool results, client-side heuristics, or a middle setting) to be evaluated in a later version; default posture TBD. **Update (2026-10-03): decided in D61 — propose posture** (agent proposes 1–3 candidates at task checkpoints, saves only on approval; auto-draft deferred until memory visibility ships, see the MEMORY_VISIBILITY question below).
1310
1323
 
1311
- - **Checkpoint proactivity for capture (open, 2026-10-02):** D53 retired checkpoint *polling* — the server pushes draft counts instead. But agent proactivity at task checkpoints genuinely helps knowledge capture (§3.5 distillation), so the open question is the degree: proactive enough to catch distillable moments, without nagging the user or burning tokens at every checkpoint. Candidate directions (server-pushed hints in tool results, client-side heuristics, or a middle setting) to be evaluated in a later version; default posture TBD.
1312
-
1313
- - **Retrieval robustness: phrasing, synonyms, extra words (open, 2026-10-03):** keyword search matches word forms, not meaning — the asker phrases it differently, adds a word, or uses a synonym and the memory is missed. The current FTS5 setup (porter stemming + pre-tokenized CJK bigrams) already covers morphology and substrings; it cannot bridge semantics. Layered candidate directions, cheapest first: (1) query construction — never feed a raw sentence to FTS; extract content terms, drop stopwords, OR them and let bm25 rank, so extra words hurt ranking instead of killing the match; (2) agent-side query expansion as an explicit protocol in the skill/instructions (decompose the question into concepts, generate 2–3 synonym variants per concept, run several searches before giving up — the LLM is the synonym dictionary, no embedding model needed); (3) capture-time aliasing — when a memory is written, the agent attaches keyword/synonym aliases so future phrasings can hit it. Embeddings are the design's reserved optional capability: real semantic matching, but they require a design decision and a local model to distribute, so they stay deferred unless (1)–(3) prove insufficient. Note (3) pairs naturally with the proactive-capture question below: the same capture pass that proposes a memory can attach its aliases. **Status (2026-10-03): layer (1) shipped (D56, 0.6.1)** — query construction now filters EN/CJK function words, dedupes, and caps terms. Layers (2) agent-side expansion and (3) capture-time aliasing remain open; embeddings stay deferred.
1324
+ - **Retrieval robustness: phrasing, synonyms, extra words (open, 2026-10-03):** keyword search matches word forms, not meaning — the asker phrases it differently, adds a word, or uses a synonym and the memory is missed. The current FTS5 setup (porter stemming + pre-tokenized CJK bigrams) already covers morphology and substrings; it cannot bridge semantics. Layered candidate directions, cheapest first: (1) query construction — never feed a raw sentence to FTS; extract content terms, drop stopwords, OR them and let bm25 rank, so extra words hurt ranking instead of killing the match; (2) agent-side query expansion as an explicit protocol in the skill/instructions (decompose the question into concepts, generate 2–3 synonym variants per concept, run several searches before giving up — the LLM is the synonym dictionary, no embedding model needed); (3) capture-time aliasing — when a memory is written, the agent attaches keyword/synonym aliases so future phrasings can hit it. Embeddings are the design's reserved optional capability: real semantic matching, but they require a design decision and a local model to distribute, so they stay deferred unless (1)–(3) prove insufficient. Note (3) pairs naturally with the proactive-capture question below: the same capture pass that proposes a memory can attach its aliases. **Status (2026-10-03): layer (1) shipped (D56, 0.6.1)** — query construction now filters EN/CJK function words, dedupes, and caps terms. Layers (2) agent-side expansion and (3) capture-time aliasing remain open; embeddings stay deferred. **Update (2026-10-03): layers (2) and (3) shipped in D61** — agent-side expansion as guidance text on every surface, capture-time aliases indexed in their own FTS column behind the `captureAliases` flag. Embeddings stay deferred.
1314
1325
 
1315
1326
  - **Proactive capture: agent judgment of what is worth remembering (open, 2026-10-03):** companion to the checkpoint-proactivity question above — that one asks *when* to prompt; this one asks *what qualifies*. Agent hosts with good long-term memory (e.g. Muse) get their results from explicit memorability criteria plus checkpoint consolidation, not from silent background magic; open-memex can adopt both halves. Candidate criteria to teach via the skill/instructions: decisions **and their rationale**, preferences, conventions, gotchas, and abandoned paths ("we tried X, it failed because…") are worth proposing; one-off task details and anything re-derivable from the code are not. Two postures to evaluate: propose-at-checkpoint (agent asks "worth saving?", user vetoes, then `memory_add`) versus auto-draft (agent writes outbox drafts unasked and surfaces them in a batch at the next checkpoint — drafts are git-invisible and deletable, so the cost of a wrong guess is low). Hard boundaries either way: project scope never auto-submits, and no cross-session background consolidation is possible — the plugin lives inside sessions, so consolidation happens at in-session checkpoints and wrap-up passes only. This question also decides how much aliasing (retrieval question above) the capture pass should generate.
1316
1327
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-memex",
3
- "version": "0.7.0-alpha.2",
3
+ "version": "0.7.0-alpha.3",
4
4
  "description": "Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -12,6 +12,8 @@ import { contentHash, similarity, NEAR_DUP_THRESHOLD } from "../src/store/lifecy
12
12
  import { userMcpConfigPath, mergeServerEntry, detectInstalledClients, mergePluginEntry, mergeV2PluginEntry, removeV2PluginEntry, opencodeGlobalConfigPath, printManualEntryHint, parseJsonConfig, removeServerEntry, removePluginEntry, removeInstructionsSection, LEGACY_PACKAGE_NAME, shouldInstallSkill } from "../src/init.ts";
13
13
  import { z } from "zod";
14
14
  import { memoryAddArgs } from "../src/tools/ops.ts";
15
+ import { dataRootPath, homeOverride } from "../src/paths.ts";
16
+ import { configFilePath } from "../src/config.ts";
15
17
 
16
18
  let fails = 0;
17
19
  function ok(name: string, cond: boolean, info?: unknown) {
@@ -423,6 +425,38 @@ ok("failed backup leaves legacy dir for retry", fs.existsSync(legacyRoot2));
423
425
  delete process.env.MY_O_MEMORY_HOME;
424
426
  fs.rmSync(tmpHome, { recursive: true, force: true });
425
427
 
428
+ console.log("== env var names (D62) ==");
429
+ {
430
+ const saved = {
431
+ n: process.env.OPEN_MEMEX_HOME,
432
+ l: process.env.MY_O_MEMORY_HOME,
433
+ nc: process.env.OPEN_MEMEX_CONFIG,
434
+ lc: process.env.MY_O_MEMORY_CONFIG,
435
+ };
436
+ const restore = () => {
437
+ const set = (k: string, v: string | undefined) => (v === undefined ? delete process.env[k] : (process.env[k] = v));
438
+ set("OPEN_MEMEX_HOME", saved.n);
439
+ set("MY_O_MEMORY_HOME", saved.l);
440
+ set("OPEN_MEMEX_CONFIG", saved.nc);
441
+ set("MY_O_MEMORY_CONFIG", saved.lc);
442
+ };
443
+ try {
444
+ delete process.env.OPEN_MEMEX_HOME;
445
+ delete process.env.MY_O_MEMORY_HOME;
446
+ ok("no override -> homeOverride null", homeOverride() === null);
447
+ process.env.MY_O_MEMORY_HOME = "/tmp/legacy-home";
448
+ ok("legacy fallback honored", dataRootPath().endsWith("legacy-home") && homeOverride()?.via === "MY_O_MEMORY_HOME");
449
+ process.env.OPEN_MEMEX_HOME = "/tmp/new-home";
450
+ ok("new name wins over legacy", dataRootPath().endsWith("new-home") && homeOverride()?.via === "OPEN_MEMEX_HOME");
451
+ process.env.MY_O_MEMORY_CONFIG = "/tmp/legacy.jsonc";
452
+ ok("config legacy fallback", configFilePath().endsWith("legacy.jsonc"));
453
+ process.env.OPEN_MEMEX_CONFIG = "/tmp/new.jsonc";
454
+ ok("config new name wins", configFilePath().endsWith("new.jsonc"));
455
+ } finally {
456
+ restore();
457
+ }
458
+ }
459
+
426
460
  console.log("== lifecycle pure: contentHash / similarity ==");
427
461
  ok("hash deterministic + whitespace-insensitive", contentHash("hello world\n") === contentHash("hello world"));
428
462
  ok("hash differs on content", contentHash("hello world") !== contentHash("hello mars"));
package/src/config.ts CHANGED
@@ -84,9 +84,13 @@ function stripJsonComments(raw: string): string {
84
84
  return out;
85
85
  }
86
86
 
87
- /** Where `config set` / interactive `init` persist. Respects MY_O_MEMORY_CONFIG. */
87
+ /**
88
+ * Where `config set` / interactive `init` persist. Respects OPEN_MEMEX_CONFIG
89
+ * (D62); MY_O_MEMORY_CONFIG is the pre-rename fallback, still honored.
90
+ */
88
91
  export function configFilePath(): string {
89
92
  return (
93
+ process.env.OPEN_MEMEX_CONFIG ??
90
94
  process.env.MY_O_MEMORY_CONFIG ??
91
95
  path.join(os.homedir(), ".config", "opencode", "open-memex.jsonc")
92
96
  );
@@ -156,6 +160,7 @@ export function saveConfig(patch: Record<string, unknown>): string {
156
160
  /** Path of the config file in effect, or null when using built-in defaults. */
157
161
  export function configSource(): string | null {
158
162
  const candidates = [
163
+ process.env.OPEN_MEMEX_CONFIG,
159
164
  process.env.MY_O_MEMORY_CONFIG,
160
165
  path.join(os.homedir(), ".config", "opencode", "open-memex.jsonc"),
161
166
  path.join(os.homedir(), ".config", "opencode", "open-memex.json"),
package/src/doctor.ts CHANGED
@@ -8,7 +8,7 @@ import path from "node:path";
8
8
  import { spawn, execFileSync } from "node:child_process";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { loadConfig, configSource, DEFAULT_CONFIG } from "./config.ts";
11
- import { paths } from "./paths.ts";
11
+ import { paths, homeOverride } from "./paths.ts";
12
12
  import { resolveCwdScope } from "./scope.ts";
13
13
  import { db, backendName } from "./store/db.ts";
14
14
  import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.ts";
@@ -66,7 +66,14 @@ function storageCheck(): Check {
66
66
  try {
67
67
  const p = paths();
68
68
  fs.accessSync(p.memories, fs.constants.W_OK);
69
- return { name: "storage", ok: true, detail: `${p.root} (writable)` };
69
+ // D62: when the root came from the legacy env var, say so (still ok —
70
+ // the fallback is supported, doctor just names the current one).
71
+ const via = homeOverride()?.via;
72
+ const note =
73
+ via === "MY_O_MEMORY_HOME"
74
+ ? " (via legacy MY_O_MEMORY_HOME — OPEN_MEMEX_HOME is the current name)"
75
+ : "";
76
+ return { name: "storage", ok: true, detail: `${p.root} (writable)${note}` };
70
77
  } catch (err) {
71
78
  return { name: "storage", ok: false, detail: String(err) };
72
79
  }
@@ -579,10 +586,12 @@ export async function runDoctor(): Promise<boolean> {
579
586
  function legacyCheck(): Check {
580
587
  const name = "legacy my-o-memory";
581
588
  const found: string[] = [];
582
- const envHome = process.env.MY_O_MEMORY_HOME;
589
+ // D62: the override may arrive via either env var name; check the value
590
+ // in effect, not just the legacy one.
591
+ const envHome = homeOverride()?.dir;
583
592
  if (envHome && envHome.includes(LEGACY_PACKAGE_NAME))
584
593
  found.push(
585
- `MY_O_MEMORY_HOME points at a pre-rename dir (${envHome}) — unset it, then merge old data with \`open-memex migrate --to-v2\``,
594
+ `memory home points at a pre-rename dir (${envHome}) — unset it, then merge old data with \`open-memex migrate --to-v2\``,
586
595
  );
587
596
  const root = paths().root;
588
597
  const legacyDir = path.join(path.dirname(root), LEGACY_PACKAGE_NAME);
package/src/paths.ts CHANGED
@@ -3,8 +3,23 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import { execFileSync } from "node:child_process";
5
5
 
6
+ /**
7
+ * D62: the data-root override env var. `OPEN_MEMEX_HOME` is the current
8
+ * name; `MY_O_MEMORY_HOME` (pre-rename package name) is still honored as a
9
+ * fallback so existing setups and scripts keep working. New name wins
10
+ * when both are set.
11
+ */
12
+ export function homeOverride(): { dir: string; via: "OPEN_MEMEX_HOME" | "MY_O_MEMORY_HOME" } | null {
13
+ if (process.env.OPEN_MEMEX_HOME)
14
+ return { dir: path.resolve(process.env.OPEN_MEMEX_HOME), via: "OPEN_MEMEX_HOME" };
15
+ if (process.env.MY_O_MEMORY_HOME)
16
+ return { dir: path.resolve(process.env.MY_O_MEMORY_HOME), via: "MY_O_MEMORY_HOME" };
17
+ return null;
18
+ }
19
+
6
20
  function dataRoot(): string {
7
- if (process.env.MY_O_MEMORY_HOME) return path.resolve(process.env.MY_O_MEMORY_HOME);
21
+ const override = homeOverride();
22
+ if (override) return override.dir;
8
23
  if (process.platform === "win32") {
9
24
  const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
10
25
  return path.join(appData, "open-memex");