@remnic/core 9.7.6 → 9.7.8
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/dist/access-boundary.d.ts +2 -0
- package/dist/access-boundary.js +1 -1
- package/dist/access-cli.js +7 -7
- package/dist/access-http.js +5 -5
- package/dist/access-mcp.d.ts +2 -0
- package/dist/access-mcp.js +4 -4
- package/dist/access-operations-batch.js +2 -2
- package/dist/access-operations.d.ts +3 -3
- package/dist/access-operations.js +3 -3
- package/dist/access-schema.d.ts +64 -64
- package/dist/{chunk-TBQ4CFIP.js → chunk-57INMZ6F.js} +1 -1
- package/dist/chunk-57INMZ6F.js.map +1 -0
- package/dist/{chunk-BFCQPJ5B.js → chunk-CHVU4RE5.js} +24 -6
- package/dist/chunk-CHVU4RE5.js.map +1 -0
- package/dist/{chunk-3CRHW42H.js → chunk-FYKIEOG6.js} +2 -2
- package/dist/{chunk-ESE55PZJ.js → chunk-NMMKRVUF.js} +10 -6
- package/dist/chunk-NMMKRVUF.js.map +1 -0
- package/dist/{chunk-3UPBVNBX.js → chunk-Q6JPMCPO.js} +3 -3
- package/dist/{chunk-P2UA6XQG.js → chunk-UHGHTOR5.js} +5 -5
- package/dist/chunk-UHGHTOR5.js.map +1 -0
- package/dist/{chunk-H67QFYUK.js → chunk-UJBESW6X.js} +918 -146
- package/dist/chunk-UJBESW6X.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/connectors/index.d.ts +7 -0
- package/dist/connectors/index.js +1 -1
- package/dist/index.js +7 -7
- package/dist/orchestrator.js +7 -7
- package/dist/schemas.d.ts +74 -74
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -0
- package/src/access-http.ts +15 -2
- package/src/access-mcp-cancellation.test.ts +405 -0
- package/src/access-mcp.ts +25 -1
- package/src/access-operations-batch.ts +3 -3
- package/src/connectors/hermes-shim.ts +523 -0
- package/src/connectors/index.ts +769 -15
- package/dist/chunk-BFCQPJ5B.js.map +0 -1
- package/dist/chunk-ESE55PZJ.js.map +0 -1
- package/dist/chunk-H67QFYUK.js.map +0 -1
- package/dist/chunk-P2UA6XQG.js.map +0 -1
- package/dist/chunk-TBQ4CFIP.js.map +0 -1
- /package/dist/{chunk-3CRHW42H.js.map → chunk-FYKIEOG6.js.map} +0 -0
- /package/dist/{chunk-3UPBVNBX.js.map → chunk-Q6JPMCPO.js.map} +0 -0
|
@@ -32,15 +32,310 @@ import {
|
|
|
32
32
|
} from "./chunk-37CTGERM.js";
|
|
33
33
|
|
|
34
34
|
// src/connectors/index.ts
|
|
35
|
-
import
|
|
36
|
-
import
|
|
35
|
+
import fs3 from "fs";
|
|
36
|
+
import path3 from "path";
|
|
37
37
|
import os from "os";
|
|
38
38
|
import { createRequire } from "module";
|
|
39
39
|
import { fileURLToPath } from "url";
|
|
40
40
|
|
|
41
|
-
// src/connectors/
|
|
41
|
+
// src/connectors/hermes-shim.ts
|
|
42
42
|
import fs from "fs";
|
|
43
43
|
import path from "path";
|
|
44
|
+
var HERMES_SHIM_MARKER = "generated by `remnic connectors install hermes`";
|
|
45
|
+
function resolveHermesRoot() {
|
|
46
|
+
const envHome = readEnvVar("HERMES_HOME");
|
|
47
|
+
if (typeof envHome === "string" && envHome.trim().length > 0) {
|
|
48
|
+
const expanded = path.resolve(expandTildePath(envHome.trim()));
|
|
49
|
+
try {
|
|
50
|
+
const stat = fs.lstatSync(expanded);
|
|
51
|
+
if (stat.isSymbolicLink()) {
|
|
52
|
+
throw new Error(`HERMES_HOME must not be a symbolic link: ${expanded}`);
|
|
53
|
+
}
|
|
54
|
+
if (!stat.isDirectory()) {
|
|
55
|
+
throw new Error(`HERMES_HOME is not a directory: ${expanded}`);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (err.code !== "ENOENT") {
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return expanded;
|
|
63
|
+
}
|
|
64
|
+
if (process.platform === "win32") {
|
|
65
|
+
const localAppData = (readEnvVar("LOCALAPPDATA") ?? "").trim();
|
|
66
|
+
const base = localAppData.length > 0 ? path.resolve(localAppData) : path.join(resolveHomeDir(), "AppData", "Local");
|
|
67
|
+
return resolveDefaultRoot(path.join(base, "hermes"));
|
|
68
|
+
}
|
|
69
|
+
return resolveDefaultRoot(path.resolve(resolveHomeDir(), ".hermes"));
|
|
70
|
+
}
|
|
71
|
+
function resolveDefaultRoot(candidate) {
|
|
72
|
+
try {
|
|
73
|
+
return fs.realpathSync.native(candidate);
|
|
74
|
+
} catch {
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function hermesShimPath() {
|
|
79
|
+
return path.join(resolveHermesRoot(), "plugins", "remnic", "__init__.py");
|
|
80
|
+
}
|
|
81
|
+
function writePlainFileAtomicSync(filePath, data) {
|
|
82
|
+
const dir = path.dirname(filePath);
|
|
83
|
+
const base = path.basename(filePath);
|
|
84
|
+
const tmpPath = path.join(dir, `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
85
|
+
let wroteTemp = false;
|
|
86
|
+
try {
|
|
87
|
+
fs.writeFileSync(tmpPath, data, { mode: 420, flag: "wx" });
|
|
88
|
+
wroteTemp = true;
|
|
89
|
+
fs.renameSync(tmpPath, filePath);
|
|
90
|
+
try {
|
|
91
|
+
fs.chmodSync(filePath, 420);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (wroteTemp) {
|
|
96
|
+
try {
|
|
97
|
+
fs.unlinkSync(tmpPath);
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function assertShimComponentsNotSymlinked(shimPath) {
|
|
105
|
+
const remnicDir = path.dirname(shimPath);
|
|
106
|
+
const pluginsDir = path.dirname(remnicDir);
|
|
107
|
+
for (const component of [pluginsDir, remnicDir, shimPath]) {
|
|
108
|
+
let isLink = false;
|
|
109
|
+
try {
|
|
110
|
+
isLink = fs.lstatSync(component).isSymbolicLink();
|
|
111
|
+
} catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (isLink) {
|
|
115
|
+
throw new Error(`refusing to operate through a symbolic link: ${component}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function materializeHermesShim(shimPath) {
|
|
120
|
+
assertShimComponentsNotSymlinked(shimPath);
|
|
121
|
+
if (fs.existsSync(shimPath)) {
|
|
122
|
+
let existing;
|
|
123
|
+
try {
|
|
124
|
+
existing = fs.readFileSync(shimPath, "utf8");
|
|
125
|
+
} catch (readErr) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`cannot read existing shim at ${shimPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (!existing.includes(HERMES_SHIM_MARKER)) {
|
|
131
|
+
return {
|
|
132
|
+
note: `Hermes plugin shim already exists and was NOT generated by Remnic \u2014 left untouched: ${shimPath}. If the provider is not discovered, ensure that file imports remnic_hermes.register AND contains the literal text register_memory_provider (or MemoryProvider) \u2014 Hermes' discovery text-scan skips the directory without it.`,
|
|
133
|
+
wrote: false
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
fs.mkdirSync(path.dirname(shimPath), { recursive: true });
|
|
138
|
+
const content = [
|
|
139
|
+
`"""Remnic memory provider shim for Hermes Agent (${HERMES_SHIM_MARKER}).`,
|
|
140
|
+
"",
|
|
141
|
+
"Hermes memory-provider discovery calls register(collector);",
|
|
142
|
+
"collector.register_memory_provider() receives the provider.",
|
|
143
|
+
`"""`,
|
|
144
|
+
"",
|
|
145
|
+
"from remnic_hermes import register # noqa: F401 (register() loads Hermes config itself)",
|
|
146
|
+
""
|
|
147
|
+
].join("\n");
|
|
148
|
+
writePlainFileAtomicSync(shimPath, content);
|
|
149
|
+
return { note: `Materialized Hermes plugin shim: ${shimPath}`, wrote: true };
|
|
150
|
+
}
|
|
151
|
+
function reconcileHermesShim(priorPersistedPath) {
|
|
152
|
+
const notes = [];
|
|
153
|
+
let target = null;
|
|
154
|
+
let materialized = false;
|
|
155
|
+
let createdNew = false;
|
|
156
|
+
try {
|
|
157
|
+
target = hermesShimPath();
|
|
158
|
+
const existedBefore = fs.existsSync(target);
|
|
159
|
+
const result = materializeHermesShim(target);
|
|
160
|
+
notes.push(result.note);
|
|
161
|
+
materialized = result.wrote;
|
|
162
|
+
createdNew = result.wrote && !existedBefore;
|
|
163
|
+
} catch (shimErr) {
|
|
164
|
+
const shimPathHint = target ?? "<hermesRoot>/plugins/remnic/__init__.py";
|
|
165
|
+
notes.push(
|
|
166
|
+
`Note: could not materialize the Hermes plugin shim (${shimErr instanceof Error ? shimErr.message : String(shimErr)}). Create ${shimPathHint} manually with exactly these two lines:
|
|
167
|
+
"""Remnic memory provider shim. Calls collector.register_memory_provider()."""
|
|
168
|
+
from remnic_hermes import register`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!materialized || target === null) {
|
|
172
|
+
return {
|
|
173
|
+
notes,
|
|
174
|
+
persistPath: priorPersistedPath,
|
|
175
|
+
materializedAt: null,
|
|
176
|
+
createdNew: false,
|
|
177
|
+
priorCleanedAt: null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const confirmedTarget = target;
|
|
181
|
+
let priorCleanedAt = null;
|
|
182
|
+
if (priorPersistedPath !== null && !sameShimTarget(priorPersistedPath, confirmedTarget)) {
|
|
183
|
+
try {
|
|
184
|
+
const stale = removeHermesShim([priorPersistedPath]);
|
|
185
|
+
if (stale.notes.length > 0) {
|
|
186
|
+
notes.push(`Cleaned prior-install shim: ${stale.notes.join("; ")}`);
|
|
187
|
+
}
|
|
188
|
+
priorCleanedAt = stale.removedPaths.includes(priorPersistedPath) ? priorPersistedPath : null;
|
|
189
|
+
} catch {
|
|
190
|
+
notes.push(
|
|
191
|
+
`Note: could not clean the prior-install Hermes plugin shim at ${priorPersistedPath} \u2014 remove it manually if present.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { notes, persistPath: target, materializedAt: target, createdNew, priorCleanedAt };
|
|
196
|
+
}
|
|
197
|
+
function sameShimTarget(leftPath, rightPath) {
|
|
198
|
+
return resolveShimTarget(leftPath) === resolveShimTarget(rightPath);
|
|
199
|
+
}
|
|
200
|
+
function resolveShimTarget(candidate) {
|
|
201
|
+
try {
|
|
202
|
+
return fs.realpathSync.native(candidate);
|
|
203
|
+
} catch {
|
|
204
|
+
return path.resolve(candidate);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function removeHermesShim(candidatePaths) {
|
|
208
|
+
const notes = [];
|
|
209
|
+
const removedPaths = [];
|
|
210
|
+
for (const shimPath of new Set(candidatePaths)) {
|
|
211
|
+
if (!isPlausibleHermesShimPath(shimPath)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
assertShimComponentsNotSymlinked(shimPath);
|
|
216
|
+
} catch {
|
|
217
|
+
notes.push(`Hermes plugin shim left untouched (symlinked path component): ${shimPath}`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!fs.existsSync(shimPath)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
let content;
|
|
224
|
+
try {
|
|
225
|
+
content = fs.readFileSync(shimPath, "utf8");
|
|
226
|
+
} catch {
|
|
227
|
+
notes.push(`Hermes plugin shim left untouched (unreadable): ${shimPath}`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!content.includes(HERMES_SHIM_MARKER)) {
|
|
231
|
+
notes.push(`Hermes plugin shim left untouched (not Remnic-generated): ${shimPath}`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
fs.unlinkSync(shimPath);
|
|
236
|
+
} catch (unlinkErr) {
|
|
237
|
+
notes.push(
|
|
238
|
+
`Hermes plugin shim could not be removed (${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}): ${shimPath} \u2014 remove it manually or re-run after fixing permissions.`
|
|
239
|
+
);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
removedPaths.push(shimPath);
|
|
243
|
+
try {
|
|
244
|
+
fs.rmdirSync(path.dirname(shimPath));
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
notes.push(`Removed Hermes plugin shim: ${shimPath}`);
|
|
248
|
+
}
|
|
249
|
+
return { removedPaths, notes };
|
|
250
|
+
}
|
|
251
|
+
function isPlausibleHermesShimPath(candidate) {
|
|
252
|
+
if (typeof candidate !== "string" || candidate.length === 0 || !path.isAbsolute(candidate)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const dir = path.dirname(candidate);
|
|
256
|
+
return path.basename(candidate) === "__init__.py" && path.basename(dir) === "remnic" && path.basename(path.dirname(dir)) === "plugins";
|
|
257
|
+
}
|
|
258
|
+
function isPlausibleHermesConfigPath(candidate) {
|
|
259
|
+
if (typeof candidate !== "string" || candidate.length === 0 || !path.isAbsolute(candidate)) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (path.basename(candidate) !== "config.yaml") {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const dir = path.dirname(candidate);
|
|
266
|
+
const dirName = path.basename(dir).toLowerCase();
|
|
267
|
+
if (dirName === ".hermes" || dirName === "hermes") {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
if (path.basename(path.dirname(dir)) === "profiles") {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
if (["plugins", "profiles"].some((sibling) => {
|
|
274
|
+
try {
|
|
275
|
+
return fs.statSync(path.join(dir, sibling)).isDirectory();
|
|
276
|
+
} catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
})) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const stat = fs.statSync(candidate);
|
|
284
|
+
if (!stat.isFile() || stat.size > 1024 * 1024) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return /^remnic:/m.test(fs.readFileSync(candidate, "utf8"));
|
|
288
|
+
} catch {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function survivingMarkerShims(candidates, excludeTarget) {
|
|
293
|
+
const survivors = [];
|
|
294
|
+
for (const candidate of new Set(candidates)) {
|
|
295
|
+
if (!isPlausibleHermesShimPath(candidate)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (excludeTarget !== null && sameShimTarget(candidate, excludeTarget)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (survivors.some((kept) => sameShimTarget(kept, candidate))) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
let carriesMarker = false;
|
|
305
|
+
try {
|
|
306
|
+
carriesMarker = fs.readFileSync(candidate, "utf8").includes(HERMES_SHIM_MARKER);
|
|
307
|
+
} catch {
|
|
308
|
+
carriesMarker = false;
|
|
309
|
+
}
|
|
310
|
+
if (carriesMarker) {
|
|
311
|
+
survivors.push(candidate);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return survivors;
|
|
315
|
+
}
|
|
316
|
+
function assertConfigComponentsNotSymlinked(cfgPath) {
|
|
317
|
+
const dir = path.dirname(cfgPath);
|
|
318
|
+
const components = [cfgPath, dir];
|
|
319
|
+
const grandparent = path.dirname(dir);
|
|
320
|
+
if (path.basename(grandparent) === "profiles") {
|
|
321
|
+
components.push(grandparent);
|
|
322
|
+
}
|
|
323
|
+
for (const component of components) {
|
|
324
|
+
let isLink = false;
|
|
325
|
+
try {
|
|
326
|
+
isLink = fs.lstatSync(component).isSymbolicLink();
|
|
327
|
+
} catch {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (isLink) {
|
|
331
|
+
throw new Error(`refusing to operate through a symbolic link: ${component}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/connectors/codex-marketplace.ts
|
|
337
|
+
import fs2 from "fs";
|
|
338
|
+
import path2 from "path";
|
|
44
339
|
var MARKETPLACE_SCHEMA_VERSION = 1;
|
|
45
340
|
var MARKETPLACE_MANIFEST_FILENAME = "marketplace.json";
|
|
46
341
|
var VALID_INSTALL_TYPES = /* @__PURE__ */ new Set(["github", "git", "local", "url"]);
|
|
@@ -142,12 +437,12 @@ async function writeMarketplaceManifest(outputDir, manifest) {
|
|
|
142
437
|
`Refusing to write invalid manifest: ${validation.errors.join("; ")}`
|
|
143
438
|
);
|
|
144
439
|
}
|
|
145
|
-
|
|
146
|
-
const destPath =
|
|
440
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
441
|
+
const destPath = path2.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);
|
|
147
442
|
const tmpPath = `${destPath}.tmp.${process.pid}`;
|
|
148
443
|
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
149
|
-
|
|
150
|
-
|
|
444
|
+
fs2.writeFileSync(tmpPath, content);
|
|
445
|
+
fs2.renameSync(tmpPath, destPath);
|
|
151
446
|
}
|
|
152
447
|
async function installFromMarketplace(source, sourceType, config, logger) {
|
|
153
448
|
const _log = logger ?? {
|
|
@@ -207,12 +502,12 @@ async function resolveManifest(source, sourceType, logger) {
|
|
|
207
502
|
}
|
|
208
503
|
}
|
|
209
504
|
function resolveLocal(dirPath, logger) {
|
|
210
|
-
const manifestPath =
|
|
211
|
-
if (!
|
|
505
|
+
const manifestPath = path2.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);
|
|
506
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
212
507
|
throw new Error(`marketplace.json not found at ${manifestPath}`);
|
|
213
508
|
}
|
|
214
509
|
logger.debug?.(`reading local marketplace manifest: ${manifestPath}`);
|
|
215
|
-
const raw =
|
|
510
|
+
const raw = fs2.readFileSync(manifestPath, "utf-8");
|
|
216
511
|
let parsed;
|
|
217
512
|
try {
|
|
218
513
|
parsed = JSON.parse(raw);
|
|
@@ -264,15 +559,15 @@ async function resolveGit(gitUrl, logger) {
|
|
|
264
559
|
}
|
|
265
560
|
function readPackageVersion() {
|
|
266
561
|
const candidates = [
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
562
|
+
path2.resolve(import.meta.dirname ?? ".", "../../../.."),
|
|
563
|
+
path2.resolve(import.meta.dirname ?? ".", "../../../../.."),
|
|
564
|
+
path2.resolve(import.meta.dirname ?? ".", "..")
|
|
270
565
|
];
|
|
271
566
|
for (const candidate of candidates) {
|
|
272
|
-
const pkgPath =
|
|
567
|
+
const pkgPath = path2.join(candidate, "package.json");
|
|
273
568
|
try {
|
|
274
|
-
if (!
|
|
275
|
-
const raw =
|
|
569
|
+
if (!fs2.existsSync(pkgPath)) continue;
|
|
570
|
+
const raw = fs2.readFileSync(pkgPath, "utf-8");
|
|
276
571
|
const parsed = JSON.parse(raw);
|
|
277
572
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.version === "string") {
|
|
278
573
|
return parsed.version;
|
|
@@ -642,7 +937,7 @@ function isConnectorManifest(value) {
|
|
|
642
937
|
}
|
|
643
938
|
function loadRegistry() {
|
|
644
939
|
const regPath = getRegistryPath();
|
|
645
|
-
if (!
|
|
940
|
+
if (!fs3.existsSync(regPath)) {
|
|
646
941
|
const registry = {
|
|
647
942
|
connectors: BUILTIN_CONNECTORS,
|
|
648
943
|
registryPath: regPath
|
|
@@ -651,7 +946,7 @@ function loadRegistry() {
|
|
|
651
946
|
return registry;
|
|
652
947
|
}
|
|
653
948
|
try {
|
|
654
|
-
const raw =
|
|
949
|
+
const raw = fs3.readFileSync(regPath, "utf8");
|
|
655
950
|
const parsed = JSON.parse(raw);
|
|
656
951
|
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.connectors)) {
|
|
657
952
|
throw new Error("invalid registry schema");
|
|
@@ -674,19 +969,19 @@ function loadRegistry() {
|
|
|
674
969
|
}
|
|
675
970
|
function saveRegistry(registry) {
|
|
676
971
|
const regPath = registry.registryPath;
|
|
677
|
-
|
|
678
|
-
|
|
972
|
+
fs3.mkdirSync(path3.dirname(regPath), { recursive: true });
|
|
973
|
+
fs3.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
|
|
679
974
|
}
|
|
680
975
|
function listConnectors() {
|
|
681
976
|
const registry = loadRegistry();
|
|
682
977
|
const connectorsDir = getConnectorsDir();
|
|
683
978
|
const installedIds = /* @__PURE__ */ new Set();
|
|
684
|
-
if (
|
|
685
|
-
for (const entry of
|
|
979
|
+
if (fs3.existsSync(connectorsDir)) {
|
|
980
|
+
for (const entry of fs3.readdirSync(connectorsDir)) {
|
|
686
981
|
if (entry.endsWith(".json")) {
|
|
687
982
|
try {
|
|
688
983
|
const config = JSON.parse(
|
|
689
|
-
|
|
984
|
+
fs3.readFileSync(path3.join(connectorsDir, entry), "utf8")
|
|
690
985
|
);
|
|
691
986
|
if (isValidConnectorId(config.connectorId)) {
|
|
692
987
|
installedIds.add(config.connectorId);
|
|
@@ -702,9 +997,9 @@ function listConnectors() {
|
|
|
702
997
|
}));
|
|
703
998
|
const installed = [];
|
|
704
999
|
for (const id of installedIds) {
|
|
705
|
-
const configPath =
|
|
1000
|
+
const configPath = path3.join(connectorsDir, `${id}.json`);
|
|
706
1001
|
try {
|
|
707
|
-
const raw = JSON.parse(
|
|
1002
|
+
const raw = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
708
1003
|
const { token: _redacted, ...config } = raw;
|
|
709
1004
|
installed.push({
|
|
710
1005
|
connectorId: id,
|
|
@@ -728,9 +1023,9 @@ function getConnectorToken(connectorId) {
|
|
|
728
1023
|
}
|
|
729
1024
|
}
|
|
730
1025
|
function readSavedConnectorConfig(configPath) {
|
|
731
|
-
if (!
|
|
1026
|
+
if (!fs3.existsSync(configPath)) return {};
|
|
732
1027
|
try {
|
|
733
|
-
const parsed = JSON.parse(
|
|
1028
|
+
const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
734
1029
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
735
1030
|
const {
|
|
736
1031
|
connectorId: _connectorId,
|
|
@@ -781,15 +1076,134 @@ function installConnector(options) {
|
|
|
781
1076
|
(c) => c.connectorId === options.connectorId
|
|
782
1077
|
);
|
|
783
1078
|
if (existing && !options.force) {
|
|
1079
|
+
let backfillNote = "";
|
|
1080
|
+
if (options.connectorId === "hermes") {
|
|
1081
|
+
try {
|
|
1082
|
+
const hermesJsonPath = path3.join(getConnectorsDir(), "hermes.json");
|
|
1083
|
+
let parsed = null;
|
|
1084
|
+
try {
|
|
1085
|
+
const parsedRaw = JSON.parse(fs3.readFileSync(hermesJsonPath, "utf8"));
|
|
1086
|
+
if (parsedRaw !== null && typeof parsedRaw === "object" && !Array.isArray(parsedRaw)) {
|
|
1087
|
+
parsed = parsedRaw;
|
|
1088
|
+
}
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
1091
|
+
const priorRaw = parsed?.pluginShimPath;
|
|
1092
|
+
const priorPersisted = typeof priorRaw === "string" && priorRaw.length > 0 ? priorRaw : null;
|
|
1093
|
+
const profileRaw = parsed?.profile;
|
|
1094
|
+
let currentConfigPath = null;
|
|
1095
|
+
let activeConfigHasBlock = false;
|
|
1096
|
+
let configResolutionError = null;
|
|
1097
|
+
try {
|
|
1098
|
+
currentConfigPath = hermesConfigPath(
|
|
1099
|
+
typeof profileRaw === "string" && profileRaw.length > 0 ? profileRaw : "default"
|
|
1100
|
+
);
|
|
1101
|
+
activeConfigHasBlock = fs3.existsSync(currentConfigPath) && /^remnic:/m.test(fs3.readFileSync(currentConfigPath, "utf8"));
|
|
1102
|
+
} catch (resolveErr) {
|
|
1103
|
+
configResolutionError = resolveErr instanceof Error ? resolveErr.message : String(resolveErr);
|
|
1104
|
+
activeConfigHasBlock = false;
|
|
1105
|
+
}
|
|
1106
|
+
if (!activeConfigHasBlock) {
|
|
1107
|
+
backfillNote = configResolutionError ? ` Note: could not resolve the active Hermes home (${configResolutionError}) \u2014 skipped the shim backfill. Fix the HERMES_HOME/profile configuration and re-run.` : " Note: the active Hermes home's config.yaml has no remnic: block \u2014 skipped the shim backfill so Hermes cannot discover an unconfigured provider. If Hermes was installed under a different HERMES_HOME, re-run with --force to rewrite the config (and token) and migrate the shim under the current home.";
|
|
1108
|
+
return {
|
|
1109
|
+
connectorId: options.connectorId,
|
|
1110
|
+
status: "already_installed",
|
|
1111
|
+
message: `Already installed. Use --force to reinstall.${backfillNote}`
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const outcome = reconcileHermesShim(priorPersisted);
|
|
1115
|
+
if (outcome.notes.length > 0) {
|
|
1116
|
+
backfillNote = ` ${outcome.notes.join(" ")}`;
|
|
1117
|
+
}
|
|
1118
|
+
let configProvenanceAdded = false;
|
|
1119
|
+
if (parsed !== null && currentConfigPath !== null && (typeof parsed.hermesConfigPath !== "string" || parsed.hermesConfigPath.length === 0)) {
|
|
1120
|
+
parsed.hermesConfigPath = currentConfigPath;
|
|
1121
|
+
configProvenanceAdded = true;
|
|
1122
|
+
}
|
|
1123
|
+
let shimPriorsChanged = false;
|
|
1124
|
+
const cleanedInheritedBackfillShims = [];
|
|
1125
|
+
if (parsed !== null) {
|
|
1126
|
+
const shimPriorCandidates = [];
|
|
1127
|
+
if (priorPersisted !== null) {
|
|
1128
|
+
shimPriorCandidates.push(priorPersisted);
|
|
1129
|
+
}
|
|
1130
|
+
if (Array.isArray(parsed.priorPluginShimPaths)) {
|
|
1131
|
+
for (const entry of parsed.priorPluginShimPaths) {
|
|
1132
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
1133
|
+
shimPriorCandidates.push(entry);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (outcome.materializedAt !== null && shimPriorCandidates.length > 0) {
|
|
1138
|
+
const cleanupTargets = shimPriorCandidates.filter(
|
|
1139
|
+
(candidate) => outcome.persistPath === null || !sameShimTarget(candidate, outcome.persistPath)
|
|
1140
|
+
);
|
|
1141
|
+
if (cleanupTargets.length > 0) {
|
|
1142
|
+
try {
|
|
1143
|
+
const cleaned = removeHermesShim(cleanupTargets);
|
|
1144
|
+
cleanedInheritedBackfillShims.push(...cleaned.removedPaths);
|
|
1145
|
+
} catch {
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const survivingShims = survivingMarkerShims(shimPriorCandidates, outcome.persistPath);
|
|
1150
|
+
const persistedShimPriors = Array.isArray(parsed.priorPluginShimPaths) ? parsed.priorPluginShimPaths : null;
|
|
1151
|
+
shimPriorsChanged = persistedShimPriors === null ? survivingShims.length > 0 : persistedShimPriors.length !== survivingShims.length || survivingShims.some((entry, index) => persistedShimPriors[index] !== entry);
|
|
1152
|
+
if (survivingShims.length > 0) {
|
|
1153
|
+
parsed.priorPluginShimPaths = survivingShims;
|
|
1154
|
+
} else {
|
|
1155
|
+
delete parsed.priorPluginShimPaths;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const shimPathChanged = parsed !== null && outcome.persistPath !== null && parsed.pluginShimPath !== outcome.persistPath;
|
|
1159
|
+
if (parsed !== null && (shimPathChanged || configProvenanceAdded || shimPriorsChanged)) {
|
|
1160
|
+
if (outcome.persistPath !== null) {
|
|
1161
|
+
parsed.pluginShimPath = outcome.persistPath;
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
writeSecretFileSync(hermesJsonPath, JSON.stringify(parsed, null, 2));
|
|
1165
|
+
} catch (persistErr) {
|
|
1166
|
+
const rollbackFailures = [];
|
|
1167
|
+
if (outcome.materializedAt !== null && outcome.createdNew && (priorPersisted === null || !sameShimTarget(outcome.materializedAt, priorPersisted))) {
|
|
1168
|
+
try {
|
|
1169
|
+
removeHermesShim([outcome.materializedAt]);
|
|
1170
|
+
} catch {
|
|
1171
|
+
rollbackFailures.push(`remove ${outcome.materializedAt} manually`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (outcome.priorCleanedAt !== null) {
|
|
1175
|
+
try {
|
|
1176
|
+
materializeHermesShim(outcome.priorCleanedAt);
|
|
1177
|
+
} catch {
|
|
1178
|
+
rollbackFailures.push(`restore the shim at ${outcome.priorCleanedAt} manually`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
for (const cleanedPath of cleanedInheritedBackfillShims) {
|
|
1182
|
+
if (outcome.priorCleanedAt !== null && sameShimTarget(cleanedPath, outcome.priorCleanedAt)) {
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
materializeHermesShim(cleanedPath);
|
|
1187
|
+
} catch {
|
|
1188
|
+
rollbackFailures.push(`restore the shim at ${cleanedPath} manually`);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
const persistMsg = persistErr instanceof Error ? persistErr.message : String(persistErr);
|
|
1192
|
+
backfillNote = rollbackFailures.length === 0 ? ` Note: could not persist the shim path (${persistMsg}); shim changes were rolled back.` : ` Note: could not persist the shim path (${persistMsg}) and rollback was incomplete \u2014 ${rollbackFailures.join("; ")}.`;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
784
1198
|
return {
|
|
785
1199
|
connectorId: options.connectorId,
|
|
786
1200
|
status: "already_installed",
|
|
787
|
-
message:
|
|
1201
|
+
message: `Already installed. Use --force to reinstall.${backfillNote}`
|
|
788
1202
|
};
|
|
789
1203
|
}
|
|
790
1204
|
const configDir = getConnectorsDir();
|
|
791
|
-
|
|
792
|
-
const configPath =
|
|
1205
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
1206
|
+
const configPath = path3.join(configDir, `${options.connectorId}.json`);
|
|
793
1207
|
const savedConnectorConfig = existing ? readSavedConnectorConfig(configPath) : {};
|
|
794
1208
|
let hermesSavedProfile;
|
|
795
1209
|
let hermesSavedHost;
|
|
@@ -798,9 +1212,9 @@ function installConnector(options) {
|
|
|
798
1212
|
let hermesResolvedHost;
|
|
799
1213
|
let hermesResolvedPort;
|
|
800
1214
|
if (options.connectorId === "hermes") {
|
|
801
|
-
if (
|
|
1215
|
+
if (fs3.existsSync(configPath)) {
|
|
802
1216
|
try {
|
|
803
|
-
const prev = JSON.parse(
|
|
1217
|
+
const prev = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
804
1218
|
if (prev?.profile != null) {
|
|
805
1219
|
try {
|
|
806
1220
|
hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));
|
|
@@ -927,6 +1341,15 @@ function installConnector(options) {
|
|
|
927
1341
|
message: "Hermes install aborted: token store unavailable. Run `remnic token generate hermes` then reinstall to complete setup."
|
|
928
1342
|
};
|
|
929
1343
|
}
|
|
1344
|
+
{
|
|
1345
|
+
const envHermesHome = readEnvVar("HERMES_HOME");
|
|
1346
|
+
if (typeof envHermesHome === "string" && envHermesHome.trim().length > 0) {
|
|
1347
|
+
try {
|
|
1348
|
+
fs3.mkdirSync(path3.dirname(hermesConfigPath(hermesProfile)), { recursive: true });
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
930
1353
|
let yamlResult;
|
|
931
1354
|
try {
|
|
932
1355
|
yamlResult = upsertHermesConfig({
|
|
@@ -967,7 +1390,7 @@ function installConnector(options) {
|
|
|
967
1390
|
let yamlRollbackErrMsg = "";
|
|
968
1391
|
try {
|
|
969
1392
|
if (yamlResult.priorContent === null) {
|
|
970
|
-
|
|
1393
|
+
fs3.unlinkSync(yamlResult.configPath);
|
|
971
1394
|
} else if (typeof yamlResult.priorContent === "string") {
|
|
972
1395
|
writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
|
|
973
1396
|
}
|
|
@@ -992,6 +1415,140 @@ function installConnector(options) {
|
|
|
992
1415
|
message
|
|
993
1416
|
};
|
|
994
1417
|
}
|
|
1418
|
+
const priorShimPathRaw = savedConnectorConfig.pluginShimPath;
|
|
1419
|
+
const shimOutcome = reconcileHermesShim(
|
|
1420
|
+
typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0 ? priorShimPathRaw : null
|
|
1421
|
+
);
|
|
1422
|
+
if (shimOutcome.persistPath !== null) {
|
|
1423
|
+
resolvedConfig.pluginShimPath = shimOutcome.persistPath;
|
|
1424
|
+
}
|
|
1425
|
+
const cleanedInheritedShims = [];
|
|
1426
|
+
{
|
|
1427
|
+
const shimPriorCandidates = [];
|
|
1428
|
+
if (typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0) {
|
|
1429
|
+
shimPriorCandidates.push(priorShimPathRaw);
|
|
1430
|
+
}
|
|
1431
|
+
const inheritedShimPriorsRaw = savedConnectorConfig.priorPluginShimPaths;
|
|
1432
|
+
if (Array.isArray(inheritedShimPriorsRaw)) {
|
|
1433
|
+
for (const entry of inheritedShimPriorsRaw) {
|
|
1434
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
1435
|
+
shimPriorCandidates.push(entry);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (shimOutcome.materializedAt !== null && shimPriorCandidates.length > 0) {
|
|
1440
|
+
const cleanupTargets = shimPriorCandidates.filter(
|
|
1441
|
+
(candidate) => shimOutcome.persistPath === null || !sameShimTarget(candidate, shimOutcome.persistPath)
|
|
1442
|
+
);
|
|
1443
|
+
if (cleanupTargets.length > 0) {
|
|
1444
|
+
try {
|
|
1445
|
+
const cleaned = removeHermesShim(cleanupTargets);
|
|
1446
|
+
cleanedInheritedShims.push(...cleaned.removedPaths);
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
const survivingShims = survivingMarkerShims(shimPriorCandidates, shimOutcome.persistPath);
|
|
1452
|
+
if (survivingShims.length > 0) {
|
|
1453
|
+
resolvedConfig.priorPluginShimPaths = survivingShims;
|
|
1454
|
+
} else {
|
|
1455
|
+
delete resolvedConfig.priorPluginShimPaths;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
resolvedConfig.hermesConfigPath = yamlResult.configPath;
|
|
1459
|
+
const hermesPriorNotes = [];
|
|
1460
|
+
const priorConfigPathRaw = savedConnectorConfig.hermesConfigPath;
|
|
1461
|
+
const priorConfigPath = typeof priorConfigPathRaw === "string" && priorConfigPathRaw.length > 0 ? priorConfigPathRaw : null;
|
|
1462
|
+
const priorConfigDiffers = priorConfigPath !== null && !sameHermesConfigTarget(priorConfigPath, yamlResult.configPath);
|
|
1463
|
+
const hermesPriorConfigMutations = [];
|
|
1464
|
+
if (priorConfigDiffers && priorConfigPath !== null && shimOutcome.materializedAt === null) {
|
|
1465
|
+
let fallbackDetail = "";
|
|
1466
|
+
if (isPlausibleHermesConfigPath(priorConfigPath)) {
|
|
1467
|
+
try {
|
|
1468
|
+
const beforeRefresh = fs3.readFileSync(priorConfigPath, "utf8");
|
|
1469
|
+
const refresh = upsertHermesConfigAt(priorConfigPath, {
|
|
1470
|
+
host: hermesHost,
|
|
1471
|
+
port: hermesPort,
|
|
1472
|
+
token: tokenEntry.token
|
|
1473
|
+
});
|
|
1474
|
+
if (refresh.updated) {
|
|
1475
|
+
hermesPriorConfigMutations.push({ mutatedPath: priorConfigPath, priorContent: beforeRefresh });
|
|
1476
|
+
}
|
|
1477
|
+
fallbackDetail = refresh.updated ? " Its remnic: block was refreshed with the newly-issued token so it keeps working." : ` Note: its token could not be refreshed (${refresh.reason ?? "config not writable"}) \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.`;
|
|
1478
|
+
} catch (refreshErr) {
|
|
1479
|
+
fallbackDetail = ` Note: its token could not be refreshed (${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}) \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.`;
|
|
1480
|
+
}
|
|
1481
|
+
} else {
|
|
1482
|
+
fallbackDetail = " Note: its token was rotated by this install \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.";
|
|
1483
|
+
}
|
|
1484
|
+
hermesPriorNotes.push(
|
|
1485
|
+
`Note: left the prior-install Hermes config in place at ${priorConfigPath} \u2014 no Remnic shim could be written under the current Hermes home, so the prior installation remains the working fallback.${fallbackDetail} Resolve the shim collision/failure above and re-run with --force to complete the migration.`
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
{
|
|
1489
|
+
const candidates = [];
|
|
1490
|
+
if (priorConfigDiffers && priorConfigPath !== null) {
|
|
1491
|
+
candidates.push(priorConfigPath);
|
|
1492
|
+
}
|
|
1493
|
+
const inheritedArrayRaw = savedConnectorConfig.priorHermesConfigPaths;
|
|
1494
|
+
if (Array.isArray(inheritedArrayRaw)) {
|
|
1495
|
+
for (const entry of inheritedArrayRaw) {
|
|
1496
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
1497
|
+
candidates.push(entry);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const inheritedLegacyRaw = savedConnectorConfig.priorHermesConfigPath;
|
|
1502
|
+
if (typeof inheritedLegacyRaw === "string" && inheritedLegacyRaw.length > 0) {
|
|
1503
|
+
candidates.push(inheritedLegacyRaw);
|
|
1504
|
+
}
|
|
1505
|
+
if (shimOutcome.materializedAt !== null) {
|
|
1506
|
+
for (const candidate of candidates) {
|
|
1507
|
+
if (sameHermesConfigTarget(candidate, yamlResult.configPath)) {
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
if (!isPlausibleHermesConfigPath(candidate)) {
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
const beforeCleanup = fs3.readFileSync(candidate, "utf8");
|
|
1515
|
+
const cleanup = removeHermesConfigFile(candidate);
|
|
1516
|
+
if (cleanup.updated) {
|
|
1517
|
+
hermesPriorConfigMutations.push({ mutatedPath: candidate, priorContent: beforeCleanup });
|
|
1518
|
+
hermesPriorNotes.push(`Cleaned remnic: block from prior-install Hermes config: ${candidate}`);
|
|
1519
|
+
}
|
|
1520
|
+
} catch {
|
|
1521
|
+
hermesPriorNotes.push(
|
|
1522
|
+
`Note: could not clean the remnic: block from the prior-install Hermes config at ${candidate} \u2014 remove it manually.`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
const survivingPriors = [];
|
|
1528
|
+
for (const candidate of candidates) {
|
|
1529
|
+
if (sameHermesConfigTarget(candidate, yamlResult.configPath)) {
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (survivingPriors.some((kept) => sameHermesConfigTarget(kept, candidate))) {
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
let blockSurvives = false;
|
|
1536
|
+
try {
|
|
1537
|
+
blockSurvives = /^remnic:/m.test(fs3.readFileSync(candidate, "utf8"));
|
|
1538
|
+
} catch {
|
|
1539
|
+
blockSurvives = false;
|
|
1540
|
+
}
|
|
1541
|
+
if (blockSurvives) {
|
|
1542
|
+
survivingPriors.push(candidate);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (survivingPriors.length > 0) {
|
|
1546
|
+
resolvedConfig.priorHermesConfigPaths = survivingPriors;
|
|
1547
|
+
} else {
|
|
1548
|
+
delete resolvedConfig.priorHermesConfigPaths;
|
|
1549
|
+
}
|
|
1550
|
+
delete resolvedConfig.priorHermesConfigPath;
|
|
1551
|
+
}
|
|
995
1552
|
try {
|
|
996
1553
|
writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
|
|
997
1554
|
} catch (writeErr) {
|
|
@@ -1009,7 +1566,7 @@ function installConnector(options) {
|
|
|
1009
1566
|
let unlinkSucceeded = false;
|
|
1010
1567
|
let unlinkErr;
|
|
1011
1568
|
try {
|
|
1012
|
-
|
|
1569
|
+
fs3.unlinkSync(yamlResult.configPath);
|
|
1013
1570
|
unlinkSucceeded = true;
|
|
1014
1571
|
} catch (err) {
|
|
1015
1572
|
unlinkErr = err;
|
|
@@ -1027,15 +1584,70 @@ function installConnector(options) {
|
|
|
1027
1584
|
} catch (yamlRollbackErr) {
|
|
1028
1585
|
yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;
|
|
1029
1586
|
}
|
|
1587
|
+
let shimRollbackMsg = "no shim changes to roll back";
|
|
1588
|
+
const shimRollbackFailures = [];
|
|
1589
|
+
const priorPersisted = typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0 ? priorShimPathRaw : null;
|
|
1590
|
+
if (shimOutcome.materializedAt !== null && shimOutcome.createdNew && (priorPersisted === null || !sameShimTarget(shimOutcome.materializedAt, priorPersisted))) {
|
|
1591
|
+
try {
|
|
1592
|
+
removeHermesShim([shimOutcome.materializedAt]);
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
shimRollbackFailures.push(
|
|
1595
|
+
`could not remove the newly-written shim at ${shimOutcome.materializedAt} (${err instanceof Error ? err.message : String(err)})`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (shimOutcome.priorCleanedAt !== null) {
|
|
1600
|
+
try {
|
|
1601
|
+
materializeHermesShim(shimOutcome.priorCleanedAt);
|
|
1602
|
+
} catch (err) {
|
|
1603
|
+
shimRollbackFailures.push(
|
|
1604
|
+
`could not restore the prior shim at ${shimOutcome.priorCleanedAt} (${err instanceof Error ? err.message : String(err)})`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
for (const cleanedPath of cleanedInheritedShims) {
|
|
1609
|
+
if (shimOutcome.priorCleanedAt !== null && sameShimTarget(cleanedPath, shimOutcome.priorCleanedAt)) {
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
try {
|
|
1613
|
+
materializeHermesShim(cleanedPath);
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
shimRollbackFailures.push(
|
|
1616
|
+
`could not restore the prior shim at ${cleanedPath} (${err instanceof Error ? err.message : String(err)})`
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
if (shimOutcome.materializedAt !== null || shimOutcome.priorCleanedAt !== null || cleanedInheritedShims.length > 0) {
|
|
1621
|
+
shimRollbackMsg = shimRollbackFailures.length === 0 ? "plugin shim changes rolled back" : `plugin shim rollback incomplete: ${shimRollbackFailures.join("; ")}`;
|
|
1622
|
+
}
|
|
1623
|
+
let priorConfigRollbackMsg = "no prior-config changes to roll back";
|
|
1624
|
+
if (hermesPriorConfigMutations.length > 0) {
|
|
1625
|
+
const priorConfigRollbackFailures = [];
|
|
1626
|
+
for (const mutation of [...hermesPriorConfigMutations].reverse()) {
|
|
1627
|
+
try {
|
|
1628
|
+
writeSecretFileSync(mutation.mutatedPath, mutation.priorContent);
|
|
1629
|
+
} catch (restoreErr) {
|
|
1630
|
+
priorConfigRollbackFailures.push(
|
|
1631
|
+
`could not restore ${mutation.mutatedPath} (${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)})`
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
priorConfigRollbackMsg = priorConfigRollbackFailures.length === 0 ? "prior-install config(s) restored" : `prior-install config rollback incomplete: ${priorConfigRollbackFailures.join("; ")}`;
|
|
1636
|
+
}
|
|
1030
1637
|
const urgentSuffix = tokenRollbackFailed ? ` tokens.json may be in an inconsistent state \u2014 manually restore hermes token with 'remnic token generate hermes'.` : "";
|
|
1031
1638
|
return {
|
|
1032
1639
|
connectorId: options.connectorId,
|
|
1033
1640
|
status: "error",
|
|
1034
|
-
message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
|
|
1641
|
+
message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}; ${shimRollbackMsg}; ${priorConfigRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
|
|
1035
1642
|
};
|
|
1036
1643
|
}
|
|
1037
1644
|
const notes = [];
|
|
1038
1645
|
notes.push(`Updated Hermes config: ${yamlResult.configPath}`);
|
|
1646
|
+
notes.push(...shimOutcome.notes);
|
|
1647
|
+
notes.push(...hermesPriorNotes);
|
|
1648
|
+
notes.push(
|
|
1649
|
+
"Next step: set `memory.provider: remnic` (and `memory_enabled: true`) in your Hermes config.yaml to activate the provider."
|
|
1650
|
+
);
|
|
1039
1651
|
if (hermesProfile === "default") {
|
|
1040
1652
|
const legacyDefaultConfigPath = hermesDefaultProfileConfigPath();
|
|
1041
1653
|
if (!sameHermesConfigTarget(yamlResult.configPath, legacyDefaultConfigPath)) {
|
|
@@ -1137,9 +1749,9 @@ function installConnector(options) {
|
|
|
1137
1749
|
if (options.connectorId === "weclone") {
|
|
1138
1750
|
try {
|
|
1139
1751
|
let proxyConfigPath = null;
|
|
1140
|
-
if (existing &&
|
|
1752
|
+
if (existing && fs3.existsSync(configPath)) {
|
|
1141
1753
|
try {
|
|
1142
|
-
const savedRegistryConfig = JSON.parse(
|
|
1754
|
+
const savedRegistryConfig = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
1143
1755
|
if (typeof savedRegistryConfig.proxyConfigPath === "string" && savedRegistryConfig.proxyConfigPath.length > 0) {
|
|
1144
1756
|
proxyConfigPath = savedRegistryConfig.proxyConfigPath;
|
|
1145
1757
|
}
|
|
@@ -1155,12 +1767,12 @@ function installConnector(options) {
|
|
|
1155
1767
|
priorConfig: prior ? safeParseJson(prior) : null,
|
|
1156
1768
|
authToken: tokenEntry?.token
|
|
1157
1769
|
});
|
|
1158
|
-
|
|
1770
|
+
fs3.mkdirSync(path3.dirname(proxyConfigPath), { recursive: true });
|
|
1159
1771
|
weCloneProxyHandleRollback = () => {
|
|
1160
1772
|
try {
|
|
1161
1773
|
if (prior === null) {
|
|
1162
|
-
if (
|
|
1163
|
-
|
|
1774
|
+
if (fs3.existsSync(proxyConfigPath)) {
|
|
1775
|
+
fs3.unlinkSync(proxyConfigPath);
|
|
1164
1776
|
}
|
|
1165
1777
|
} else {
|
|
1166
1778
|
writeSecretFileSync(proxyConfigPath, prior);
|
|
@@ -1270,13 +1882,13 @@ function removeConnector(connectorId) {
|
|
|
1270
1882
|
message: `Removal aborted: invalid connector ID ${JSON.stringify(connectorId)}. Connector IDs must match [A-Za-z0-9][A-Za-z0-9._-]*.`
|
|
1271
1883
|
};
|
|
1272
1884
|
}
|
|
1273
|
-
const configPath =
|
|
1885
|
+
const configPath = path3.join(configDir, `${connectorId}.json`);
|
|
1274
1886
|
let codexHomeOverride = null;
|
|
1275
1887
|
let savedInstallExtension = void 0;
|
|
1276
1888
|
let configParsed = false;
|
|
1277
|
-
if (connectorId === "codex-cli" &&
|
|
1889
|
+
if (connectorId === "codex-cli" && fs3.existsSync(configPath)) {
|
|
1278
1890
|
try {
|
|
1279
|
-
const parsed = JSON.parse(
|
|
1891
|
+
const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
1280
1892
|
configParsed = true;
|
|
1281
1893
|
if (typeof parsed.codexHome === "string" && parsed.codexHome.length > 0) {
|
|
1282
1894
|
codexHomeOverride = parsed.codexHome;
|
|
@@ -1291,7 +1903,50 @@ function removeConnector(connectorId) {
|
|
|
1291
1903
|
);
|
|
1292
1904
|
}
|
|
1293
1905
|
}
|
|
1294
|
-
|
|
1906
|
+
let savedHermesShimPath = null;
|
|
1907
|
+
let savedHermesConfigPath = null;
|
|
1908
|
+
const savedPriorHermesConfigPaths = [];
|
|
1909
|
+
const savedPriorHermesShimPaths = [];
|
|
1910
|
+
if (connectorId === "hermes" && fs3.existsSync(configPath)) {
|
|
1911
|
+
try {
|
|
1912
|
+
const parsedRaw = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
1913
|
+
if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
|
|
1914
|
+
throw new Error("hermes.json is not a JSON object");
|
|
1915
|
+
}
|
|
1916
|
+
const parsed = parsedRaw;
|
|
1917
|
+
if (typeof parsed.pluginShimPath === "string" && parsed.pluginShimPath.length > 0) {
|
|
1918
|
+
savedHermesShimPath = parsed.pluginShimPath;
|
|
1919
|
+
}
|
|
1920
|
+
if (Array.isArray(parsed.priorPluginShimPaths)) {
|
|
1921
|
+
for (const entry of parsed.priorPluginShimPaths) {
|
|
1922
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
1923
|
+
savedPriorHermesShimPaths.push(entry);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (typeof parsed.hermesConfigPath === "string" && parsed.hermesConfigPath.length > 0) {
|
|
1928
|
+
savedHermesConfigPath = parsed.hermesConfigPath;
|
|
1929
|
+
}
|
|
1930
|
+
if (Array.isArray(parsed.priorHermesConfigPaths)) {
|
|
1931
|
+
for (const entry of parsed.priorHermesConfigPaths) {
|
|
1932
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
1933
|
+
savedPriorHermesConfigPaths.push(entry);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
if (typeof parsed.priorHermesConfigPath === "string" && parsed.priorHermesConfigPath.length > 0) {
|
|
1938
|
+
savedPriorHermesConfigPaths.push(parsed.priorHermesConfigPath);
|
|
1939
|
+
}
|
|
1940
|
+
} catch {
|
|
1941
|
+
return {
|
|
1942
|
+
connectorId,
|
|
1943
|
+
configPath,
|
|
1944
|
+
status: "error",
|
|
1945
|
+
message: `Removal aborted: ${configPath} is malformed and cannot be parsed. It records where the Hermes plugin shim was installed; removing the connector without it could orphan the shim. Fix or delete the file (and remove any plugins/remnic/__init__.py under your Hermes home manually), then re-run removal.`
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
if (!fs3.existsSync(configPath)) {
|
|
1295
1950
|
let staleTokenRevoked = false;
|
|
1296
1951
|
try {
|
|
1297
1952
|
staleTokenRevoked = revokeToken(connectorId);
|
|
@@ -1308,7 +1963,7 @@ function removeConnector(connectorId) {
|
|
|
1308
1963
|
let storedProfile = "default";
|
|
1309
1964
|
if (connectorId === "hermes") {
|
|
1310
1965
|
try {
|
|
1311
|
-
const stored = JSON.parse(
|
|
1966
|
+
const stored = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
1312
1967
|
if (typeof stored?.profile === "string") storedProfile = stored.profile;
|
|
1313
1968
|
} catch {
|
|
1314
1969
|
}
|
|
@@ -1317,7 +1972,7 @@ function removeConnector(connectorId) {
|
|
|
1317
1972
|
let weCloneRegistryParseFailed = false;
|
|
1318
1973
|
if (connectorId === "weclone") {
|
|
1319
1974
|
try {
|
|
1320
|
-
const stored = JSON.parse(
|
|
1975
|
+
const stored = JSON.parse(fs3.readFileSync(configPath, "utf8"));
|
|
1321
1976
|
if (typeof stored.proxyConfigPath === "string" && stored.proxyConfigPath.length > 0) {
|
|
1322
1977
|
weCloneProxyConfigPath = stored.proxyConfigPath;
|
|
1323
1978
|
}
|
|
@@ -1343,7 +1998,7 @@ function removeConnector(connectorId) {
|
|
|
1343
1998
|
reason: "config-parse-failed"
|
|
1344
1999
|
};
|
|
1345
2000
|
}
|
|
1346
|
-
if (connectorId === "codex-cli" &&
|
|
2001
|
+
if (connectorId === "codex-cli" && fs3.existsSync(configPath) && !configParsed) {
|
|
1347
2002
|
console.warn(
|
|
1348
2003
|
"[remnic/connectors] removeConnector: codex-cli.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
|
|
1349
2004
|
);
|
|
@@ -1366,8 +2021,57 @@ function removeConnector(connectorId) {
|
|
|
1366
2021
|
extensionMessage = extResult.removed ? ` (memory extension removed: ${extResult.remnicExtensionDir})` : " (no memory extension present)";
|
|
1367
2022
|
}
|
|
1368
2023
|
}
|
|
2024
|
+
if (connectorId === "hermes") {
|
|
2025
|
+
const preflightCandidates = [];
|
|
2026
|
+
try {
|
|
2027
|
+
preflightCandidates.push(...hermesConfigCleanupPaths(storedProfile));
|
|
2028
|
+
} catch {
|
|
2029
|
+
}
|
|
2030
|
+
if (savedHermesConfigPath !== null && isPlausibleHermesConfigPath(savedHermesConfigPath)) {
|
|
2031
|
+
preflightCandidates.push(savedHermesConfigPath);
|
|
2032
|
+
}
|
|
2033
|
+
preflightCandidates.push(...savedPriorHermesConfigPaths.filter(isPlausibleHermesConfigPath));
|
|
2034
|
+
const uniqueCandidates = [];
|
|
2035
|
+
for (const candidate of preflightCandidates) {
|
|
2036
|
+
if (!uniqueCandidates.some((kept) => sameHermesConfigTarget(kept, candidate))) {
|
|
2037
|
+
uniqueCandidates.push(candidate);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
const blocked = [];
|
|
2041
|
+
for (const candidate of uniqueCandidates) {
|
|
2042
|
+
let needsCleanup = false;
|
|
2043
|
+
try {
|
|
2044
|
+
needsCleanup = /^remnic:/m.test(fs3.readFileSync(candidate, "utf8"));
|
|
2045
|
+
} catch {
|
|
2046
|
+
needsCleanup = false;
|
|
2047
|
+
}
|
|
2048
|
+
if (!needsCleanup) {
|
|
2049
|
+
continue;
|
|
2050
|
+
}
|
|
2051
|
+
try {
|
|
2052
|
+
assertConfigComponentsNotSymlinked(candidate);
|
|
2053
|
+
} catch {
|
|
2054
|
+
blocked.push(`${candidate} (symlinked path component)`);
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
try {
|
|
2058
|
+
fs3.accessSync(candidate, fs3.constants.W_OK);
|
|
2059
|
+
fs3.accessSync(path3.dirname(candidate), fs3.constants.W_OK);
|
|
2060
|
+
} catch {
|
|
2061
|
+
blocked.push(`${candidate} (not writable)`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (blocked.length > 0) {
|
|
2065
|
+
return {
|
|
2066
|
+
connectorId,
|
|
2067
|
+
configPath,
|
|
2068
|
+
status: "error",
|
|
2069
|
+
message: `Hermes remove aborted: the following config.yaml path(s) hold a remnic: block but cannot be cleaned: ${blocked.join(", ")}. The connector registry entry, provenance, and token were left untouched \u2014 fix the permissions or resolve the symlink and re-run removal.`
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
1369
2073
|
try {
|
|
1370
|
-
|
|
2074
|
+
fs3.unlinkSync(configPath);
|
|
1371
2075
|
} catch (unlinkErr) {
|
|
1372
2076
|
const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
|
|
1373
2077
|
return {
|
|
@@ -1393,14 +2097,14 @@ function removeConnector(connectorId) {
|
|
|
1393
2097
|
"WeClone proxy config cleanup skipped: no persisted path found in saved config (likely a legacy install predating proxyConfigPath provenance)."
|
|
1394
2098
|
);
|
|
1395
2099
|
} else {
|
|
1396
|
-
const expectedSuffix =
|
|
1397
|
-
const isSafePath =
|
|
2100
|
+
const expectedSuffix = path3.join("connectors", "weclone.json");
|
|
2101
|
+
const isSafePath = path3.isAbsolute(weCloneProxyConfigPath) && weCloneProxyConfigPath.endsWith(expectedSuffix);
|
|
1398
2102
|
if (!isSafePath) {
|
|
1399
2103
|
weCloneProxyDeleteFailed = `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation (must be absolute and end with "${expectedSuffix}"). Refusing to delete \u2014 remove the file manually if it exists.`;
|
|
1400
2104
|
} else {
|
|
1401
2105
|
try {
|
|
1402
|
-
if (
|
|
1403
|
-
|
|
2106
|
+
if (fs3.existsSync(weCloneProxyConfigPath)) {
|
|
2107
|
+
fs3.unlinkSync(weCloneProxyConfigPath);
|
|
1404
2108
|
notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);
|
|
1405
2109
|
}
|
|
1406
2110
|
} catch (err) {
|
|
@@ -1420,16 +2124,42 @@ function removeConnector(connectorId) {
|
|
|
1420
2124
|
}
|
|
1421
2125
|
if (connectorId === "hermes") {
|
|
1422
2126
|
try {
|
|
1423
|
-
const
|
|
2127
|
+
const shimCandidates = [];
|
|
2128
|
+
if (savedHermesShimPath !== null) {
|
|
2129
|
+
shimCandidates.push(savedHermesShimPath);
|
|
2130
|
+
}
|
|
2131
|
+
shimCandidates.push(...savedPriorHermesShimPaths);
|
|
2132
|
+
try {
|
|
2133
|
+
shimCandidates.push(hermesShimPath());
|
|
2134
|
+
} catch {
|
|
2135
|
+
}
|
|
2136
|
+
const shimResult = removeHermesShim(shimCandidates);
|
|
2137
|
+
if (shimResult.notes.length > 0) {
|
|
2138
|
+
notes.push(shimResult.notes.join("; "));
|
|
2139
|
+
}
|
|
2140
|
+
} catch (shimErr) {
|
|
2141
|
+
notes.push(
|
|
2142
|
+
`Hermes plugin shim cleanup skipped: ${shimErr instanceof Error ? shimErr.message : String(shimErr)}`
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
const yamlResult = removeHermesConfig({
|
|
2147
|
+
profile: storedProfile,
|
|
2148
|
+
extraConfigPaths: [
|
|
2149
|
+
...savedHermesConfigPath !== null ? [savedHermesConfigPath] : [],
|
|
2150
|
+
...savedPriorHermesConfigPaths
|
|
2151
|
+
]
|
|
2152
|
+
});
|
|
1424
2153
|
if (yamlResult.updated) {
|
|
1425
2154
|
notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);
|
|
1426
2155
|
} else if (yamlResult.reason?.startsWith("Hermes config cleanup partially failed:")) {
|
|
1427
2156
|
const tokenStatus = tokenRevoked ? "the connector registry config was deleted and the token was revoked" : "the connector registry config was deleted but TOKEN REVOCATION ALSO FAILED \u2014 inspect ~/.remnic/tokens.json and revoke manually";
|
|
2157
|
+
const shimSuffix = notes.length > 0 ? ` Completed cleanup: ${notes.join("; ")}.` : "";
|
|
1428
2158
|
return {
|
|
1429
2159
|
connectorId,
|
|
1430
2160
|
configPath,
|
|
1431
2161
|
status: "error",
|
|
1432
|
-
message: `Hermes remove partially succeeded: ${tokenStatus}, but ${yamlResult.reason}. Updated paths: ${yamlResult.configPath}. Manually remove any stale remnic: block and token material from the failed Hermes config path.`
|
|
2162
|
+
message: `Hermes remove partially succeeded: ${tokenStatus}, but ${yamlResult.reason}. Updated paths: ${yamlResult.configPath}. Manually remove any stale remnic: block and token material from the failed Hermes config path.` + shimSuffix
|
|
1433
2163
|
};
|
|
1434
2164
|
} else if (yamlResult.skipped) {
|
|
1435
2165
|
notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);
|
|
@@ -1465,18 +2195,18 @@ function sanitizeHermesProfile(profile) {
|
|
|
1465
2195
|
}
|
|
1466
2196
|
function hermesConfigPath(profile) {
|
|
1467
2197
|
const safeProfile = sanitizeHermesProfile(profile);
|
|
1468
|
-
const hermesRoot =
|
|
1469
|
-
const rootConfigPath =
|
|
1470
|
-
const profilesRoot =
|
|
2198
|
+
const hermesRoot = resolveHermesRoot();
|
|
2199
|
+
const rootConfigPath = path3.join(hermesRoot, "config.yaml");
|
|
2200
|
+
const profilesRoot = path3.join(hermesRoot, "profiles");
|
|
1471
2201
|
if (safeProfile === "default") {
|
|
1472
|
-
const defaultProfileDir =
|
|
1473
|
-
if (isFile(rootConfigPath) || !
|
|
2202
|
+
const defaultProfileDir = path3.join(profilesRoot, safeProfile);
|
|
2203
|
+
if (isFile(rootConfigPath) || !fs3.existsSync(rootConfigPath) && !isDirectory(defaultProfileDir)) {
|
|
1474
2204
|
return rootConfigPath;
|
|
1475
2205
|
}
|
|
1476
2206
|
}
|
|
1477
|
-
const cfgPath =
|
|
1478
|
-
const rel =
|
|
1479
|
-
if (rel.startsWith("..") ||
|
|
2207
|
+
const cfgPath = path3.resolve(profilesRoot, safeProfile, "config.yaml");
|
|
2208
|
+
const rel = path3.relative(profilesRoot, cfgPath);
|
|
2209
|
+
if (rel.startsWith("..") || path3.isAbsolute(rel)) {
|
|
1480
2210
|
throw new Error(
|
|
1481
2211
|
`Invalid Hermes profile path: resolved outside ${profilesRoot}`
|
|
1482
2212
|
);
|
|
@@ -1485,31 +2215,30 @@ function hermesConfigPath(profile) {
|
|
|
1485
2215
|
}
|
|
1486
2216
|
function isDirectory(filePath) {
|
|
1487
2217
|
try {
|
|
1488
|
-
return
|
|
2218
|
+
return fs3.statSync(filePath).isDirectory();
|
|
1489
2219
|
} catch {
|
|
1490
2220
|
return false;
|
|
1491
2221
|
}
|
|
1492
2222
|
}
|
|
1493
2223
|
function isFile(filePath) {
|
|
1494
2224
|
try {
|
|
1495
|
-
return
|
|
2225
|
+
return fs3.statSync(filePath).isFile();
|
|
1496
2226
|
} catch {
|
|
1497
2227
|
return false;
|
|
1498
2228
|
}
|
|
1499
2229
|
}
|
|
1500
2230
|
function hermesConfigTarget(filePath) {
|
|
1501
2231
|
try {
|
|
1502
|
-
return
|
|
2232
|
+
return fs3.realpathSync.native(filePath);
|
|
1503
2233
|
} catch {
|
|
1504
|
-
return
|
|
2234
|
+
return path3.resolve(filePath);
|
|
1505
2235
|
}
|
|
1506
2236
|
}
|
|
1507
2237
|
function sameHermesConfigTarget(leftPath, rightPath) {
|
|
1508
2238
|
return hermesConfigTarget(leftPath) === hermesConfigTarget(rightPath);
|
|
1509
2239
|
}
|
|
1510
2240
|
function hermesDefaultProfileConfigPath() {
|
|
1511
|
-
|
|
1512
|
-
return path2.join(hermesRoot, "profiles", "default", "config.yaml");
|
|
2241
|
+
return path3.join(resolveHermesRoot(), "profiles", "default", "config.yaml");
|
|
1513
2242
|
}
|
|
1514
2243
|
function hermesConfigCleanupPaths(profile) {
|
|
1515
2244
|
const cfgPath = hermesConfigPath(profile);
|
|
@@ -1565,29 +2294,29 @@ function sanitizeHermesPort(port) {
|
|
|
1565
2294
|
return numeric;
|
|
1566
2295
|
}
|
|
1567
2296
|
function writeSecretFileSync(filePath, data) {
|
|
1568
|
-
const dir =
|
|
1569
|
-
const base =
|
|
1570
|
-
const tmpPath =
|
|
2297
|
+
const dir = path3.dirname(filePath);
|
|
2298
|
+
const base = path3.basename(filePath);
|
|
2299
|
+
const tmpPath = path3.join(
|
|
1571
2300
|
dir,
|
|
1572
2301
|
`.${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
|
|
1573
2302
|
);
|
|
1574
2303
|
let wroteTemp = false;
|
|
1575
2304
|
try {
|
|
1576
|
-
|
|
2305
|
+
fs3.writeFileSync(tmpPath, data, { mode: 384, flag: "wx" });
|
|
1577
2306
|
wroteTemp = true;
|
|
1578
2307
|
try {
|
|
1579
|
-
|
|
2308
|
+
fs3.chmodSync(tmpPath, 384);
|
|
1580
2309
|
} catch {
|
|
1581
2310
|
}
|
|
1582
|
-
|
|
2311
|
+
fs3.renameSync(tmpPath, filePath);
|
|
1583
2312
|
try {
|
|
1584
|
-
|
|
2313
|
+
fs3.chmodSync(filePath, 384);
|
|
1585
2314
|
} catch {
|
|
1586
2315
|
}
|
|
1587
2316
|
} catch (err) {
|
|
1588
2317
|
if (wroteTemp) {
|
|
1589
2318
|
try {
|
|
1590
|
-
|
|
2319
|
+
fs3.unlinkSync(tmpPath);
|
|
1591
2320
|
} catch {
|
|
1592
2321
|
}
|
|
1593
2322
|
}
|
|
@@ -1595,8 +2324,11 @@ function writeSecretFileSync(filePath, data) {
|
|
|
1595
2324
|
}
|
|
1596
2325
|
}
|
|
1597
2326
|
function upsertHermesConfig(opts) {
|
|
1598
|
-
|
|
1599
|
-
|
|
2327
|
+
return upsertHermesConfigAt(hermesConfigPath(opts.profile), opts);
|
|
2328
|
+
}
|
|
2329
|
+
function upsertHermesConfigAt(cfgPath, opts) {
|
|
2330
|
+
assertConfigComponentsNotSymlinked(cfgPath);
|
|
2331
|
+
const profileDir = path3.dirname(cfgPath);
|
|
1600
2332
|
const safeHost = sanitizeHermesHost(opts.host);
|
|
1601
2333
|
const safePort = sanitizeHermesPort(opts.port);
|
|
1602
2334
|
if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {
|
|
@@ -1616,11 +2348,11 @@ function upsertHermesConfig(opts) {
|
|
|
1616
2348
|
` port: ${safePort}`,
|
|
1617
2349
|
` token: "${opts.token}"`
|
|
1618
2350
|
].join("\n");
|
|
1619
|
-
if (!
|
|
2351
|
+
if (!fs3.existsSync(cfgPath)) {
|
|
1620
2352
|
writeSecretFileSync(cfgPath, block + "\n");
|
|
1621
2353
|
return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };
|
|
1622
2354
|
}
|
|
1623
|
-
const raw =
|
|
2355
|
+
const raw = fs3.readFileSync(cfgPath, "utf8");
|
|
1624
2356
|
const hasRemnicBlock = /^remnic:/m.test(raw);
|
|
1625
2357
|
if (!hasRemnicBlock) {
|
|
1626
2358
|
const separator = raw.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -1678,7 +2410,33 @@ function upsertHermesConfig(opts) {
|
|
|
1678
2410
|
return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
|
|
1679
2411
|
}
|
|
1680
2412
|
function removeHermesConfig(opts) {
|
|
1681
|
-
|
|
2413
|
+
let envCfgPaths;
|
|
2414
|
+
let envResolutionError = null;
|
|
2415
|
+
try {
|
|
2416
|
+
envCfgPaths = [...hermesConfigCleanupPaths(opts.profile)];
|
|
2417
|
+
} catch (err) {
|
|
2418
|
+
envCfgPaths = [];
|
|
2419
|
+
envResolutionError = err instanceof Error ? err.message : String(err);
|
|
2420
|
+
}
|
|
2421
|
+
const cfgPaths = [];
|
|
2422
|
+
for (const candidate of envCfgPaths) {
|
|
2423
|
+
if (!cfgPaths.some((existing) => sameHermesConfigTarget(existing, candidate))) {
|
|
2424
|
+
cfgPaths.push(candidate);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
for (const extra of opts.extraConfigPaths ?? []) {
|
|
2428
|
+
if (isPlausibleHermesConfigPath(extra) && !cfgPaths.some((existing) => sameHermesConfigTarget(existing, extra))) {
|
|
2429
|
+
cfgPaths.push(extra);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
if (cfgPaths.length === 0) {
|
|
2433
|
+
return {
|
|
2434
|
+
updated: false,
|
|
2435
|
+
skipped: true,
|
|
2436
|
+
reason: envResolutionError ? `Hermes config cleanup failed: ${envResolutionError}` : "Hermes config.yaml not found",
|
|
2437
|
+
configPath: "<unresolved>"
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
1682
2440
|
const results = cfgPaths.map((cfgPath) => {
|
|
1683
2441
|
try {
|
|
1684
2442
|
return removeHermesConfigFile(cfgPath);
|
|
@@ -1715,15 +2473,29 @@ function removeHermesConfig(opts) {
|
|
|
1715
2473
|
return cleanupFailure;
|
|
1716
2474
|
}
|
|
1717
2475
|
const existingWithoutBlock = results.find((result) => result.reason !== "Hermes config.yaml not found");
|
|
1718
|
-
|
|
2476
|
+
const fallbackResult = existingWithoutBlock ?? results[0];
|
|
2477
|
+
if (fallbackResult) {
|
|
2478
|
+
return fallbackResult;
|
|
2479
|
+
}
|
|
2480
|
+
return {
|
|
1719
2481
|
updated: false,
|
|
1720
2482
|
skipped: true,
|
|
1721
2483
|
reason: "Hermes config.yaml not found",
|
|
1722
|
-
configPath:
|
|
2484
|
+
configPath: cfgPaths[0] ?? "<unresolved>"
|
|
1723
2485
|
};
|
|
1724
2486
|
}
|
|
1725
2487
|
function removeHermesConfigFile(cfgPath) {
|
|
1726
|
-
|
|
2488
|
+
try {
|
|
2489
|
+
assertConfigComponentsNotSymlinked(cfgPath);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
return {
|
|
2492
|
+
updated: false,
|
|
2493
|
+
skipped: true,
|
|
2494
|
+
reason: `Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`,
|
|
2495
|
+
configPath: cfgPath
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
if (!fs3.existsSync(cfgPath)) {
|
|
1727
2499
|
return {
|
|
1728
2500
|
updated: false,
|
|
1729
2501
|
skipped: true,
|
|
@@ -1731,7 +2503,7 @@ function removeHermesConfigFile(cfgPath) {
|
|
|
1731
2503
|
configPath: cfgPath
|
|
1732
2504
|
};
|
|
1733
2505
|
}
|
|
1734
|
-
const raw =
|
|
2506
|
+
const raw = fs3.readFileSync(cfgPath, "utf8");
|
|
1735
2507
|
if (!/^remnic:/m.test(raw)) {
|
|
1736
2508
|
return {
|
|
1737
2509
|
updated: false,
|
|
@@ -1828,15 +2600,15 @@ async function doctorConnector(connectorId) {
|
|
|
1828
2600
|
healthy: false
|
|
1829
2601
|
};
|
|
1830
2602
|
}
|
|
1831
|
-
const configPath =
|
|
2603
|
+
const configPath = path3.join(getConnectorsDir(), `${connectorId}.json`);
|
|
1832
2604
|
const checks = [];
|
|
1833
2605
|
checks.push({
|
|
1834
2606
|
name: "Config file",
|
|
1835
|
-
ok:
|
|
2607
|
+
ok: fs3.existsSync(configPath),
|
|
1836
2608
|
detail: configPath
|
|
1837
2609
|
});
|
|
1838
2610
|
try {
|
|
1839
|
-
const raw =
|
|
2611
|
+
const raw = fs3.readFileSync(configPath, "utf8");
|
|
1840
2612
|
JSON.parse(raw);
|
|
1841
2613
|
checks.push({ name: "Config valid", ok: true, detail: "OK" });
|
|
1842
2614
|
} catch (e) {
|
|
@@ -1860,7 +2632,7 @@ async function doctorConnector(connectorId) {
|
|
|
1860
2632
|
}
|
|
1861
2633
|
const memoryDir = instance.config.memoryDir;
|
|
1862
2634
|
if (memoryDir) {
|
|
1863
|
-
if (
|
|
2635
|
+
if (fs3.existsSync(memoryDir)) {
|
|
1864
2636
|
checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
|
|
1865
2637
|
} else {
|
|
1866
2638
|
checks.push({ name: "Memory directory", ok: false, detail: `Not found: ${memoryDir}` });
|
|
@@ -1874,32 +2646,32 @@ var CODEX_EXTENSIONS_SUBDIR = "memories_extensions";
|
|
|
1874
2646
|
var REMNIC_EXTENSION_DIR_NAME = "remnic";
|
|
1875
2647
|
function resolveCodexHome(override) {
|
|
1876
2648
|
if (override && typeof override === "string" && override.trim().length > 0) {
|
|
1877
|
-
return
|
|
2649
|
+
return path3.resolve(override.trim());
|
|
1878
2650
|
}
|
|
1879
2651
|
const envHome = readEnvVar("CODEX_HOME");
|
|
1880
2652
|
if (envHome && envHome.trim().length > 0) {
|
|
1881
|
-
return
|
|
2653
|
+
return path3.resolve(envHome.trim());
|
|
1882
2654
|
}
|
|
1883
2655
|
const home = readEnvVar("HOME") || readEnvVar("USERPROFILE") || resolveHomeDir();
|
|
1884
|
-
return
|
|
2656
|
+
return path3.resolve(home, ".codex");
|
|
1885
2657
|
}
|
|
1886
2658
|
function resolveCodexMemoryExtensionPaths(codexHomeOverride) {
|
|
1887
2659
|
const codexHome = resolveCodexHome(codexHomeOverride);
|
|
1888
|
-
const memoriesDir =
|
|
1889
|
-
const extensionsRoot =
|
|
1890
|
-
const remnicExtensionDir =
|
|
2660
|
+
const memoriesDir = path3.join(codexHome, CODEX_MEMORIES_SUBDIR);
|
|
2661
|
+
const extensionsRoot = path3.join(path3.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);
|
|
2662
|
+
const remnicExtensionDir = path3.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);
|
|
1891
2663
|
return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };
|
|
1892
2664
|
}
|
|
1893
2665
|
function locatePluginCodexExtensionSource(override) {
|
|
1894
2666
|
if (override && typeof override === "string" && override.trim().length > 0) {
|
|
1895
|
-
const resolved =
|
|
1896
|
-
if (
|
|
2667
|
+
const resolved = path3.resolve(override.trim());
|
|
2668
|
+
if (fs3.existsSync(resolved) && fs3.statSync(resolved).isDirectory()) {
|
|
1897
2669
|
return resolved;
|
|
1898
2670
|
}
|
|
1899
2671
|
throw new Error(`Codex extension source directory not found: ${resolved}`);
|
|
1900
2672
|
}
|
|
1901
|
-
const EXTENSION_SUBPATH =
|
|
1902
|
-
const WORKSPACE_RELATIVE_PATH =
|
|
2673
|
+
const EXTENSION_SUBPATH = path3.join("memories_extensions", "remnic");
|
|
2674
|
+
const WORKSPACE_RELATIVE_PATH = path3.join(
|
|
1903
2675
|
"packages",
|
|
1904
2676
|
"plugin-codex",
|
|
1905
2677
|
"memories_extensions",
|
|
@@ -1907,15 +2679,15 @@ function locatePluginCodexExtensionSource(override) {
|
|
|
1907
2679
|
);
|
|
1908
2680
|
const searched = [];
|
|
1909
2681
|
try {
|
|
1910
|
-
const moduleDir =
|
|
1911
|
-
const bundledCandidate =
|
|
2682
|
+
const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
2683
|
+
const bundledCandidate = path3.join(moduleDir, "codex");
|
|
1912
2684
|
searched.push(bundledCandidate);
|
|
1913
|
-
if (
|
|
2685
|
+
if (fs3.existsSync(bundledCandidate) && fs3.statSync(bundledCandidate).isDirectory()) {
|
|
1914
2686
|
return bundledCandidate;
|
|
1915
2687
|
}
|
|
1916
|
-
const distConnectorsCandidate =
|
|
2688
|
+
const distConnectorsCandidate = path3.join(moduleDir, "connectors", "codex");
|
|
1917
2689
|
searched.push(distConnectorsCandidate);
|
|
1918
|
-
if (
|
|
2690
|
+
if (fs3.existsSync(distConnectorsCandidate) && fs3.statSync(distConnectorsCandidate).isDirectory()) {
|
|
1919
2691
|
return distConnectorsCandidate;
|
|
1920
2692
|
}
|
|
1921
2693
|
} catch {
|
|
@@ -1923,19 +2695,19 @@ function locatePluginCodexExtensionSource(override) {
|
|
|
1923
2695
|
try {
|
|
1924
2696
|
const requireFromHere = createRequire(import.meta.url);
|
|
1925
2697
|
const pluginPkgJsonPath = requireFromHere.resolve("@remnic/plugin-codex/package.json");
|
|
1926
|
-
const pluginPkgRoot =
|
|
1927
|
-
const candidate =
|
|
2698
|
+
const pluginPkgRoot = path3.dirname(pluginPkgJsonPath);
|
|
2699
|
+
const candidate = path3.join(pluginPkgRoot, EXTENSION_SUBPATH);
|
|
1928
2700
|
searched.push(candidate);
|
|
1929
|
-
if (
|
|
2701
|
+
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
1930
2702
|
return candidate;
|
|
1931
2703
|
}
|
|
1932
2704
|
} catch {
|
|
1933
2705
|
}
|
|
1934
2706
|
try {
|
|
1935
|
-
const moduleDir =
|
|
2707
|
+
const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
1936
2708
|
let dir = moduleDir;
|
|
1937
2709
|
for (let depth = 0; depth < 8; depth += 1) {
|
|
1938
|
-
const candidate =
|
|
2710
|
+
const candidate = path3.join(
|
|
1939
2711
|
dir,
|
|
1940
2712
|
"node_modules",
|
|
1941
2713
|
"@remnic",
|
|
@@ -1943,10 +2715,10 @@ function locatePluginCodexExtensionSource(override) {
|
|
|
1943
2715
|
EXTENSION_SUBPATH
|
|
1944
2716
|
);
|
|
1945
2717
|
searched.push(candidate);
|
|
1946
|
-
if (
|
|
2718
|
+
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
1947
2719
|
return candidate;
|
|
1948
2720
|
}
|
|
1949
|
-
const parent =
|
|
2721
|
+
const parent = path3.dirname(dir);
|
|
1950
2722
|
if (parent === dir) break;
|
|
1951
2723
|
dir = parent;
|
|
1952
2724
|
}
|
|
@@ -1954,19 +2726,19 @@ function locatePluginCodexExtensionSource(override) {
|
|
|
1954
2726
|
}
|
|
1955
2727
|
const anchors = [];
|
|
1956
2728
|
try {
|
|
1957
|
-
anchors.push(
|
|
2729
|
+
anchors.push(path3.dirname(fileURLToPath(import.meta.url)));
|
|
1958
2730
|
} catch {
|
|
1959
2731
|
}
|
|
1960
2732
|
anchors.push(process.cwd());
|
|
1961
2733
|
for (const anchor of anchors) {
|
|
1962
2734
|
let dir = anchor;
|
|
1963
2735
|
for (let depth = 0; depth < 12; depth += 1) {
|
|
1964
|
-
const candidate =
|
|
2736
|
+
const candidate = path3.join(dir, WORKSPACE_RELATIVE_PATH);
|
|
1965
2737
|
searched.push(candidate);
|
|
1966
|
-
if (
|
|
2738
|
+
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
|
|
1967
2739
|
return candidate;
|
|
1968
2740
|
}
|
|
1969
|
-
const parent =
|
|
2741
|
+
const parent = path3.dirname(dir);
|
|
1970
2742
|
if (parent === dir) break;
|
|
1971
2743
|
dir = parent;
|
|
1972
2744
|
}
|
|
@@ -1977,15 +2749,15 @@ function locatePluginCodexExtensionSource(override) {
|
|
|
1977
2749
|
}
|
|
1978
2750
|
function copyDirRecursiveSync(src, dest) {
|
|
1979
2751
|
let count = 0;
|
|
1980
|
-
|
|
1981
|
-
const entries =
|
|
2752
|
+
fs3.mkdirSync(dest, { recursive: true });
|
|
2753
|
+
const entries = fs3.readdirSync(src, { withFileTypes: true });
|
|
1982
2754
|
for (const entry of entries) {
|
|
1983
|
-
const from =
|
|
1984
|
-
const to =
|
|
2755
|
+
const from = path3.join(src, entry.name);
|
|
2756
|
+
const to = path3.join(dest, entry.name);
|
|
1985
2757
|
if (entry.isDirectory()) {
|
|
1986
2758
|
count += copyDirRecursiveSync(from, to);
|
|
1987
2759
|
} else if (entry.isFile()) {
|
|
1988
|
-
|
|
2760
|
+
fs3.copyFileSync(from, to);
|
|
1989
2761
|
count += 1;
|
|
1990
2762
|
}
|
|
1991
2763
|
}
|
|
@@ -1994,29 +2766,29 @@ function copyDirRecursiveSync(src, dest) {
|
|
|
1994
2766
|
function installCodexMemoryExtension(options = {}) {
|
|
1995
2767
|
const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
|
|
1996
2768
|
const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);
|
|
1997
|
-
|
|
2769
|
+
fs3.mkdirSync(paths.extensionsRoot, { recursive: true });
|
|
1998
2770
|
const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;
|
|
1999
2771
|
const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
2000
2772
|
const now = Date.now();
|
|
2001
2773
|
try {
|
|
2002
|
-
const existingEntries =
|
|
2774
|
+
const existingEntries = fs3.readdirSync(paths.extensionsRoot);
|
|
2003
2775
|
for (const entry of existingEntries) {
|
|
2004
2776
|
if (!entry.startsWith(tmpPrefix)) continue;
|
|
2005
|
-
const stalePath =
|
|
2777
|
+
const stalePath = path3.join(paths.extensionsRoot, entry);
|
|
2006
2778
|
try {
|
|
2007
|
-
const stat =
|
|
2779
|
+
const stat = fs3.statSync(stalePath);
|
|
2008
2780
|
const ageMs = now - stat.mtimeMs;
|
|
2009
2781
|
if (ageMs < STALE_TMP_THRESHOLD_MS) {
|
|
2010
2782
|
continue;
|
|
2011
2783
|
}
|
|
2012
|
-
|
|
2784
|
+
fs3.rmSync(stalePath, { recursive: true, force: true });
|
|
2013
2785
|
} catch {
|
|
2014
2786
|
}
|
|
2015
2787
|
}
|
|
2016
2788
|
} catch {
|
|
2017
2789
|
}
|
|
2018
2790
|
const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;
|
|
2019
|
-
const tmpDir =
|
|
2791
|
+
const tmpDir = path3.join(paths.extensionsRoot, tmpName);
|
|
2020
2792
|
let filesCopied = 0;
|
|
2021
2793
|
let commitFn = () => {
|
|
2022
2794
|
};
|
|
@@ -2025,16 +2797,16 @@ function installCodexMemoryExtension(options = {}) {
|
|
|
2025
2797
|
try {
|
|
2026
2798
|
filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);
|
|
2027
2799
|
const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;
|
|
2028
|
-
const hadExisting =
|
|
2800
|
+
const hadExisting = fs3.existsSync(paths.remnicExtensionDir);
|
|
2029
2801
|
if (hadExisting) {
|
|
2030
|
-
|
|
2802
|
+
fs3.renameSync(paths.remnicExtensionDir, backupDir);
|
|
2031
2803
|
}
|
|
2032
2804
|
try {
|
|
2033
|
-
|
|
2805
|
+
fs3.renameSync(tmpDir, paths.remnicExtensionDir);
|
|
2034
2806
|
} catch (renameErr) {
|
|
2035
2807
|
if (hadExisting) {
|
|
2036
2808
|
try {
|
|
2037
|
-
|
|
2809
|
+
fs3.renameSync(backupDir, paths.remnicExtensionDir);
|
|
2038
2810
|
} catch {
|
|
2039
2811
|
}
|
|
2040
2812
|
}
|
|
@@ -2043,7 +2815,7 @@ function installCodexMemoryExtension(options = {}) {
|
|
|
2043
2815
|
commitFn = () => {
|
|
2044
2816
|
if (hadExisting) {
|
|
2045
2817
|
try {
|
|
2046
|
-
|
|
2818
|
+
fs3.rmSync(backupDir, { recursive: true, force: true });
|
|
2047
2819
|
} catch {
|
|
2048
2820
|
}
|
|
2049
2821
|
}
|
|
@@ -2051,31 +2823,31 @@ function installCodexMemoryExtension(options = {}) {
|
|
|
2051
2823
|
rollbackFn = () => {
|
|
2052
2824
|
if (hadExisting) {
|
|
2053
2825
|
try {
|
|
2054
|
-
if (
|
|
2055
|
-
|
|
2826
|
+
if (fs3.existsSync(paths.remnicExtensionDir)) {
|
|
2827
|
+
fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
2056
2828
|
}
|
|
2057
|
-
|
|
2829
|
+
fs3.renameSync(backupDir, paths.remnicExtensionDir);
|
|
2058
2830
|
} catch {
|
|
2059
2831
|
}
|
|
2060
2832
|
} else {
|
|
2061
2833
|
try {
|
|
2062
|
-
if (
|
|
2063
|
-
|
|
2834
|
+
if (fs3.existsSync(paths.remnicExtensionDir)) {
|
|
2835
|
+
fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
2064
2836
|
}
|
|
2065
2837
|
} catch {
|
|
2066
2838
|
}
|
|
2067
2839
|
}
|
|
2068
2840
|
};
|
|
2069
2841
|
} catch (err) {
|
|
2070
|
-
if (
|
|
2842
|
+
if (fs3.existsSync(tmpDir)) {
|
|
2071
2843
|
try {
|
|
2072
|
-
|
|
2844
|
+
fs3.rmSync(tmpDir, { recursive: true, force: true });
|
|
2073
2845
|
} catch {
|
|
2074
2846
|
}
|
|
2075
2847
|
}
|
|
2076
2848
|
throw err;
|
|
2077
2849
|
}
|
|
2078
|
-
const instructionsPath =
|
|
2850
|
+
const instructionsPath = path3.join(paths.remnicExtensionDir, "instructions.md");
|
|
2079
2851
|
return {
|
|
2080
2852
|
...paths,
|
|
2081
2853
|
instructionsPath,
|
|
@@ -2087,8 +2859,8 @@ function installCodexMemoryExtension(options = {}) {
|
|
|
2087
2859
|
function removeCodexMemoryExtension(options = {}) {
|
|
2088
2860
|
const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
|
|
2089
2861
|
let removed = false;
|
|
2090
|
-
if (
|
|
2091
|
-
|
|
2862
|
+
if (fs3.existsSync(paths.remnicExtensionDir)) {
|
|
2863
|
+
fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
2092
2864
|
removed = true;
|
|
2093
2865
|
}
|
|
2094
2866
|
return { ...paths, removed };
|
|
@@ -2099,11 +2871,11 @@ function resolveWeCloneProxyConfigPath() {
|
|
|
2099
2871
|
const remnicHome = readEnvVar("REMNIC_HOME");
|
|
2100
2872
|
const override = remnicHome && remnicHome.length > 0 ? remnicHome : readEnvVar("ENGRAM_HOME");
|
|
2101
2873
|
if (override && override.length > 0) {
|
|
2102
|
-
return
|
|
2874
|
+
return path3.resolve(expandTildePath(override), "connectors", WECLONE_PROXY_CONFIG_FILENAME);
|
|
2103
2875
|
}
|
|
2104
2876
|
const envHome = readEnvVar("HOME");
|
|
2105
2877
|
const home = envHome && envHome.length > 0 ? envHome : os.homedir();
|
|
2106
|
-
return
|
|
2878
|
+
return path3.resolve(
|
|
2107
2879
|
home,
|
|
2108
2880
|
WECLONE_PROXY_CONFIG_DIRNAME,
|
|
2109
2881
|
"connectors",
|
|
@@ -2112,8 +2884,8 @@ function resolveWeCloneProxyConfigPath() {
|
|
|
2112
2884
|
}
|
|
2113
2885
|
function readWeCloneProxyConfigIfExists(configPath) {
|
|
2114
2886
|
try {
|
|
2115
|
-
if (!
|
|
2116
|
-
return
|
|
2887
|
+
if (!fs3.existsSync(configPath)) return null;
|
|
2888
|
+
return fs3.readFileSync(configPath, "utf8");
|
|
2117
2889
|
} catch {
|
|
2118
2890
|
return null;
|
|
2119
2891
|
}
|
|
@@ -2257,4 +3029,4 @@ export {
|
|
|
2257
3029
|
resolveWeCloneProxyConfigPath,
|
|
2258
3030
|
buildWeCloneProxyConfig
|
|
2259
3031
|
};
|
|
2260
|
-
//# sourceMappingURL=chunk-
|
|
3032
|
+
//# sourceMappingURL=chunk-UJBESW6X.js.map
|