nlm-memory 0.4.2 → 0.5.1
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/README.md +72 -34
- package/dist/cli/nlm.js +223 -33
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/adapters/cursor.d.ts +45 -0
- package/dist/core/adapters/cursor.js +397 -0
- package/dist/core/adapters/cursor.js.map +1 -0
- package/dist/core/adapters/from-source.js +10 -0
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/windsurf.d.ts +44 -0
- package/dist/core/adapters/windsurf.js +299 -0
- package/dist/core/adapters/windsurf.js.map +1 -0
- package/dist/core/hook/claude-settings.d.ts +12 -5
- package/dist/core/hook/claude-settings.js +21 -6
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +1 -1
- package/dist/core/sources/source-registry.js +18 -0
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +2 -0
- package/dist/core/storage/sqlite-session-store.js +38 -2
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/hook/hook-auth.d.ts +13 -0
- package/dist/hook/hook-auth.js +19 -0
- package/dist/hook/hook-auth.js.map +1 -0
- package/dist/hook/prompt-recall-hook.js +7 -1
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +4 -1
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +4 -1
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +2 -0
- package/dist/http/app.js +76 -1
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.js +1 -1
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/cursor.d.ts +25 -0
- package/dist/install/cursor.js +43 -0
- package/dist/install/cursor.js.map +1 -0
- package/dist/install/nlm-dir-perms.d.ts +19 -0
- package/dist/install/nlm-dir-perms.js +43 -0
- package/dist/install/nlm-dir-perms.js.map +1 -0
- package/dist/install/ollama.d.ts +18 -1
- package/dist/install/ollama.js +62 -7
- package/dist/install/ollama.js.map +1 -1
- package/dist/install/setup.d.ts +4 -0
- package/dist/install/setup.js +141 -18
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +25 -0
- package/dist/install/windsurf.js +43 -0
- package/dist/install/windsurf.js.map +1 -0
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/shared/types.d.ts +4 -0
- package/dist/ui/assets/{index-BA6IpU8g.css → index-Beo8psd-.css} +1 -1
- package/dist/ui/assets/index-CSPTTeeM.js +69 -0
- package/dist/ui/index.html +2 -2
- package/package.json +26 -1
- package/plugin/scripts/prompt-recall-hook.mjs +55 -4
- package/plugin/scripts/stop-hook.mjs +57 -6
- package/.agents/plugins/marketplace.json +0 -20
- package/.github/workflows/ci.yml +0 -30
- package/dist/ui/assets/index-B_qIVV0k.js +0 -69
- package/docs/methodology/re-derivation-rate.md +0 -112
- package/docs/methodology/useful-hit-rate.md +0 -79
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
- package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
- package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
- package/docs/plans/desktop-product.md +0 -69
- package/docs/plans/factstore-design.md +0 -236
- package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1389
- package/logs/CHANGELOG/CHANGELOG.md +0 -337
- package/migrations/000_initial_schema.sql +0 -174
- package/migrations/001_entity_type_rename.sql +0 -17
- package/migrations/002_adapter_state_extend.sql +0 -12
- package/migrations/003_session_embeddings.sql +0 -11
- package/migrations/004_facts.sql +0 -46
- package/migrations/005_sources.sql +0 -31
- package/migrations/006_providers.sql +0 -33
- package/migrations/007_source_tokens.sql +0 -17
- package/migrations/008_fts_rebuild.sql +0 -9
- package/migrations/009_session_embedding_chunks.sql +0 -46
- package/migrations/010_sources_opencode.sql +0 -30
- package/migrations/011_sources_hermes_agent.sql +0 -30
- package/migrations/012_sources_aider.sql +0 -30
- package/migrations/013_adapter_state_failure_count.sql +0 -12
- package/plugin-hermes-agent/README.md +0 -49
- package/plugin-hermes-agent/__init__.py +0 -75
- package/plugin-hermes-agent/plugin.yaml +0 -15
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +0 -61
- package/scripts/deepseek-probe.mjs +0 -67
- package/scripts/extract-triples.mjs +0 -207
- package/scripts/longmemeval/embedding-cache.ts +0 -77
- package/scripts/longmemeval/fetch-dataset.sh +0 -25
- package/scripts/longmemeval/run-harness.ts +0 -315
- package/scripts/longmemeval/scorer.ts +0 -99
- package/scripts/longmemeval/tsconfig.json +0 -9
- package/scripts/longmemeval/types.ts +0 -35
- package/scripts/nlm-daily-digest.py +0 -239
- package/scripts/nlm-daily-digest.sh +0 -28
- package/src/cli/classify-parity.ts +0 -257
- package/src/cli/launchctl-helpers.ts +0 -49
- package/src/cli/nlm.ts +0 -885
- package/src/core/actions/actions-log.ts +0 -118
- package/src/core/actions/overlay.ts +0 -117
- package/src/core/adapters/aider.ts +0 -205
- package/src/core/adapters/claude-code.ts +0 -293
- package/src/core/adapters/common.ts +0 -54
- package/src/core/adapters/from-source.ts +0 -57
- package/src/core/adapters/hermes-agent.ts +0 -240
- package/src/core/adapters/hermes.ts +0 -277
- package/src/core/adapters/jsonl-generic.ts +0 -208
- package/src/core/adapters/opencode.ts +0 -281
- package/src/core/adapters/pi.ts +0 -264
- package/src/core/classifier/prompt.ts +0 -200
- package/src/core/dataset/build-dataset.ts +0 -463
- package/src/core/embedding/chunk-body.ts +0 -76
- package/src/core/embedding/embed-backfill.ts +0 -210
- package/src/core/embedding/embed-normalize.ts +0 -135
- package/src/core/facts/backfill-facts.ts +0 -254
- package/src/core/facts/extract-facts.ts +0 -50
- package/src/core/hook/citation-detect.ts +0 -124
- package/src/core/hook/cite-memo.ts +0 -68
- package/src/core/hook/claude-settings.ts +0 -166
- package/src/core/hook/gate.ts +0 -25
- package/src/core/hook/hook-log.ts +0 -41
- package/src/core/hook/memo-sweep.ts +0 -164
- package/src/core/hook/memo.ts +0 -67
- package/src/core/hook/pointer-block.ts +0 -26
- package/src/core/hook/select.ts +0 -32
- package/src/core/hook/transcript.ts +0 -121
- package/src/core/ingest/ingest-session.ts +0 -111
- package/src/core/providers/provider-models.ts +0 -100
- package/src/core/providers/provider-registry.ts +0 -196
- package/src/core/recall/citation-log.ts +0 -108
- package/src/core/recall/filter.ts +0 -27
- package/src/core/recall/index.ts +0 -6
- package/src/core/recall/match-fields.ts +0 -40
- package/src/core/recall/query-log.ts +0 -149
- package/src/core/recall/query-shape.ts +0 -66
- package/src/core/recall/recall-service.ts +0 -320
- package/src/core/recall/recent-log.ts +0 -59
- package/src/core/recall/tokenize.ts +0 -18
- package/src/core/recall/useful-scan.ts +0 -336
- package/src/core/recall-facts/fact-query-log.ts +0 -150
- package/src/core/recall-facts/fact-recall-service.ts +0 -327
- package/src/core/scheduler/scan-once.ts +0 -142
- package/src/core/scheduler/scheduler.ts +0 -225
- package/src/core/sources/source-registry.ts +0 -260
- package/src/core/storage/db-restore.ts +0 -133
- package/src/core/storage/live-status.ts +0 -45
- package/src/core/storage/migrate.ts +0 -72
- package/src/core/storage/sqlite-fact-store.ts +0 -304
- package/src/core/storage/sqlite-session-store.ts +0 -765
- package/src/hook/prompt-recall-hook.ts +0 -174
- package/src/hook/session-end-hook.ts +0 -81
- package/src/hook/session-start-hook.ts +0 -165
- package/src/hook/stop-hook.ts +0 -236
- package/src/http/app.ts +0 -1137
- package/src/install/claude-code.ts +0 -128
- package/src/install/codex.ts +0 -367
- package/src/install/hermes-agent.ts +0 -76
- package/src/install/hermes.ts +0 -78
- package/src/install/ollama.ts +0 -211
- package/src/install/setup.ts +0 -368
- package/src/llm/classifier-box.ts +0 -64
- package/src/llm/deepseek-client.ts +0 -150
- package/src/llm/env-autoload.ts +0 -55
- package/src/llm/ollama-client.ts +0 -189
- package/src/mcp/server.ts +0 -534
- package/src/ports/fact-store.ts +0 -102
- package/src/ports/llm-client.ts +0 -52
- package/src/ports/logger.ts +0 -16
- package/src/ports/session-store.ts +0 -45
- package/src/ports/transcript-adapter.ts +0 -55
- package/src/shared/types.ts +0 -145
- package/src/ui/App.tsx +0 -58
- package/src/ui/components/PromoteOpenButton.tsx +0 -65
- package/src/ui/components/SessionDrawer.tsx +0 -136
- package/src/ui/components/SideNav.tsx +0 -162
- package/src/ui/components/Skeleton.tsx +0 -107
- package/src/ui/index.html +0 -13
- package/src/ui/lib/actions.ts +0 -30
- package/src/ui/lib/api.ts +0 -92
- package/src/ui/lib/dataset.ts +0 -141
- package/src/ui/lib/registries.ts +0 -155
- package/src/ui/lib/view-settings.ts +0 -41
- package/src/ui/main.tsx +0 -15
- package/src/ui/pages/Live.tsx +0 -229
- package/src/ui/pages/Pulse.tsx +0 -415
- package/src/ui/pages/Recall.tsx +0 -190
- package/src/ui/pages/River.tsx +0 -308
- package/src/ui/pages/Search.tsx +0 -93
- package/src/ui/pages/Stub.tsx +0 -9
- package/src/ui/pages/Thread.tsx +0 -262
- package/src/ui/pages/settings/Classifier.tsx +0 -227
- package/src/ui/pages/settings/Data.tsx +0 -190
- package/src/ui/pages/settings/Index.tsx +0 -65
- package/src/ui/pages/settings/Labels.tsx +0 -224
- package/src/ui/pages/settings/Providers.tsx +0 -305
- package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
- package/src/ui/pages/settings/Sources.tsx +0 -326
- package/src/ui/pages/settings/Views.tsx +0 -96
- package/src/ui/styles.css +0 -1766
- package/src/ui/tsconfig.json +0 -21
- package/src/ui/vite.config.ts +0 -19
- package/tests/fixtures/claude_code/short_session.jsonl +0 -2
- package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
- package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
- package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
- package/tests/fixtures/facts.ts +0 -17
- package/tests/fixtures/golden-corpus.ts +0 -85
- package/tests/fixtures/hermes/paired_request_dump.json +0 -24
- package/tests/fixtures/hermes/paired_session.json +0 -23
- package/tests/fixtures/hermes/request_dump.json +0 -28
- package/tests/fixtures/hermes/session_iso.json +0 -38
- package/tests/fixtures/hermes/session_unix.json +0 -38
- package/tests/fixtures/hermes/system_only.json +0 -18
- package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
- package/tests/fixtures/pi/short-successful.jsonl +0 -5
- package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
- package/tests/fixtures/sessions.ts +0 -22
- package/tests/integration/backfill-facts.test.ts +0 -362
- package/tests/integration/citation-explicit.test.ts +0 -111
- package/tests/integration/cite-event.test.ts +0 -169
- package/tests/integration/cite-memo.test.ts +0 -87
- package/tests/integration/db-restore.test.ts +0 -153
- package/tests/integration/embed-backfill.test.ts +0 -176
- package/tests/integration/fact-supersedence.test.ts +0 -313
- package/tests/integration/fts-index.test.ts +0 -60
- package/tests/integration/getbyids-sqlite.test.ts +0 -60
- package/tests/integration/hermes-agent-hooks.test.ts +0 -248
- package/tests/integration/hook-claude-settings.test.ts +0 -205
- package/tests/integration/hook-log.test.ts +0 -54
- package/tests/integration/hook-memo.test.ts +0 -68
- package/tests/integration/hook-pre-compact.test.ts +0 -105
- package/tests/integration/hook-subagent-start.test.ts +0 -102
- package/tests/integration/http.test.ts +0 -401
- package/tests/integration/keyword-search-fts.test.ts +0 -66
- package/tests/integration/mcp-recall-logging.test.ts +0 -88
- package/tests/integration/mcp.test.ts +0 -248
- package/tests/integration/memo-sweep.test.ts +0 -91
- package/tests/integration/prompt-recall-hook.test.ts +0 -88
- package/tests/integration/provider-registry.test.ts +0 -107
- package/tests/integration/recall-golden.test.ts +0 -59
- package/tests/integration/recall-sqlite.test.ts +0 -169
- package/tests/integration/scheduler.test.ts +0 -391
- package/tests/integration/session-end-hook.test.ts +0 -48
- package/tests/integration/session-start-hook.test.ts +0 -126
- package/tests/integration/source-registry.test.ts +0 -120
- package/tests/integration/sqlite-fact-store.test.ts +0 -346
- package/tests/integration/stop-hook.test.ts +0 -560
- package/tests/integration/wal-checkpoint.test.ts +0 -49
- package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
- package/tests/unit/core/adapters/aider.test.ts +0 -230
- package/tests/unit/core/adapters/claude-code.test.ts +0 -118
- package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
- package/tests/unit/core/adapters/hermes.test.ts +0 -81
- package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
- package/tests/unit/core/adapters/opencode.test.ts +0 -354
- package/tests/unit/core/adapters/pi.test.ts +0 -110
- package/tests/unit/core/classifier/prompt.test.ts +0 -126
- package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
- package/tests/unit/core/facts/extract-facts.test.ts +0 -117
- package/tests/unit/core/filter.test.ts +0 -40
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
- package/tests/unit/core/hook/citation-detect.test.ts +0 -124
- package/tests/unit/core/hook/gate.test.ts +0 -29
- package/tests/unit/core/hook/pointer-block.test.ts +0 -22
- package/tests/unit/core/hook/select.test.ts +0 -66
- package/tests/unit/core/match-fields.test.ts +0 -39
- package/tests/unit/core/mcp-cite-session.test.ts +0 -51
- package/tests/unit/core/providers/provider-models.test.ts +0 -101
- package/tests/unit/core/query-shape.test.ts +0 -92
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
- package/tests/unit/core/recall-service.test.ts +0 -200
- package/tests/unit/core/storage/live-status.test.ts +0 -54
- package/tests/unit/core/tokenize.test.ts +0 -32
- package/tests/unit/core/useful-scan.test.ts +0 -537
- package/tests/unit/llm/embed.test.ts +0 -93
- package/tests/unit/llm/ollama-client.test.ts +0 -124
- package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
- package/tsconfig.json +0 -31
- package/tsconfig.test.json +0 -11
- package/vitest.config.ts +0 -22
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `nlm connect claude-code` / `nlm disconnect claude-code` — writes the
|
|
3
|
-
* nlm-memory MCP server block into ~/.mcp.json and removes it on disconnect.
|
|
4
|
-
*
|
|
5
|
-
* ~/.mcp.json is the global MCP config file that Claude Code reads on
|
|
6
|
-
* startup. We merge our entry into the existing mcpServers object rather
|
|
7
|
-
* than replacing the file, so other MCP servers the user has configured are
|
|
8
|
-
* preserved.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { homedir } from "node:os";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
14
|
-
import type { ClaudeHookEvent } from "../core/hook/claude-settings.js";
|
|
15
|
-
|
|
16
|
-
export interface ConnectClaudeCodeOptions {
|
|
17
|
-
readonly nlmBinPath: string;
|
|
18
|
-
readonly nodeExecPath: string;
|
|
19
|
-
readonly dryRun?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ConnectClaudeCodeReport {
|
|
23
|
-
readonly mcpConfigPath: string;
|
|
24
|
-
readonly alreadyPresent: boolean;
|
|
25
|
-
readonly written: boolean;
|
|
26
|
-
readonly dryRun: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface DisconnectClaudeCodeReport {
|
|
30
|
-
readonly mcpConfigPath: string;
|
|
31
|
-
readonly removed: boolean;
|
|
32
|
-
readonly dryRun: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function mcpConfigPath(): string {
|
|
36
|
-
return process.env["NLM_MCP_CONFIG"] ?? join(homedir(), ".mcp.json");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function readConfig(path: string): Record<string, unknown> {
|
|
40
|
-
if (!existsSync(path)) return {};
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
|
|
43
|
-
} catch {
|
|
44
|
-
throw new Error(`${path} is not valid JSON. Fix or remove it, then re-run \`nlm connect claude-code\`.`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function connectClaudeCode(opts: ConnectClaudeCodeOptions): ConnectClaudeCodeReport {
|
|
49
|
-
const configPath = mcpConfigPath();
|
|
50
|
-
const config = readConfig(configPath);
|
|
51
|
-
const mcpServers = (config["mcpServers"] ?? {}) as Record<string, unknown>;
|
|
52
|
-
const alreadyPresent = "nlm-memory" in mcpServers;
|
|
53
|
-
|
|
54
|
-
if (!opts.dryRun) {
|
|
55
|
-
mcpServers["nlm-memory"] = {
|
|
56
|
-
command: opts.nodeExecPath,
|
|
57
|
-
args: [opts.nlmBinPath, "mcp"],
|
|
58
|
-
};
|
|
59
|
-
config["mcpServers"] = mcpServers;
|
|
60
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
61
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return { mcpConfigPath: configPath, alreadyPresent, written: !opts.dryRun, dryRun: opts.dryRun ?? false };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── Hook install helper (shared by setup wizard + connect --with-hooks) ──────
|
|
68
|
-
|
|
69
|
-
export interface HookSpec {
|
|
70
|
-
readonly event: ClaudeHookEvent;
|
|
71
|
-
readonly script: string;
|
|
72
|
-
readonly label: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface HookInstallOptions {
|
|
76
|
-
readonly nodeExecPath: string;
|
|
77
|
-
readonly hooks: ReadonlyArray<HookSpec>;
|
|
78
|
-
readonly settingsPath: string;
|
|
79
|
-
readonly hookLogPath: string;
|
|
80
|
-
readonly addHook: (path: string, command: string, event?: ClaudeHookEvent) => void;
|
|
81
|
-
readonly removeHook: (path: string, event?: ClaudeHookEvent | "*") => void;
|
|
82
|
-
readonly buildHookCommand: (nodeExec: string, script: string, mode: "shadow" | "live") => string;
|
|
83
|
-
readonly smokeTestHookCommand: (command: string, logPath: string) => { ok: boolean; reason?: string; stderr?: string };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface HookInstallResult {
|
|
87
|
-
readonly ok: boolean;
|
|
88
|
-
readonly count: number;
|
|
89
|
-
readonly failedLabel?: string;
|
|
90
|
-
readonly errorMessage?: string;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function installClaudeCodeHooks(opts: HookInstallOptions): HookInstallResult {
|
|
94
|
-
const installed: HookSpec[] = [];
|
|
95
|
-
for (const spec of opts.hooks) {
|
|
96
|
-
try {
|
|
97
|
-
const command = opts.buildHookCommand(opts.nodeExecPath, spec.script, "shadow");
|
|
98
|
-
opts.addHook(opts.settingsPath, command, spec.event);
|
|
99
|
-
const smoke = opts.smokeTestHookCommand(command, opts.hookLogPath);
|
|
100
|
-
if (!smoke.ok) {
|
|
101
|
-
for (const prior of [...installed, spec]) opts.removeHook(opts.settingsPath, prior.event);
|
|
102
|
-
const result: HookInstallResult = { ok: false, count: installed.length, failedLabel: spec.label };
|
|
103
|
-
return smoke.reason ? { ...result, errorMessage: smoke.reason } : result;
|
|
104
|
-
}
|
|
105
|
-
installed.push(spec);
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return { ok: false, count: installed.length, failedLabel: spec.label, errorMessage: e instanceof Error ? e.message : String(e) };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return { ok: true, count: installed.length };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function disconnectClaudeCode(opts?: { dryRun?: boolean }): DisconnectClaudeCodeReport {
|
|
114
|
-
const configPath = mcpConfigPath();
|
|
115
|
-
const config = readConfig(configPath);
|
|
116
|
-
const mcpServers = config["mcpServers"] as Record<string, unknown> | undefined;
|
|
117
|
-
|
|
118
|
-
if (!mcpServers || !("nlm-memory" in mcpServers)) {
|
|
119
|
-
return { mcpConfigPath: configPath, removed: false, dryRun: opts?.dryRun ?? false };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!opts?.dryRun) {
|
|
123
|
-
delete mcpServers["nlm-memory"];
|
|
124
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { mcpConfigPath: configPath, removed: true, dryRun: opts?.dryRun ?? false };
|
|
128
|
-
}
|
package/src/install/codex.ts
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `nlm connect codex` / `nlm disconnect codex` — installs nlm-memory as a
|
|
3
|
-
* Codex CLI plugin via the marketplace mechanism.
|
|
4
|
-
*
|
|
5
|
-
* Two distribution surfaces:
|
|
6
|
-
*
|
|
7
|
-
* 1. The plugin path (default). Registers a Codex marketplace pointing at
|
|
8
|
-
* pbmagnet4/nlm-memory-ts and installs the `nlm-memory` plugin from it.
|
|
9
|
-
* Codex prompts for hook trust on first invocation; once trusted,
|
|
10
|
-
* UserPromptSubmit + Stop hooks fire, and the .mcp.json wires the
|
|
11
|
-
* `nlm-memory` MCP server alongside.
|
|
12
|
-
*
|
|
13
|
-
* 2. The legacy hooks.json fallback (--with-hooks). For Codex Desktop
|
|
14
|
-
* builds where openai/codex#16430 blocks plugin-local hook dispatch,
|
|
15
|
-
* additionally writes absolute paths into ~/.codex/hooks.json so the
|
|
16
|
-
* hooks fire via the project-local code path. MCP still comes through
|
|
17
|
-
* the plugin's .mcp.json.
|
|
18
|
-
*
|
|
19
|
-
* Marketplace + plugin add are delegated to the `codex` binary rather than
|
|
20
|
-
* mutating ~/.codex/config.toml directly — the binary owns the trust state
|
|
21
|
-
* machine and the snapshot fetch flow, and writing TOML by hand would race
|
|
22
|
-
* against codex's own writes. The legacy hooks.json IS authored directly
|
|
23
|
-
* because it's a project-local file the binary doesn't manage.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { spawnSync } from "node:child_process";
|
|
27
|
-
import {
|
|
28
|
-
existsSync,
|
|
29
|
-
mkdirSync,
|
|
30
|
-
readFileSync,
|
|
31
|
-
writeFileSync,
|
|
32
|
-
} from "node:fs";
|
|
33
|
-
import { homedir } from "node:os";
|
|
34
|
-
import { dirname, join, resolve } from "node:path";
|
|
35
|
-
|
|
36
|
-
const DEFAULT_SOURCE = "pbmagnet4/nlm-memory-ts";
|
|
37
|
-
const PLUGIN_NAME = "nlm-memory";
|
|
38
|
-
// Marketplace name is derived from the source's basename by codex when
|
|
39
|
-
// `codex plugin marketplace add <source>` runs without a --name flag. For
|
|
40
|
-
// owner/repo this is the repo name; for a local path it's the directory
|
|
41
|
-
// basename. Both resolve to "nlm-memory-ts" in our case.
|
|
42
|
-
const MARKETPLACE_NAME = "nlm-memory-ts";
|
|
43
|
-
|
|
44
|
-
// Marker substring identifying entries this CLI owns in ~/.codex/hooks.json
|
|
45
|
-
// so disconnect can strip only our entries and leave anything the user
|
|
46
|
-
// added by hand intact.
|
|
47
|
-
const LEGACY_HOOK_MARKER = "/plugin/scripts/";
|
|
48
|
-
|
|
49
|
-
// Sentinels bracketing the [mcp_servers.nlm-memory] block we manage in
|
|
50
|
-
// ~/.codex/config.toml. Sentinel-bracketed regions are removed atomically
|
|
51
|
-
// on disconnect and replaced atomically on connect — no TOML parser
|
|
52
|
-
// required, no risk of mangling user-authored entries above or below.
|
|
53
|
-
const MCP_SENTINEL_BEGIN = "# >>> nlm-memory (managed by nlm connect codex)";
|
|
54
|
-
const MCP_SENTINEL_END = "# <<< nlm-memory";
|
|
55
|
-
|
|
56
|
-
export interface ConnectOptions {
|
|
57
|
-
readonly source?: string;
|
|
58
|
-
readonly withHooks?: boolean;
|
|
59
|
-
readonly dryRun?: boolean;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface DisconnectOptions {
|
|
63
|
-
readonly withHooks?: boolean;
|
|
64
|
-
readonly dryRun?: boolean;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface CodexCommandResult {
|
|
68
|
-
readonly status: number | null;
|
|
69
|
-
readonly stdout: string;
|
|
70
|
-
readonly stderr: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function runCodex(args: ReadonlyArray<string>): CodexCommandResult {
|
|
74
|
-
const result = spawnSync("codex", args, { encoding: "utf8" });
|
|
75
|
-
return {
|
|
76
|
-
status: result.status,
|
|
77
|
-
stdout: result.stdout ?? "",
|
|
78
|
-
stderr: result.stderr ?? "",
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function codexBinaryAvailable(): boolean {
|
|
83
|
-
const r = spawnSync("codex", ["--version"], { encoding: "utf8" });
|
|
84
|
-
return r.status === 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function codexHooksPath(): string {
|
|
88
|
-
return process.env["NLM_CODEX_HOOKS"] ?? join(homedir(), ".codex", "hooks.json");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function codexConfigPath(): string {
|
|
92
|
-
return process.env["NLM_CODEX_CONFIG"] ?? join(homedir(), ".codex", "config.toml");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Idempotently insert (or update) the [mcp_servers.nlm-memory] block in
|
|
97
|
-
* ~/.codex/config.toml. The block is bracketed by sentinel comments so a
|
|
98
|
-
* later disconnect can strip the exact region without touching anything
|
|
99
|
-
* else. MCP wiring is universal infrastructure — every runtime gets its
|
|
100
|
-
* MCP server registered in its native format. Codex's is TOML in
|
|
101
|
-
* config.toml; we write that directly rather than relying on the plugin
|
|
102
|
-
* system's .mcp.json indirection (which we can't currently verify works
|
|
103
|
-
* outside the upstream plugin pipeline).
|
|
104
|
-
*/
|
|
105
|
-
export function writeMcpServerToConfig(configPath: string): void {
|
|
106
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
107
|
-
const existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
108
|
-
|
|
109
|
-
const block = `${MCP_SENTINEL_BEGIN}\n[mcp_servers.nlm-memory]\ncommand = "nlm"\nargs = ["mcp"]\n${MCP_SENTINEL_END}\n`;
|
|
110
|
-
|
|
111
|
-
const next = stripSentinelBlock(existing);
|
|
112
|
-
const sep = next.length > 0 && !next.endsWith("\n\n") ? (next.endsWith("\n") ? "\n" : "\n\n") : "";
|
|
113
|
-
writeFileSync(configPath, next + sep + block, "utf8");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function removeMcpServerFromConfig(configPath: string): boolean {
|
|
117
|
-
if (!existsSync(configPath)) return false;
|
|
118
|
-
const existing = readFileSync(configPath, "utf8");
|
|
119
|
-
const next = stripSentinelBlock(existing);
|
|
120
|
-
if (next === existing) return false;
|
|
121
|
-
writeFileSync(configPath, next, "utf8");
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Remove our sentinel-bracketed region from a config.toml string. Tolerant
|
|
127
|
-
* of an unterminated begin sentinel (treats it as a no-op rather than
|
|
128
|
-
* eating the rest of the file) so a corrupted config never amplifies.
|
|
129
|
-
*/
|
|
130
|
-
function stripSentinelBlock(content: string): string {
|
|
131
|
-
const beginIdx = content.indexOf(MCP_SENTINEL_BEGIN);
|
|
132
|
-
if (beginIdx < 0) return content;
|
|
133
|
-
const endMarker = MCP_SENTINEL_END;
|
|
134
|
-
const endIdx = content.indexOf(endMarker, beginIdx + MCP_SENTINEL_BEGIN.length);
|
|
135
|
-
if (endIdx < 0) return content; // unterminated — refuse to mutate
|
|
136
|
-
let cutEnd = endIdx + endMarker.length;
|
|
137
|
-
if (content[cutEnd] === "\n") cutEnd += 1;
|
|
138
|
-
let cutStart = beginIdx;
|
|
139
|
-
// Also eat the single leading newline that connected this block to the
|
|
140
|
-
// prior section, so repeated connect/disconnect cycles don't accrete blanks.
|
|
141
|
-
if (cutStart > 0 && content[cutStart - 1] === "\n") cutStart -= 1;
|
|
142
|
-
return content.slice(0, cutStart) + content.slice(cutEnd);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
interface CodexHookEntry {
|
|
146
|
-
readonly type: string;
|
|
147
|
-
readonly command: string;
|
|
148
|
-
readonly statusMessage?: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
interface CodexHookGroup {
|
|
152
|
-
readonly matcher?: string;
|
|
153
|
-
readonly hooks: CodexHookEntry[];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface CodexHooksFile {
|
|
157
|
-
hooks?: Record<string, CodexHookGroup[]>;
|
|
158
|
-
[k: string]: unknown;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function readHooksFile(path: string): CodexHooksFile {
|
|
162
|
-
if (!existsSync(path)) return {};
|
|
163
|
-
try {
|
|
164
|
-
return JSON.parse(readFileSync(path, "utf8")) as CodexHooksFile;
|
|
165
|
-
} catch {
|
|
166
|
-
// Treat a malformed hooks.json as empty rather than silently
|
|
167
|
-
// overwriting the user's intent. The legacy writer below merges
|
|
168
|
-
// entries — if the file is broken we'd rather error than clobber.
|
|
169
|
-
throw new Error(`~/.codex/hooks.json is not valid JSON: ${path}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function writeHooksFile(path: string, content: CodexHooksFile): void {
|
|
174
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
175
|
-
writeFileSync(path, JSON.stringify(content, null, 2) + "\n", "utf8");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Append our two hook entries into ~/.codex/hooks.json without touching any
|
|
180
|
-
* pre-existing entries. Idempotent: a second call replaces our entries
|
|
181
|
-
* rather than duplicating them (matched by LEGACY_HOOK_MARKER substring).
|
|
182
|
-
*/
|
|
183
|
-
export function writeLegacyHooks(
|
|
184
|
-
pluginScriptsDir: string,
|
|
185
|
-
hooksPath: string,
|
|
186
|
-
): void {
|
|
187
|
-
const file = readHooksFile(hooksPath);
|
|
188
|
-
const hooks = (file.hooks ??= {});
|
|
189
|
-
|
|
190
|
-
const ourEntries: Record<string, CodexHookGroup> = {
|
|
191
|
-
UserPromptSubmit: {
|
|
192
|
-
hooks: [
|
|
193
|
-
{
|
|
194
|
-
type: "command",
|
|
195
|
-
command: `node "${join(pluginScriptsDir, "prompt-recall-hook.mjs")}"`,
|
|
196
|
-
statusMessage: "nlm-memory: recalling prior sessions",
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
Stop: {
|
|
201
|
-
hooks: [
|
|
202
|
-
{
|
|
203
|
-
type: "command",
|
|
204
|
-
command: `node "${join(pluginScriptsDir, "stop-hook.mjs")}"`,
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
for (const [event, ourGroup] of Object.entries(ourEntries)) {
|
|
211
|
-
const existing = hooks[event] ?? [];
|
|
212
|
-
const kept = existing.filter(
|
|
213
|
-
(group) =>
|
|
214
|
-
!group.hooks.some((h) => h.command.includes(LEGACY_HOOK_MARKER)),
|
|
215
|
-
);
|
|
216
|
-
kept.push(ourGroup);
|
|
217
|
-
hooks[event] = kept;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
writeHooksFile(hooksPath, file);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function removeLegacyHooks(hooksPath: string): boolean {
|
|
224
|
-
if (!existsSync(hooksPath)) return false;
|
|
225
|
-
const file = readHooksFile(hooksPath);
|
|
226
|
-
const hooks = file.hooks;
|
|
227
|
-
if (!hooks) return false;
|
|
228
|
-
|
|
229
|
-
let mutated = false;
|
|
230
|
-
for (const [event, groups] of Object.entries(hooks)) {
|
|
231
|
-
const kept = groups.filter(
|
|
232
|
-
(group) =>
|
|
233
|
-
!group.hooks.some((h) => h.command.includes(LEGACY_HOOK_MARKER)),
|
|
234
|
-
);
|
|
235
|
-
if (kept.length !== groups.length) mutated = true;
|
|
236
|
-
if (kept.length === 0) delete hooks[event];
|
|
237
|
-
else hooks[event] = kept;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (mutated) writeHooksFile(hooksPath, file);
|
|
241
|
-
return mutated;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export interface ConnectReport {
|
|
245
|
-
readonly source: string;
|
|
246
|
-
readonly marketplaceName: string;
|
|
247
|
-
readonly pluginName: string;
|
|
248
|
-
readonly marketplaceAdd: CodexCommandResult | null;
|
|
249
|
-
readonly pluginAdd: CodexCommandResult | null;
|
|
250
|
-
readonly legacyHooksWritten: string | null;
|
|
251
|
-
readonly mcpServerWritten: string | null;
|
|
252
|
-
readonly dryRun: boolean;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function connectCodex(
|
|
256
|
-
opts: ConnectOptions,
|
|
257
|
-
pluginScriptsDir: string,
|
|
258
|
-
): ConnectReport {
|
|
259
|
-
const source = opts.source ?? DEFAULT_SOURCE;
|
|
260
|
-
const marketplaceName = MARKETPLACE_NAME;
|
|
261
|
-
const pluginName = PLUGIN_NAME;
|
|
262
|
-
|
|
263
|
-
if (opts.dryRun) {
|
|
264
|
-
return {
|
|
265
|
-
source,
|
|
266
|
-
marketplaceName,
|
|
267
|
-
pluginName,
|
|
268
|
-
marketplaceAdd: null,
|
|
269
|
-
pluginAdd: null,
|
|
270
|
-
legacyHooksWritten: opts.withHooks ? codexHooksPath() : null,
|
|
271
|
-
mcpServerWritten: codexConfigPath(),
|
|
272
|
-
dryRun: true,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Marketplace add is idempotent at the codex layer; a re-add of the same
|
|
277
|
-
// source no-ops or refreshes the snapshot depending on the binary.
|
|
278
|
-
const marketplaceAdd = runCodex(["plugin", "marketplace", "add", source]);
|
|
279
|
-
// plugin add is the action that triggers the trust-prompt path on first
|
|
280
|
-
// run. We let codex's exit code propagate to the caller.
|
|
281
|
-
const pluginAdd = runCodex([
|
|
282
|
-
"plugin",
|
|
283
|
-
"add",
|
|
284
|
-
`${pluginName}@${marketplaceName}`,
|
|
285
|
-
]);
|
|
286
|
-
|
|
287
|
-
let legacyHooksWritten: string | null = null;
|
|
288
|
-
if (opts.withHooks) {
|
|
289
|
-
const hooksPath = codexHooksPath();
|
|
290
|
-
writeLegacyHooks(pluginScriptsDir, hooksPath);
|
|
291
|
-
legacyHooksWritten = hooksPath;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// MCP wiring is always written directly to config.toml — it's the
|
|
295
|
-
// universal infrastructure that should work whether or not the plugin
|
|
296
|
-
// system honors the bundled .mcp.json indirection.
|
|
297
|
-
const configPath = codexConfigPath();
|
|
298
|
-
writeMcpServerToConfig(configPath);
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
source,
|
|
302
|
-
marketplaceName,
|
|
303
|
-
pluginName,
|
|
304
|
-
marketplaceAdd,
|
|
305
|
-
pluginAdd,
|
|
306
|
-
legacyHooksWritten,
|
|
307
|
-
mcpServerWritten: configPath,
|
|
308
|
-
dryRun: false,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export interface DisconnectReport {
|
|
313
|
-
readonly marketplaceName: string;
|
|
314
|
-
readonly pluginName: string;
|
|
315
|
-
readonly pluginRemove: CodexCommandResult | null;
|
|
316
|
-
readonly marketplaceRemove: CodexCommandResult | null;
|
|
317
|
-
readonly legacyHooksRemoved: boolean;
|
|
318
|
-
readonly mcpServerRemoved: boolean;
|
|
319
|
-
readonly dryRun: boolean;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function disconnectCodex(opts: DisconnectOptions): DisconnectReport {
|
|
323
|
-
if (opts.dryRun) {
|
|
324
|
-
return {
|
|
325
|
-
marketplaceName: MARKETPLACE_NAME,
|
|
326
|
-
pluginName: PLUGIN_NAME,
|
|
327
|
-
pluginRemove: null,
|
|
328
|
-
marketplaceRemove: null,
|
|
329
|
-
legacyHooksRemoved: opts.withHooks ?? false,
|
|
330
|
-
mcpServerRemoved: true,
|
|
331
|
-
dryRun: true,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const pluginRemove = runCodex([
|
|
336
|
-
"plugin",
|
|
337
|
-
"remove",
|
|
338
|
-
`${PLUGIN_NAME}@${MARKETPLACE_NAME}`,
|
|
339
|
-
]);
|
|
340
|
-
const marketplaceRemove = runCodex([
|
|
341
|
-
"plugin",
|
|
342
|
-
"marketplace",
|
|
343
|
-
"remove",
|
|
344
|
-
MARKETPLACE_NAME,
|
|
345
|
-
]);
|
|
346
|
-
|
|
347
|
-
let legacyHooksRemoved = false;
|
|
348
|
-
if (opts.withHooks) {
|
|
349
|
-
legacyHooksRemoved = removeLegacyHooks(codexHooksPath());
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const mcpServerRemoved = removeMcpServerFromConfig(codexConfigPath());
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
marketplaceName: MARKETPLACE_NAME,
|
|
356
|
-
pluginName: PLUGIN_NAME,
|
|
357
|
-
pluginRemove,
|
|
358
|
-
marketplaceRemove,
|
|
359
|
-
legacyHooksRemoved,
|
|
360
|
-
mcpServerRemoved,
|
|
361
|
-
dryRun: false,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function pluginScriptsDir(repoRoot: string): string {
|
|
366
|
-
return resolve(repoRoot, "plugin", "scripts");
|
|
367
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `nlm connect hermes-agent` / `nlm disconnect hermes-agent` — installs the
|
|
3
|
-
* nlm-memory plugin into NousResearch Hermes Agent's plugin directory and
|
|
4
|
-
* optionally enables it via the `hermes` binary.
|
|
5
|
-
*
|
|
6
|
-
* The plugin lives in plugin-hermes-agent/ at the repo root. `connect`
|
|
7
|
-
* copies it to ~/.hermes/plugins/nlm-memory/ (flat layout, one category
|
|
8
|
-
* level max per Hermes plugin discovery rules). `disconnect` removes that
|
|
9
|
-
* directory.
|
|
10
|
-
*
|
|
11
|
-
* MCP server wiring (the [mcp_servers.nlm-memory] block in
|
|
12
|
-
* ~/.hermes/config.yaml) is handled separately by `nlm connect hermes`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
|
-
import { spawnSync } from "node:child_process";
|
|
19
|
-
|
|
20
|
-
export interface ConnectHermesAgentOptions {
|
|
21
|
-
readonly pluginSrcDir: string;
|
|
22
|
-
readonly dryRun?: boolean;
|
|
23
|
-
readonly enableViaCliIfAvailable?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ConnectHermesAgentReport {
|
|
27
|
-
readonly destDir: string;
|
|
28
|
-
readonly copied: boolean;
|
|
29
|
-
readonly alreadyPresent: boolean;
|
|
30
|
-
readonly enabledViaCli: boolean;
|
|
31
|
-
readonly dryRun: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface DisconnectHermesAgentReport {
|
|
35
|
-
readonly destDir: string;
|
|
36
|
-
readonly removed: boolean;
|
|
37
|
-
readonly dryRun: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function hermesAgentPluginDir(): string {
|
|
41
|
-
return process.env["NLM_HERMES_PLUGIN_DIR"] ?? join(homedir(), ".hermes", "plugins", "nlm-memory");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function connectHermesAgent(opts: ConnectHermesAgentOptions): ConnectHermesAgentReport {
|
|
45
|
-
const destDir = hermesAgentPluginDir();
|
|
46
|
-
const alreadyPresent = existsSync(destDir);
|
|
47
|
-
|
|
48
|
-
if (!opts.dryRun) {
|
|
49
|
-
mkdirSync(dirname(destDir), { recursive: true });
|
|
50
|
-
cpSync(opts.pluginSrcDir, destDir, { recursive: true });
|
|
51
|
-
|
|
52
|
-
let enabledViaCli = false;
|
|
53
|
-
if (opts.enableViaCliIfAvailable !== false) {
|
|
54
|
-
const result = spawnSync("hermes", ["plugins", "enable", "nlm-memory"], {
|
|
55
|
-
encoding: "utf8",
|
|
56
|
-
timeout: 10_000,
|
|
57
|
-
});
|
|
58
|
-
enabledViaCli = result.status === 0;
|
|
59
|
-
}
|
|
60
|
-
return { destDir, copied: true, alreadyPresent, enabledViaCli, dryRun: false };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { destDir, copied: false, alreadyPresent, enabledViaCli: false, dryRun: true };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function disconnectHermesAgent(opts?: { dryRun?: boolean }): DisconnectHermesAgentReport {
|
|
67
|
-
const destDir = hermesAgentPluginDir();
|
|
68
|
-
const present = existsSync(destDir);
|
|
69
|
-
|
|
70
|
-
if (!opts?.dryRun && present) {
|
|
71
|
-
rmSync(destDir, { recursive: true, force: true });
|
|
72
|
-
return { destDir, removed: true, dryRun: false };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return { destDir, removed: false, dryRun: opts?.dryRun ?? false };
|
|
76
|
-
}
|
package/src/install/hermes.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `nlm connect hermes` / `nlm disconnect hermes` — writes the nlm-memory
|
|
3
|
-
* MCP server entry into ~/.hermes/config.yaml.
|
|
4
|
-
*
|
|
5
|
-
* Uses yaml's Document API (parseDocument / doc.setIn / doc.toString) to
|
|
6
|
-
* preserve any comments the user has written in their config file. Round-
|
|
7
|
-
* tripping through parse+stringify would silently destroy comments.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
import { dirname, join } from "node:path";
|
|
13
|
-
import { Document as YamlDocument, parseDocument as parseYamlDocument } from "yaml";
|
|
14
|
-
|
|
15
|
-
export interface ConnectHermesOptions {
|
|
16
|
-
readonly nlmBinPath: string;
|
|
17
|
-
readonly nodeExecPath: string;
|
|
18
|
-
readonly dryRun?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ConnectHermesReport {
|
|
22
|
-
readonly configPath: string;
|
|
23
|
-
readonly alreadyPresent: boolean;
|
|
24
|
-
readonly written: boolean;
|
|
25
|
-
readonly dryRun: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface DisconnectHermesReport {
|
|
29
|
-
readonly configPath: string;
|
|
30
|
-
readonly removed: boolean;
|
|
31
|
-
readonly dryRun: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function hermesConfigPath(): string {
|
|
35
|
-
return process.env["NLM_HERMES_CONFIG"] ?? join(homedir(), ".hermes", "config.yaml");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function readDocument(path: string): YamlDocument {
|
|
39
|
-
if (!existsSync(path)) return new YamlDocument();
|
|
40
|
-
try {
|
|
41
|
-
return parseYamlDocument(readFileSync(path, "utf8"));
|
|
42
|
-
} catch {
|
|
43
|
-
throw new Error(`${path} is not valid YAML. Fix or remove it, then re-run \`nlm connect hermes\`.`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function connectHermes(opts: ConnectHermesOptions): ConnectHermesReport {
|
|
48
|
-
const configPath = hermesConfigPath();
|
|
49
|
-
const doc = readDocument(configPath);
|
|
50
|
-
const alreadyPresent = doc.getIn(["mcp_servers", "nlm-memory"]) !== undefined;
|
|
51
|
-
|
|
52
|
-
if (!opts.dryRun) {
|
|
53
|
-
doc.setIn(["mcp_servers", "nlm-memory"], {
|
|
54
|
-
command: opts.nodeExecPath,
|
|
55
|
-
args: [opts.nlmBinPath, "mcp"],
|
|
56
|
-
});
|
|
57
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
58
|
-
writeFileSync(configPath, doc.toString(), "utf8");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { configPath, alreadyPresent, written: !opts.dryRun, dryRun: opts.dryRun ?? false };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function disconnectHermes(opts?: { dryRun?: boolean }): DisconnectHermesReport {
|
|
65
|
-
const configPath = hermesConfigPath();
|
|
66
|
-
const doc = readDocument(configPath);
|
|
67
|
-
|
|
68
|
-
if (doc.getIn(["mcp_servers", "nlm-memory"]) === undefined) {
|
|
69
|
-
return { configPath, removed: false, dryRun: opts?.dryRun ?? false };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!opts?.dryRun) {
|
|
73
|
-
doc.deleteIn(["mcp_servers", "nlm-memory"]);
|
|
74
|
-
writeFileSync(configPath, doc.toString(), "utf8");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { configPath, removed: true, dryRun: opts?.dryRun ?? false };
|
|
78
|
-
}
|