engramx 2.1.0 → 3.0.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +114 -17
  3. package/dist/{aider-context-J557IHIP.js → aider-context-6IDE3R7U.js} +1 -1
  4. package/dist/{chunk-PEH54LYC.js → chunk-645NBY6L.js} +42 -5
  5. package/dist/chunk-73IBCRFI.js +215 -0
  6. package/dist/{chunk-ZVWRIVWQ.js → chunk-B4UOE64J.js} +29 -11
  7. package/dist/{chunk-XFE6ZANP.js → chunk-FKY6HIT2.js} +1 -1
  8. package/dist/chunk-RJC6RNXJ.js +1405 -0
  9. package/dist/{chunk-4XA6ENNL.js → chunk-VLTWBTQ7.js} +14 -15
  10. package/dist/chunk-ZUC6OXSL.js +178 -0
  11. package/dist/cli.js +277 -1259
  12. package/dist/{core-TSXA5XZH.js → core-77F2BVYV.js} +2 -2
  13. package/dist/{cursor-mdc-VEOFFDVO.js → cursor-mdc-EEO7PYZ3.js} +1 -1
  14. package/dist/{exporter-AWXS34AS.js → exporter-ZYJ4WM2F.js} +1 -1
  15. package/dist/{importer-3Q5M6QBL.js → importer-4UWQDH4W.js} +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/mcp-client-ROOJF76V.js +9 -0
  18. package/dist/mcp-config-QD4NPVXB.js +12 -0
  19. package/dist/{migrate-UKCO6BUU.js → migrate-KJ5K5NWO.js} +1 -1
  20. package/dist/{plugin-loader-STTGYIL5.js → plugin-loader-SQQB6V74.js} +69 -23
  21. package/dist/resolver-H7GXVP73.js +21 -0
  22. package/dist/serve.js +2 -2
  23. package/dist/{server-A6MUVKQK.js → server-2ZQKXJ5M.js} +74 -6
  24. package/dist/{windsurf-rules-RWPKBHRD.js → windsurf-rules-XF7MYF6J.js} +1 -1
  25. package/dist/{wizard-AOXWMSXW.js → wizard-UH27IO4I.js} +2 -2
  26. package/package.json +8 -3
  27. package/scripts/postinstall.mjs +32 -0
  28. package/scripts/preuninstall.mjs +200 -0
  29. package/dist/{tuner-KFNNGKG3.js → tuner-Y2YENAZC.js} +3 -3
@@ -310,7 +310,7 @@ function writeToFile(filePath, summary) {
310
310
  writeFileSync2(filePath, newContent);
311
311
  }
