open-memex 0.5.1 → 0.6.0-alpha.10
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 +23 -2
- package/README.md +28 -9
- package/README.zh-CN.md +23 -8
- package/dist/cli.js +36 -1
- package/dist/distill-agents.js +3 -2
- package/dist/doctor.js +30 -0
- package/dist/first-run.js +75 -0
- package/dist/init.js +210 -57
- package/dist/mcp.js +49 -33
- package/dist/paths.js +8 -0
- package/dist/submit.js +13 -0
- package/dist/tools/memory.js +4 -4
- package/dist/tools/ops.js +18 -3
- package/docs/V2-DESIGN.md +159 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +9 -0
- package/scripts/smoke-pure.ts +16 -1
- package/skills/open-memex/SKILL.md +51 -0
- package/src/cli.ts +41 -1
- package/src/distill-agents.ts +4 -3
- package/src/doctor.ts +32 -0
- package/src/first-run.ts +96 -0
- package/src/init.ts +225 -58
- package/src/mcp.ts +51 -32
- package/src/paths.ts +9 -0
- package/src/submit.ts +16 -0
- package/src/tools/memory.ts +4 -3
- package/src/tools/ops.ts +23 -3
package/AGENTS.md
CHANGED
|
@@ -64,13 +64,34 @@ previews keyword capture without writing, `open-memex doctor` runs health checks
|
|
|
64
64
|
(node version, config, scope resolution, storage writability, VS Code MCP enablement, MCP handshake).
|
|
65
65
|
`open-memex init` with no --client auto-detects and wires every installed editor
|
|
66
66
|
(`--yes` skips, scripts never prompt); `open-memex config set <key> <value>` edits settings after install.
|
|
67
|
+
`npm install -g` prints a pointer to `open-memex init` via a postinstall script
|
|
68
|
+
(print-only — postinstall must never prompt, it runs in CI/Docker; D50).
|
|
69
|
+
Note: npm swallows lifecycle-script stdout (background run unless
|
|
70
|
+
`--foreground-scripts`), so the postinstall pointer is best-effort only —
|
|
71
|
+
every CLI entry point also prints a one-line stderr nudge until init has run
|
|
72
|
+
or been declined (F28; stderr keeps the MCP stdio protocol intact).
|
|
73
|
+
Bare `open-memex` on a machine where init never completed offers to run it on a
|
|
74
|
+
TTY (usage as before when non-interactive); init/uninstall maintain a
|
|
75
|
+
`.init.json` first-run marker at the data root so the offer is asked once (D50);
|
|
76
|
+
init ends with a one-line next-step hint (`open-memex add` + ask the agent to
|
|
77
|
+
recall it) so a first-time user sees what "it works" looks like (D51).
|
|
78
|
+
init also installs the bundled `open-memex` Agent Skill (skills/open-memex/SKILL.md,
|
|
79
|
+
teaches skill-aware agents the CLI: save/search/scope rules/outbox flow) into each
|
|
80
|
+
wired editor's user-level skills dir — ~/.copilot/skills/ (VS Code),
|
|
81
|
+
~/.cursor/skills/ (Cursor), ~/.config/opencode/skills/ (opencode, per-project MCP
|
|
82
|
+
mode only — with the native plugin the skill is skipped and any previously installed
|
|
83
|
+
one is removed, D55); Visual Studio
|
|
84
|
+
has no skills concept and is skipped. Copy, not symlink (Windows needs no
|
|
85
|
+
Developer Mode); existing skill kept unless --force (D54).
|
|
67
86
|
`open-memex uninstall [--client vscode|cursor|opencode|visualstudio] [--global] [--yes]`
|
|
68
87
|
reverses init — removes the MCP server entry / opencode plugin line / Copilot
|
|
69
|
-
instructions section; memory data never touched (D48); no --client → auto-detect
|
|
88
|
+
instructions section / Agent Skill directory; memory data never touched (D48); no --client → auto-detect
|
|
70
89
|
with an interactive confirm, explicit --client never prompts; `--yes` only skips
|
|
71
90
|
that confirm; `--global` limits cleanup to user-level. Empty/whitespace-only
|
|
72
91
|
config files parse as `{}` and are safely populated (D49); non-JSON (JSONC)
|
|
73
|
-
files are left untouched with a printed manual snippet (D47).
|
|
92
|
+
files are left untouched with a printed manual snippet (D47). init/uninstall also sweep
|
|
93
|
+
pre-rename `my-o-memory` plugin entries and MCP server keys wherever they touch a
|
|
94
|
+
config, and `open-memex doctor` reports any remaining pre-rename leftovers (F30).
|
|
74
95
|
The published `open-memex` bin points at `dist/cli.js` (compiled at publish time).
|
|
75
96
|
From a source checkout, `npm run cli` / `npm run mcp` still run `src/` directly
|
|
76
97
|
with type-stripping — no build step needed for development.
|
package/README.md
CHANGED
|
@@ -113,6 +113,12 @@ This installs the `0.5.1` stable release.
|
|
|
113
113
|
npm install -g open-memex@alpha
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
```sh
|
|
117
|
+
open-memex init
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The install isn't complete until you run `open-memex init` — it wires up your editors (VS Code, Cursor, opencode, and Visual Studio for solution projects).
|
|
121
|
+
|
|
116
122
|
See what's published:
|
|
117
123
|
|
|
118
124
|
```sh
|
|
@@ -180,7 +186,10 @@ npx -y open-memex init --yes
|
|
|
180
186
|
With no `--client`, `init` **detects your installed editors and wires them all**
|
|
181
187
|
— user-level where the editor supports it (VS Code / Cursor MCP config, opencode
|
|
182
188
|
native plugin), so one init covers every project. Visual Studio joins in when the
|
|
183
|
-
project has a solution file.
|
|
189
|
+
project has a solution file. It also installs an **Agent Skill** (`open-memex`)
|
|
190
|
+
into each editor's skills folder (not opencode when the native plugin is wired —
|
|
191
|
+
the plugin already provides memory tools), so skill-aware agents can use your memory via
|
|
192
|
+
the CLI with no MCP configuration. Prefer to pick a single editor? Pass `--client`:
|
|
184
193
|
|
|
185
194
|
> **Two different "globals" — don't mix them up.**
|
|
186
195
|
> - `npm install -g open-memex` installs the *package* globally: it puts the
|
|
@@ -189,6 +198,14 @@ project has a solution file. Prefer to pick a single editor? Pass `--client`:
|
|
|
189
198
|
> project: init once, the wiring works in every project. It works the same
|
|
190
199
|
> whether the package was installed globally or run via npx.
|
|
191
200
|
|
|
201
|
+
Installing the package also prints a reminder to run `open-memex init` — the
|
|
202
|
+
wiring is a separate step. And if you run bare `open-memex` on a machine where
|
|
203
|
+
init never completed, it offers to run it for you (only on an interactive
|
|
204
|
+
terminal; scripts and CI just see the usual usage text). When init finishes,
|
|
205
|
+
it prints one concrete next step — save a memory with `open-memex add`, then
|
|
206
|
+
ask your agent to recall it — so a first-time user sees what "it works" looks
|
|
207
|
+
like.
|
|
208
|
+
|
|
192
209
|
**VS Code** (Copilot):
|
|
193
210
|
|
|
194
211
|
```sh
|
|
@@ -508,8 +525,9 @@ branches and PRs. Nothing moves without you naming it.
|
|
|
508
525
|
|
|
509
526
|
In an AI chat with the MCP server connected, just say **"sync memory"**
|
|
510
527
|
(or "同步记忆") — the agent runs the status check, summarizes the outbox drafts,
|
|
511
|
-
and asks which ones to sync. The
|
|
512
|
-
start
|
|
528
|
+
and asks which ones to sync. The server also tells the agent on its own: at
|
|
529
|
+
session start the handshake reports how many drafts are waiting, and every
|
|
530
|
+
memory-changing tool result carries the current count when it is non-zero.
|
|
513
531
|
|
|
514
532
|
```sh
|
|
515
533
|
open-memex sync-status
|
|
@@ -563,8 +581,8 @@ open-memex distill-agents [--scope project|personal] [--type t1,t2] [--limit N]
|
|
|
563
581
|
# (decisions, constraints, lessons, gotchas, howtos). Prints markdown;
|
|
564
582
|
# -o writes it to a file. You review and merge by hand — open-memex
|
|
565
583
|
# never rewrites your AGENTS.md on its own. The snippet ends with a
|
|
566
|
-
# "memory hygiene" section (§3.5
|
|
567
|
-
# AGENTS.md learn to propose distilled captures
|
|
584
|
+
# "memory hygiene" section (§3.5 distillation guidance) so agents reading
|
|
585
|
+
# AGENTS.md learn to propose distilled captures when a task ends.
|
|
568
586
|
|
|
569
587
|
open-memex propose <id...> --to project [--local-approve]
|
|
570
588
|
# propose one or several personal memories at once (one branch, one PR);
|
|
@@ -619,8 +637,9 @@ server with cwd set to your project root (`init` handles this for you).
|
|
|
619
637
|
> **Note:** MCP is request/response — it gives the agent tools, not the opencode
|
|
620
638
|
> plugin's automatic keyword capture or first-turn context injection. Proactive
|
|
621
639
|
> memory use depends on the agent's instructions: the server sends session-start
|
|
622
|
-
> guidance
|
|
623
|
-
>
|
|
640
|
+
> guidance in the MCP handshake `instructions` (including the live outbox draft
|
|
641
|
+
> count at session start, plus the pending count appended to memory-changing
|
|
642
|
+
> tool results when non-zero), and `init` writes the fuller version into the
|
|
624
643
|
> editor's instruction files. Both are advisory — no MCP consumer offers a hard
|
|
625
644
|
> session-start hook.
|
|
626
645
|
|
|
@@ -636,8 +655,8 @@ outbox → `sync-status` → `submit` (local branch+commit, push/PR on your Yes)
|
|
|
636
655
|
`export` / `import` archive for user portability (Markdown + manifest, no walled
|
|
637
656
|
garden; private excluded by default, `-a` / `--all` for full migration);
|
|
638
657
|
distill-to-AGENTS.md assist (`distill-agents`, propose-only — you merge by hand);
|
|
639
|
-
§3.5
|
|
640
|
-
|
|
658
|
+
§3.5 distillation in the MCP handshake + init instructions (the agent proposes
|
|
659
|
+
1–3 captures when a task ends, the human decides); 1–2 colleague pilot.
|
|
641
660
|
|
|
642
661
|
**`0.5.0` (stable):** init UX pass — `init --global` writes the editor wiring
|
|
643
662
|
once at user level (D45); bare `init` auto-detects installed editors and wires
|
package/README.zh-CN.md
CHANGED
|
@@ -108,6 +108,12 @@ npm install -g open-memex
|
|
|
108
108
|
npm install -g open-memex@alpha
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
```sh
|
|
112
|
+
open-memex init
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
不跑 `open-memex init` 把编辑器接上,安装就不算完成(支持 VS Code、Cursor、opencode,有 .sln 的项目还支持 Visual Studio)。
|
|
116
|
+
|
|
111
117
|
查看已发布版本:
|
|
112
118
|
|
|
113
119
|
```sh
|
|
@@ -173,6 +179,9 @@ npx -y open-memex init --yes
|
|
|
173
179
|
不带 `--client` 时,`init` 会**自动检测本机装了哪些编辑器,一次全接上**——
|
|
174
180
|
支持用户级的编辑器走用户级(VS Code / Cursor 的 MCP 配置、opencode 原生插件),
|
|
175
181
|
一次 init,所有项目通用;项目里有 solution 文件时 Visual Studio 也会一起配。
|
|
182
|
+
同时会给每个编辑器装一个 **Agent Skill**(`open-memex`,opencode 用原生插件时除外——
|
|
183
|
+
插件已经提供了记忆工具),懂 skill 的 agent
|
|
184
|
+
不用配 MCP 也能通过 CLI 用你的记忆。
|
|
176
185
|
想只配某一个编辑器?加 `--client`:
|
|
177
186
|
|
|
178
187
|
> **两个"全局"不是一回事,别搞混。**
|
|
@@ -181,6 +190,12 @@ npx -y open-memex init --yes
|
|
|
181
190
|
> - `init --global` 是把*编辑器配置*写到用户级而不是项目里:init 一次,
|
|
182
191
|
> 每个项目都生效。不管包是全局安装的还是用 npx 临时跑的,效果一样。
|
|
183
192
|
|
|
193
|
+
装完包还会打印一句提醒,让你跑 `open-memex init`——接线是独立的一步。
|
|
194
|
+
如果你在从没跑过 init 的机器上直接敲 `open-memex`,它会问你要不要现在
|
|
195
|
+
init(只在交互终端里问;脚本和 CI 里看到的还是原来的 usage)。
|
|
196
|
+
init 跑完会打印一个具体的下一步——用 `open-memex add` 存一条记忆,再让
|
|
197
|
+
agent 回忆它——让第一次用的用户一眼看到"跑起来了"是什么样子。
|
|
198
|
+
|
|
184
199
|
**VS Code**(Copilot):
|
|
185
200
|
|
|
186
201
|
```sh
|
|
@@ -482,8 +497,8 @@ project 草稿先住在 **appdata outbox**(git 看不见、跟分支无关)
|
|
|
482
497
|
没经过你点名,什么都不会动。
|
|
483
498
|
|
|
484
499
|
在接了 MCP 服务器的 AI 对话里,直接说 **"同步记忆"**(或 "sync memory")——
|
|
485
|
-
agent 会查状态、把 outbox
|
|
486
|
-
|
|
500
|
+
agent 会查状态、把 outbox 草稿逐条摘要、问你同步哪几条。服务器也会主动告诉
|
|
501
|
+
agent:新对话开始时握手里带待审草稿数,每次改记忆的 tool 返回里也带当前数(为零时不带)。
|
|
487
502
|
|
|
488
503
|
```sh
|
|
489
504
|
open-memex sync-status
|
|
@@ -530,7 +545,7 @@ open-memex distill-agents [--scope project|personal] [--type t1,t2] [--limit N]
|
|
|
530
545
|
# 把项目记忆(decision/constraint/lesson/gotcha/howto)提炼成
|
|
531
546
|
# AGENTS.md 片段。默认打印到 stdout;-o 写文件。人工审阅后手工合并——
|
|
532
547
|
# open-memex 永不自动改写你的 AGENTS.md。片段末尾带一段"记忆卫生"
|
|
533
|
-
# (§3.5
|
|
548
|
+
# (§3.5 蒸馏指引),让读 AGENTS.md 的 agent 学会在任务结束时提议蒸馏捕获。
|
|
534
549
|
|
|
535
550
|
open-memex propose <id...> --to project [--local-approve]
|
|
536
551
|
# 一次 propose 一条或多条(一个分支、一个 PR),每条独立新 id。
|
|
@@ -582,9 +597,9 @@ project scope 从进程工作目录解析,所以配置 server 时 cwd 要指
|
|
|
582
597
|
> **注意:** MCP 是请求/响应式的——它给 agent 提供 tools,但没有 opencode
|
|
583
598
|
> 插件的关键词自动捕获和首轮上下文注入。想让 agent 主动用记忆,
|
|
584
599
|
> 靠的是 agent 的 instructions:服务器在 MCP 握手的 `instructions` 里自带
|
|
585
|
-
> session-start
|
|
586
|
-
>
|
|
587
|
-
>
|
|
600
|
+
> session-start 指引(含开场时的 outbox 待审草稿数;改记忆的 tool 返回里也会
|
|
601
|
+
> 带当前数,为零时不带),`init` 则把更完整的版本写进编辑器的 instruction
|
|
602
|
+
> 文件。两者都是建议性的——MCP 客户端没有强制的 session-start hook。
|
|
588
603
|
|
|
589
604
|
## 路线图(Roadmap)
|
|
590
605
|
|
|
@@ -598,8 +613,8 @@ project scope 从进程工作目录解析,所以配置 server 时 cwd 要指
|
|
|
598
613
|
`export` / `import` 归档做用户可携带(Markdown + manifest,不造围墙花园;
|
|
599
614
|
private 默认不导出,`-a` / `--all` 全量迁移);
|
|
600
615
|
distill-to-AGENTS.md 辅助(`distill-agents`,只提议不改写——人工合并);
|
|
601
|
-
§3.5
|
|
602
|
-
(agent
|
|
616
|
+
§3.5 蒸馏写进 MCP 握手指令和 init 指令文件
|
|
617
|
+
(agent 在任务结束时提议 1–3 条捕获,人来定);找 1–2 个同事做 pilot。
|
|
603
618
|
|
|
604
619
|
**`0.5.0`(稳定版):** init 体验整修——`init --global` 一次写好用户级编辑器接线(D45);裸 `init` 自动检测已装编辑器并一次全接上(D46);非标准 JSON 配置不再报错,而是原样保留并打印手贴片段(D47);`uninstall` 逆转 `init` 且永不碰记忆数据(D48);空配置文件按空白处理、不再误判为损坏(D49)。"一份记忆,所有 Agent 通用":同一台机器上的每个编辑器,经由同一个 MCP 接口读写同一份记忆。
|
|
605
620
|
|
package/dist/cli.js
CHANGED
|
@@ -496,7 +496,42 @@ function printMcpConfig(client) {
|
|
|
496
496
|
}
|
|
497
497
|
async function main() {
|
|
498
498
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
499
|
-
if (!cmd
|
|
499
|
+
if (!cmd) {
|
|
500
|
+
// D50: fresh machine + interactive terminal → offer init instead of bare usage.
|
|
501
|
+
// Non-interactive (CI/scripts/pipes) prints usage exactly as before.
|
|
502
|
+
const { offerFirstRunInit } = await import("./first-run.js");
|
|
503
|
+
const outcome = await offerFirstRunInit(async () => {
|
|
504
|
+
// Re-exec `open-memex init` as a child with inherited stdio instead of
|
|
505
|
+
// calling initProject() in-process: the offer's readline already
|
|
506
|
+
// consumed stdin's buffer, and a second readline on the same stream
|
|
507
|
+
// would see EOF on burst input instead of the user's next answers.
|
|
508
|
+
const { spawnSync } = await import("node:child_process");
|
|
509
|
+
const r = spawnSync(process.execPath, [...process.execArgv, fileURLToPath(import.meta.url), "init"], { stdio: "inherit" });
|
|
510
|
+
if (r.error)
|
|
511
|
+
throw r.error;
|
|
512
|
+
if ((r.status ?? 1) !== 0)
|
|
513
|
+
process.exit(r.status ?? 1);
|
|
514
|
+
});
|
|
515
|
+
if (outcome !== "initialized")
|
|
516
|
+
usage(0);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
// F28: npm runs lifecycle scripts in the background and swallows their
|
|
520
|
+
// stdout (unless --foreground-scripts), so the D50 postinstall pointer
|
|
521
|
+
// never reaches the user. Every CLI entry point therefore carries a
|
|
522
|
+
// one-line nudge on stderr until init has run or been declined — stderr
|
|
523
|
+
// keeps the MCP stdio protocol (stdout) intact, and the .init.json marker
|
|
524
|
+
// makes it once-ever. `init`/`uninstall` are excluded (already there /
|
|
525
|
+
// nothing to wire). Placed before the --help/--version early returns:
|
|
526
|
+
// `-v` is the first thing people run after installing.
|
|
527
|
+
if (cmd !== "init" && cmd !== "uninstall") {
|
|
528
|
+
const { isFirstRun } = await import("./first-run.js");
|
|
529
|
+
if (isFirstRun()) {
|
|
530
|
+
console.error("open-memex: first run? `open-memex init` wires it into your editors " +
|
|
531
|
+
"(auto-detects VS Code, Cursor, opencode — and Visual Studio for solution projects).");
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help")
|
|
500
535
|
usage(0);
|
|
501
536
|
if (cmd === "--version" || cmd === "-v") {
|
|
502
537
|
// package.json sits two levels above this file in both layouts
|
package/dist/distill-agents.js
CHANGED
|
@@ -53,10 +53,11 @@ export function distillAgentsMarkdown(opts) {
|
|
|
53
53
|
}
|
|
54
54
|
// D43 — §3.5 memory-hygiene footer (double insurance for opencode users,
|
|
55
55
|
// who never see the MCP handshake / init instructions): teach the agent
|
|
56
|
-
// reading this AGENTS.md to propose distilled captures
|
|
56
|
+
// reading this AGENTS.md to propose distilled captures when a task ends
|
|
57
|
+
// (D53: the checkpoint mechanism is gone).
|
|
57
58
|
lines.push(`### Memory hygiene (open-memex)`);
|
|
58
59
|
lines.push(``);
|
|
59
|
-
lines.push(`-
|
|
60
|
+
lines.push(`- When you finish a task the user would describe in one sentence, distill`, ` the session: propose 1–3 short memories capturing the useful`, ` conclusion — what was learned or decided, how an issue was resolved, what`, ` to avoid, where the authoritative doc lives — not the raw transcript.`, ` Save nothing without user approval.`, `- If the knowledge already lives in project docs, save a \`reference\` memory`, ` pointing at the doc instead of copying it.`);
|
|
60
61
|
lines.push(``);
|
|
61
62
|
return lines.join("\n");
|
|
62
63
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { loadConfig, configSource, DEFAULT_CONFIG } from "./config.js";
|
|
11
11
|
import { paths } from "./paths.js";
|
|
12
12
|
import { resolveCwdScope } from "./scope.js";
|
|
13
|
+
import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.js";
|
|
13
14
|
const EXPECTED_TOOLS = [
|
|
14
15
|
"memory_add",
|
|
15
16
|
"memory_search",
|
|
@@ -278,6 +279,7 @@ export async function runDoctor() {
|
|
|
278
279
|
console.log("open-memex doctor");
|
|
279
280
|
const checks = [nodeCheck(), configCheck(), scopeCheck(), storageCheck(), vscodeMcpCheck()];
|
|
280
281
|
checks.push(await mcpCheck());
|
|
282
|
+
checks.push(legacyCheck());
|
|
281
283
|
let allOk = true;
|
|
282
284
|
for (const c of checks) {
|
|
283
285
|
console.log(` ${c.ok ? "ok " : "FAIL"} ${c.name}: ${c.detail}`);
|
|
@@ -287,3 +289,31 @@ export async function runDoctor() {
|
|
|
287
289
|
console.log(allOk ? "All checks passed." : "Some checks failed — see above.");
|
|
288
290
|
return allOk;
|
|
289
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* F30: the my-o-memory → open-memex rename left configs and data behind that
|
|
294
|
+
* silently split memories across two data dirs (the exact trap: the old
|
|
295
|
+
* opencode plugin kept loading and writing to the OLD dir). Read-only.
|
|
296
|
+
*/
|
|
297
|
+
function legacyCheck() {
|
|
298
|
+
const name = "legacy my-o-memory";
|
|
299
|
+
const found = [];
|
|
300
|
+
const envHome = process.env.MY_O_MEMORY_HOME;
|
|
301
|
+
if (envHome && envHome.includes(LEGACY_PACKAGE_NAME))
|
|
302
|
+
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\``);
|
|
303
|
+
const root = paths().root;
|
|
304
|
+
const legacyDir = path.join(path.dirname(root), LEGACY_PACKAGE_NAME);
|
|
305
|
+
if (fs.existsSync(legacyDir) && fs.statSync(legacyDir).isDirectory())
|
|
306
|
+
found.push(`legacy data dir ${legacyDir} — merge it with \`open-memex migrate --to-v2\``);
|
|
307
|
+
for (const f of [opencodeGlobalConfigPath(), userMcpConfigPath("vscode"), userMcpConfigPath("cursor")]) {
|
|
308
|
+
try {
|
|
309
|
+
if (fs.existsSync(f) && fs.readFileSync(f, "utf8").includes(LEGACY_PACKAGE_NAME))
|
|
310
|
+
found.push(`stale "${LEGACY_PACKAGE_NAME}" reference in ${f} — re-run \`open-memex init --force --yes\``);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
/* unreadable — not this check's problem */
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return found.length === 0
|
|
317
|
+
? { name, ok: true, detail: "no pre-rename leftovers found" }
|
|
318
|
+
: { name, ok: false, detail: found.join("; ") };
|
|
319
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D50: first-run init offer. `npm install -g open-memex` only puts the CLI on
|
|
3
|
+
* PATH — the editor wiring is `init`'s job, and a clean reinstall wipes it.
|
|
4
|
+
* When bare `open-memex` runs on a machine where init never completed, offer
|
|
5
|
+
* to run it instead of just printing usage.
|
|
6
|
+
*
|
|
7
|
+
* The "asked" state is a marker file at the data root (`init` writes it on
|
|
8
|
+
* success, a declined offer writes it too), so the question is asked exactly
|
|
9
|
+
* once. `uninstall` removes it — unwiring is the reverse of init, so the next
|
|
10
|
+
* bare run offers to wire again. The marker is a dotfile: export builds from
|
|
11
|
+
* DB rows, never by walking the data root, so it can't leak into bundles.
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import readline from "node:readline";
|
|
16
|
+
import { dataRootPath } from "./paths.js";
|
|
17
|
+
const MARKER = ".init.json";
|
|
18
|
+
export function firstRunMarkerPath(root = dataRootPath()) {
|
|
19
|
+
return path.join(root, MARKER);
|
|
20
|
+
}
|
|
21
|
+
/** True when init has neither run nor been declined on this machine. */
|
|
22
|
+
export function isFirstRun(root) {
|
|
23
|
+
return !fs.existsSync(firstRunMarkerPath(root ?? dataRootPath()));
|
|
24
|
+
}
|
|
25
|
+
/** Record the outcome — best effort; a missing marker just asks again next time. */
|
|
26
|
+
export function markFirstRunDone(state) {
|
|
27
|
+
const file = firstRunMarkerPath();
|
|
28
|
+
try {
|
|
29
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
30
|
+
fs.writeFileSync(file, JSON.stringify({ v: 1, state, at: new Date().toISOString() }) + "\n");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* ignore */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function clearFirstRunMarker() {
|
|
37
|
+
try {
|
|
38
|
+
fs.rmSync(firstRunMarkerPath(), { force: true });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* ignore */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Pure decision, kept separate for tests: prompt only on an interactive
|
|
45
|
+
* terminal — scripts, CI and piped runs never get the question. */
|
|
46
|
+
export function shouldOfferFirstRun(tty = { stdinTTY: process.stdin.isTTY, stdoutTTY: process.stdout.isTTY }, firstRun = isFirstRun()) {
|
|
47
|
+
return firstRun && !!tty.stdinTTY && !!tty.stdoutTTY;
|
|
48
|
+
}
|
|
49
|
+
function askYesNo(question) {
|
|
50
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
rl.question(question, (ans) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
resolve(!/^\s*(n|no)\s*$/i.test(ans));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Bare-`open-memex` first-run flow. `runInit` is injected so tests don't need
|
|
60
|
+
* the real init. Returns "skipped" when there's nothing to ask (already set
|
|
61
|
+
* up, or non-interactive) — the caller then prints usage as before.
|
|
62
|
+
*/
|
|
63
|
+
export async function offerFirstRunInit(runInit) {
|
|
64
|
+
if (!shouldOfferFirstRun())
|
|
65
|
+
return "skipped";
|
|
66
|
+
console.log(`It looks like open-memex hasn't been set up on this machine yet.\n` +
|
|
67
|
+
"`open-memex init` wires it into your editors (auto-detects VS Code, Cursor and opencode — and Visual Studio for solution projects).\n");
|
|
68
|
+
if (await askYesNo("Run it now? [Y/n] ")) {
|
|
69
|
+
await runInit();
|
|
70
|
+
return "initialized";
|
|
71
|
+
}
|
|
72
|
+
markFirstRunDone("declined");
|
|
73
|
+
console.log("No problem — run `open-memex init` any time.");
|
|
74
|
+
return "declined";
|
|
75
|
+
}
|