312
312
  async function autogen(projectRoot, target, task) {
313
- const { getStore } = await import("./core-TSXA5XZH.js");
313
+ const { getStore } = await import("./core-77F2BVYV.js");
314
314
  const store = await getStore(projectRoot);
315
315
  try {
316
316
  let view = VIEWS.general;
@@ -326,26 +326,25 @@ async function autogen(projectRoot, target, task) {
326
326
  }
327
327
  const summary = generateSummary(store, view);
328
328
  const stats = store.getStats();
329
- let targetFile;
329
+ const targetFiles = [];
330
330
  if (target === "claude") {
331
- targetFile = join2(projectRoot, "CLAUDE.md");
331
+ targetFiles.push(join2(projectRoot, "CLAUDE.md"));
332
332
  } else if (target === "cursor") {
333
- targetFile = join2(projectRoot, ".cursorrules");
333
+ targetFiles.push(join2(projectRoot, ".cursorrules"));
334
334
  } else if (target === "agents") {
335
- targetFile = join2(projectRoot, "AGENTS.md");
335
+ targetFiles.push(join2(projectRoot, "AGENTS.md"));
336
336
  } else {
337
- if (existsSync2(join2(projectRoot, "CLAUDE.md"))) {
338
- targetFile = join2(projectRoot, "CLAUDE.md");
339
- } else if (existsSync2(join2(projectRoot, ".cursorrules"))) {
340
- targetFile = join2(projectRoot, ".cursorrules");
341
- } else if (existsSync2(join2(projectRoot, "AGENTS.md"))) {
342
- targetFile = join2(projectRoot, "AGENTS.md");
343
- } else {
344
- targetFile = join2(projectRoot, "CLAUDE.md");
337
+ targetFiles.push(join2(projectRoot, "CLAUDE.md"));
338
+ targetFiles.push(join2(projectRoot, "AGENTS.md"));
339
+ const cursorRules = join2(projectRoot, ".cursorrules");
340
+ if (existsSync2(cursorRules)) {
341
+ targetFiles.push(cursorRules);
345
342
  }
346
343
  }
347
- writeToFile(targetFile, summary);
348
- return { file: targetFile, nodesIncluded: stats.nodes, view: view.name };
344
+ for (const f of targetFiles) {
345
+ writeToFile(f, summary);
346
+ }
347
+ return { files: targetFiles, nodesIncluded: stats.nodes, view: view.name };
349
348
  } finally {
350
349
  store.close();
351
350
  }
@@ -0,0 +1,178 @@
1
+ // src/providers/mcp-config.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ function getMcpConfigPath() {
6
+ const override = process.env.ENGRAM_MCP_CONFIG_PATH;
7
+ if (override && override.length > 0) return override;
8
+ return join(homedir(), ".engram", "mcp-providers.json");
9
+ }
10
+ function loadMcpConfigs(path = getMcpConfigPath()) {
11
+ if (!existsSync(path)) {
12
+ return { configs: [], failed: [] };
13
+ }
14
+ let raw;
15
+ try {
16
+ raw = readFileSync(path, "utf-8");
17
+ } catch (err) {
18
+ return {
19
+ configs: [],
20
+ failed: [
21
+ {
22
+ index: -1,
23
+ reason: `failed to read config file: ${err.message}`
24
+ }
25
+ ]
26
+ };
27
+ }
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(raw);
31
+ } catch (err) {
32
+ return {
33
+ configs: [],
34
+ failed: [
35
+ {
36
+ index: -1,
37
+ reason: `invalid JSON in ${path}: ${err.message}`
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ if (!isMcpProvidersFile(parsed)) {
43
+ return {
44
+ configs: [],
45
+ failed: [
46
+ {
47
+ index: -1,
48
+ reason: `expected { providers: [...] } shape in ${path}`
49
+ }
50
+ ]
51
+ };
52
+ }
53
+ const valid = [];
54
+ const failed = [];
55
+ const seenNames = /* @__PURE__ */ new Set();
56
+ for (let i = 0; i < parsed.providers.length; i++) {
57
+ const entry = parsed.providers[i];
58
+ const validation = validateProviderConfig(entry);
59
+ if (validation.ok) {
60
+ if (seenNames.has(validation.value.name)) {
61
+ failed.push({
62
+ index: i,
63
+ reason: `duplicate provider name '${validation.value.name}' \u2014 first occurrence wins`
64
+ });
65
+ continue;
66
+ }
67
+ seenNames.add(validation.value.name);
68
+ valid.push(validation.value);
69
+ } else {
70
+ failed.push({ index: i, reason: validation.reason });
71
+ }
72
+ }
73
+ return { configs: valid, failed };
74
+ }
75
+ function isMcpProvidersFile(v) {
76
+ if (!v || typeof v !== "object") return false;
77
+ const obj = v;
78
+ return Array.isArray(obj.providers);
79
+ }
80
+ function validateProviderConfig(raw) {
81
+ if (!raw || typeof raw !== "object") {
82
+ return { ok: false, reason: "entry is not an object" };
83
+ }
84
+ const o = raw;
85
+ if (typeof o.name !== "string" || o.name.length === 0) {
86
+ return { ok: false, reason: "`name` must be a non-empty string" };
87
+ }
88
+ if (typeof o.label !== "string" || o.label.length === 0) {
89
+ return { ok: false, reason: `[${o.name}] 'label' must be a non-empty string` };
90
+ }
91
+ if (o.transport !== "stdio" && o.transport !== "http") {
92
+ return {
93
+ ok: false,
94
+ reason: `[${o.name}] 'transport' must be 'stdio' or 'http'`
95
+ };
96
+ }
97
+ if (!Array.isArray(o.tools)) {
98
+ return { ok: false, reason: `[${o.name}] 'tools' must be an array` };
99
+ }
100
+ for (let i = 0; i < o.tools.length; i++) {
101
+ const t = o.tools[i];
102
+ if (!t || typeof t.name !== "string" || t.name.length === 0) {
103
+ return {
104
+ ok: false,
105
+ reason: `[${o.name}] tools[${i}].name must be a non-empty string`
106
+ };
107
+ }
108
+ if (t.args !== void 0 && (typeof t.args !== "object" || t.args === null)) {
109
+ return { ok: false, reason: `[${o.name}] tools[${i}].args must be an object` };
110
+ }
111
+ if (t.confidence !== void 0) {
112
+ if (typeof t.confidence !== "number" || t.confidence < 0 || t.confidence > 1) {
113
+ return {
114
+ ok: false,
115
+ reason: `[${o.name}] tools[${i}].confidence must be in [0, 1]`
116
+ };
117
+ }
118
+ }
119
+ }
120
+ if (o.transport === "stdio") {
121
+ if (typeof o.command !== "string" || o.command.length === 0) {
122
+ return { ok: false, reason: `[${o.name}] 'command' required for stdio transport` };
123
+ }
124
+ if (o.args !== void 0 && !Array.isArray(o.args)) {
125
+ return { ok: false, reason: `[${o.name}] 'args' must be an array of strings` };
126
+ }
127
+ } else {
128
+ if (typeof o.url !== "string" || o.url.length === 0) {
129
+ return { ok: false, reason: `[${o.name}] 'url' required for http transport` };
130
+ }
131
+ try {
132
+ new URL(o.url);
133
+ } catch {
134
+ return { ok: false, reason: `[${o.name}] 'url' is not a valid URL` };
135
+ }
136
+ }
137
+ for (const field of ["tokenBudget", "timeoutMs", "cacheTtlSec", "priority"]) {
138
+ if (o[field] !== void 0) {
139
+ if (typeof o[field] !== "number" || o[field] < 0) {
140
+ return {
141
+ ok: false,
142
+ reason: `[${o.name}] '${field}' must be a non-negative number`
143
+ };
144
+ }
145
+ }
146
+ }
147
+ return { ok: true, value: raw };
148
+ }
149
+ function applyArgTemplate(template, ctx) {
150
+ const defaults = { path: "{filePath}" };
151
+ const src = template ?? defaults;
152
+ const out = {};
153
+ const basename = ctx.fileBasename ?? ctx.filePath.split(/[\\/]/).pop() ?? ctx.filePath;
154
+ const tokens = {
155
+ filePath: ctx.filePath,
156
+ projectRoot: ctx.projectRoot,
157
+ imports: ctx.imports.join(","),
158
+ fileBasename: basename
159
+ };
160
+ for (const [key, value] of Object.entries(src)) {
161
+ if (typeof value === "string") {
162
+ out[key] = value.replace(
163
+ /\{(\w+)\}/g,
164
+ (match, token) => Object.prototype.hasOwnProperty.call(tokens, token) ? tokens[token] : match
165
+ );
166
+ } else {
167
+ out[key] = value;
168
+ }
169
+ }
170
+ return out;
171
+ }
172
+
173
+ export {
174
+ getMcpConfigPath,
175
+ loadMcpConfigs,
176
+ validateProviderConfig,
177
+ applyArgTemplate
178
+ };