engramx 2.0.2 → 2.1.0
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/CHANGELOG.md +201 -0
- package/README.md +55 -0
- package/dist/{aider-context-BC5R2ZTA.js → aider-context-J557IHIP.js} +1 -1
- package/dist/check-2Z3MPZEJ.js +12 -0
- package/dist/{chunk-C6GBUOAL.js → chunk-4XA6ENNL.js} +1 -1
- package/dist/{chunk-533LR4I7.js → chunk-G4U3QOOW.js} +13 -97
- package/dist/chunk-RM2TBOVW.js +121 -0
- package/dist/chunk-SMU4WR3D.js +187 -0
- package/dist/chunk-XFE6ZANP.js +99 -0
- package/dist/chunk-XVYE4OX2.js +232 -0
- package/dist/{chunk-SJT7VS2G.js → chunk-ZVWRIVWQ.js} +17 -0
- package/dist/cli.js +584 -317
- package/dist/{core-6IY5L6II.js → core-TSXA5XZH.js} +1 -1
- package/dist/{cursor-mdc-GJ7E5LDD.js → cursor-mdc-VEOFFDVO.js} +1 -1
- package/dist/{exporter-GWU2GF23.js → exporter-AWXS34AS.js} +1 -1
- package/dist/{importer-V62NGZRK.js → importer-3Q5M6QBL.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/install-YVMVCFQW.js +121 -0
- package/dist/notify-5POGKMRX.js +36 -0
- package/dist/report-C3GTM3HY.js +12 -0
- package/dist/serve.js +4 -3
- package/dist/{server-KUG7U6SG.js → server-A6MUVKQK.js} +5 -3
- package/dist/{windsurf-rules-C7SVDHBL.js → windsurf-rules-RWPKBHRD.js} +1 -1
- package/dist/wizard-AOXWMSXW.js +274 -0
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ function buildSection(heading, bullets) {
|
|
|
33
33
|
return [`## ${heading}`, "", ...bullets, ""].join("\n");
|
|
34
34
|
}
|
|
35
35
|
async function generateCursorMdc(projectPath) {
|
|
36
|
-
const { getStore } = await import("./core-
|
|
36
|
+
const { getStore } = await import("./core-TSXA5XZH.js");
|
|
37
37
|
const store = await getStore(projectPath);
|
|
38
38
|
try {
|
|
39
39
|
const allNodes = store.getAllNodes();
|
|
@@ -12,7 +12,7 @@ function buildSection(heading, nodes) {
|
|
|
12
12
|
return [`## ${heading}`, "", ...bullets, ""].join("\n");
|
|
13
13
|
}
|
|
14
14
|
async function exportCcs(projectRoot) {
|
|
15
|
-
const { getStore } = await import("./core-
|
|
15
|
+
const { getStore } = await import("./core-TSXA5XZH.js");
|
|
16
16
|
const store = await getStore(projectRoot);
|
|
17
17
|
try {
|
|
18
18
|
const allNodes = store.getAllNodes();
|
|
@@ -25,7 +25,7 @@ async function importCcs(projectRoot) {
|
|
|
25
25
|
if (!existsSync(filePath)) {
|
|
26
26
|
return { nodesCreated: 0, sectionsFound: 0 };
|
|
27
27
|
}
|
|
28
|
-
const { getStore } = await import("./core-
|
|
28
|
+
const { getStore } = await import("./core-TSXA5XZH.js");
|
|
29
29
|
const store = await getStore(projectRoot);
|
|
30
30
|
try {
|
|
31
31
|
const raw = readFileSync(filePath, "utf-8");
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
generateSummary,
|
|
5
5
|
install,
|
|
6
6
|
uninstall
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4XA6ENNL.js";
|
|
8
8
|
import {
|
|
9
9
|
GraphStore,
|
|
10
10
|
SUPPORTED_EXTENSIONS,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
sliceGraphemeSafe,
|
|
24
24
|
stats,
|
|
25
25
|
truncateGraphemeSafe
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ZVWRIVWQ.js";
|
|
27
27
|
import "./chunk-PEH54LYC.js";
|
|
28
28
|
export {
|
|
29
29
|
GraphStore,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/update/install.ts
|
|
2
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var PACKAGE_NAME = "engramx";
|
|
7
|
+
function detectPackageManager() {
|
|
8
|
+
let installPath = null;
|
|
9
|
+
try {
|
|
10
|
+
installPath = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
} catch {
|
|
12
|
+
installPath = null;
|
|
13
|
+
}
|
|
14
|
+
if (!installPath) {
|
|
15
|
+
return { manager: null, installPath: null, reason: "none" };
|
|
16
|
+
}
|
|
17
|
+
const lower = installPath.toLowerCase();
|
|
18
|
+
if (lower.includes("/pnpm/") || lower.includes("\\pnpm\\")) {
|
|
19
|
+
return { manager: "pnpm", installPath, reason: "pnpm-path-marker" };
|
|
20
|
+
}
|
|
21
|
+
if (lower.includes("/.yarn/") || lower.includes("\\.yarn\\")) {
|
|
22
|
+
return { manager: "yarn", installPath, reason: "yarn-path-marker" };
|
|
23
|
+
}
|
|
24
|
+
if (lower.includes("/bun/") || lower.includes("\\bun\\")) {
|
|
25
|
+
return { manager: "bun", installPath, reason: "bun-path-marker" };
|
|
26
|
+
}
|
|
27
|
+
return { manager: "npm", installPath, reason: "npm-fallback" };
|
|
28
|
+
}
|
|
29
|
+
function upgradeCommand(manager, channel = "latest") {
|
|
30
|
+
const target = `${PACKAGE_NAME}@${channel}`;
|
|
31
|
+
switch (manager) {
|
|
32
|
+
case "npm":
|
|
33
|
+
return { cmd: "npm", args: ["install", "-g", target] };
|
|
34
|
+
case "pnpm":
|
|
35
|
+
return { cmd: "pnpm", args: ["add", "-g", target] };
|
|
36
|
+
case "yarn":
|
|
37
|
+
return { cmd: "yarn", args: ["global", "add", target] };
|
|
38
|
+
case "bun":
|
|
39
|
+
return { cmd: "bun", args: ["add", "-g", target] };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function managerOnPath(manager) {
|
|
43
|
+
try {
|
|
44
|
+
const r = spawnSync(manager, ["--version"], {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
timeout: 2e3
|
|
47
|
+
});
|
|
48
|
+
return r.status === 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runUpgrade(opts = {}) {
|
|
54
|
+
const channel = opts.channel ?? "latest";
|
|
55
|
+
const detected = opts.manager !== void 0 ? { manager: opts.manager, installPath: null, reason: "override" } : detectPackageManager();
|
|
56
|
+
if (detected.manager === null) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
message: "Could not detect how engram was installed. Run your package manager's upgrade manually.",
|
|
60
|
+
executed: null,
|
|
61
|
+
stderrTail: null
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (!managerOnPath(detected.manager)) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
message: `${detected.manager} not found on PATH. Install it or use a different package manager.`,
|
|
68
|
+
executed: null,
|
|
69
|
+
stderrTail: null
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const { cmd, args } = upgradeCommand(detected.manager, channel);
|
|
73
|
+
const executed = `${cmd} ${args.join(" ")}`;
|
|
74
|
+
if (opts.dryRun) {
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
message: `Would run: ${executed}`,
|
|
78
|
+
executed,
|
|
79
|
+
stderrTail: null
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
execFileSync(cmd, args, {
|
|
84
|
+
stdio: "inherit",
|
|
85
|
+
timeout: 12e4
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
message: `Upgrade complete via ${detected.manager}.`,
|
|
90
|
+
executed,
|
|
91
|
+
stderrTail: null
|
|
92
|
+
};
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
95
|
+
const tail = msg.split("\n").slice(-5).join("\n");
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
message: `Upgrade failed via ${detected.manager}. Try manually: ${executed}`,
|
|
99
|
+
executed,
|
|
100
|
+
stderrTail: tail
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function manualCommand(channel = "latest") {
|
|
105
|
+
return `npm install -g ${PACKAGE_NAME}@${channel}`;
|
|
106
|
+
}
|
|
107
|
+
function safeEnvironment() {
|
|
108
|
+
if (existsSync("/opt/homebrew/bin/engram") || existsSync("/usr/local/Homebrew/bin/engram")) {
|
|
109
|
+
}
|
|
110
|
+
return { ok: true };
|
|
111
|
+
}
|
|
112
|
+
var PACKAGE = PACKAGE_NAME;
|
|
113
|
+
export {
|
|
114
|
+
PACKAGE,
|
|
115
|
+
detectPackageManager,
|
|
116
|
+
managerOnPath,
|
|
117
|
+
manualCommand,
|
|
118
|
+
runUpgrade,
|
|
119
|
+
safeEnvironment,
|
|
120
|
+
upgradeCommand
|
|
121
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cachePath,
|
|
3
|
+
isNewer,
|
|
4
|
+
optedOut
|
|
5
|
+
} from "./chunk-RM2TBOVW.js";
|
|
6
|
+
|
|
7
|
+
// src/update/notify.ts
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
var printedThisProcess = false;
|
|
11
|
+
function maybePrintUpdateHint(currentVersion) {
|
|
12
|
+
if (printedThisProcess) return null;
|
|
13
|
+
if (optedOut()) return null;
|
|
14
|
+
const path = cachePath();
|
|
15
|
+
if (!existsSync(path)) return null;
|
|
16
|
+
let latest = null;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
19
|
+
if (typeof parsed?.latest === "string") latest = parsed.latest;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!latest) return null;
|
|
24
|
+
if (!isNewer(latest, currentVersion)) return null;
|
|
25
|
+
const hint = chalk.dim("\u{1F4A1} ") + chalk.yellow(`engram v${latest}`) + chalk.dim(" is available \u2014 run ") + chalk.white("engram update") + chalk.dim(` (you're on v${currentVersion})`);
|
|
26
|
+
process.stderr.write(hint + "\n");
|
|
27
|
+
printedThisProcess = true;
|
|
28
|
+
return hint;
|
|
29
|
+
}
|
|
30
|
+
function _resetPrintedFlag() {
|
|
31
|
+
printedThisProcess = false;
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
_resetPrintedFlag,
|
|
35
|
+
maybePrintUpdateHint
|
|
36
|
+
};
|
package/dist/serve.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MAX_MISTAKE_LABEL_CHARS,
|
|
4
4
|
benchmark,
|
|
5
|
+
formatThousands,
|
|
5
6
|
godNodes,
|
|
6
7
|
mistakes,
|
|
7
8
|
path,
|
|
8
9
|
query,
|
|
9
10
|
stats,
|
|
10
11
|
truncateGraphemeSafe
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ZVWRIVWQ.js";
|
|
12
13
|
import "./chunk-PEH54LYC.js";
|
|
13
14
|
|
|
14
15
|
// src/serve.ts
|
|
@@ -111,8 +112,8 @@ AMBIGUOUS: ${s.ambiguousPct}%`;
|
|
|
111
112
|
case "benchmark": {
|
|
112
113
|
const b = await benchmark(PROJECT_ROOT);
|
|
113
114
|
return [
|
|
114
|
-
`Full corpus: ~${b.naiveFullCorpus
|
|
115
|
-
`Avg graph query: ~${b.avgQueryTokens
|
|
115
|
+
`Full corpus: ~${formatThousands(b.naiveFullCorpus)} tokens`,
|
|
116
|
+
`Avg graph query: ~${formatThousands(b.avgQueryTokens)} tokens`,
|
|
116
117
|
`Reduction vs full corpus: ${b.reductionVsFull}x`,
|
|
117
118
|
`Reduction vs relevant files: ${b.reductionVsRelevant}x`,
|
|
118
119
|
"",
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
safeEqual
|
|
11
11
|
} from "./chunk-N6PPKOPK.js";
|
|
12
12
|
import {
|
|
13
|
-
getComponentStatus,
|
|
14
13
|
summarizeHookLog
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XFE6ZANP.js";
|
|
15
|
+
import {
|
|
16
|
+
getComponentStatus
|
|
17
|
+
} from "./chunk-G4U3QOOW.js";
|
|
16
18
|
import {
|
|
17
19
|
readHookLog
|
|
18
20
|
} from "./chunk-KL6NSPVA.js";
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
learn,
|
|
22
24
|
query,
|
|
23
25
|
stats
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ZVWRIVWQ.js";
|
|
25
27
|
import "./chunk-PEH54LYC.js";
|
|
26
28
|
|
|
27
29
|
// src/server/http.ts
|
|
@@ -7,7 +7,7 @@ function buildSection(heading, lines) {
|
|
|
7
7
|
return [`## ${heading}`, "", ...lines, ""].join("\n");
|
|
8
8
|
}
|
|
9
9
|
async function generateWindsurfRules(projectRoot) {
|
|
10
|
-
const { getStore } = await import("./core-
|
|
10
|
+
const { getStore } = await import("./core-TSXA5XZH.js");
|
|
11
11
|
const store = await getStore(projectRoot);
|
|
12
12
|
try {
|
|
13
13
|
const allNodes = store.getAllNodes();
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildReport,
|
|
3
|
+
formatReport
|
|
4
|
+
} from "./chunk-XVYE4OX2.js";
|
|
5
|
+
import "./chunk-RM2TBOVW.js";
|
|
6
|
+
import {
|
|
7
|
+
installEngramHooks
|
|
8
|
+
} from "./chunk-SMU4WR3D.js";
|
|
9
|
+
import "./chunk-G4U3QOOW.js";
|
|
10
|
+
import {
|
|
11
|
+
init
|
|
12
|
+
} from "./chunk-ZVWRIVWQ.js";
|
|
13
|
+
import "./chunk-PEH54LYC.js";
|
|
14
|
+
|
|
15
|
+
// src/setup/wizard.ts
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import readline from "readline/promises";
|
|
18
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
19
|
+
import { dirname, join as join2, resolve as pathResolve } from "path";
|
|
20
|
+
import { homedir as homedir2 } from "os";
|
|
21
|
+
|
|
22
|
+
// src/setup/detect.ts
|
|
23
|
+
import { existsSync, readFileSync } from "fs";
|
|
24
|
+
import { join } from "path";
|
|
25
|
+
import { homedir } from "os";
|
|
26
|
+
function detectClaudeCode(projectRoot) {
|
|
27
|
+
const settingsCandidates = [
|
|
28
|
+
join(projectRoot, ".claude", "settings.local.json"),
|
|
29
|
+
join(projectRoot, ".claude", "settings.json"),
|
|
30
|
+
join(homedir(), ".claude", "settings.json")
|
|
31
|
+
];
|
|
32
|
+
const settingsPresent = settingsCandidates.some(existsSync);
|
|
33
|
+
const claudeCliPresent = existsSync(join(homedir(), ".claude")) || existsSync("/usr/local/bin/claude") || existsSync(join(homedir(), ".local/bin/claude"));
|
|
34
|
+
const installed = settingsPresent || claudeCliPresent;
|
|
35
|
+
let configured = false;
|
|
36
|
+
try {
|
|
37
|
+
configured = settingsCandidates.filter(existsSync).some((p) => readFileSync(p, "utf-8").includes("engram intercept"));
|
|
38
|
+
} catch {
|
|
39
|
+
configured = false;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: "Claude Code",
|
|
43
|
+
installed,
|
|
44
|
+
configured,
|
|
45
|
+
status: !installed ? "not detected" : configured ? "Sentinel hook installed" : "detected \u2014 hook not yet installed"
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function detectCursor(projectRoot) {
|
|
49
|
+
const cursorConfigs = [
|
|
50
|
+
join(homedir(), "Library/Application Support/Cursor"),
|
|
51
|
+
join(homedir(), ".config/Cursor"),
|
|
52
|
+
join(homedir(), "AppData/Roaming/Cursor")
|
|
53
|
+
];
|
|
54
|
+
const installed = cursorConfigs.some(existsSync);
|
|
55
|
+
const configured = existsSync(
|
|
56
|
+
join(projectRoot, ".cursor", "rules", "engram-context.mdc")
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
name: "Cursor",
|
|
60
|
+
installed,
|
|
61
|
+
configured,
|
|
62
|
+
status: !installed ? "not detected" : configured ? "MDC adapter present" : "detected \u2014 run `engram gen-mdc`"
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function detectWindsurf(projectRoot) {
|
|
66
|
+
const configured = existsSync(join(projectRoot, ".windsurfrules"));
|
|
67
|
+
return {
|
|
68
|
+
name: "Windsurf",
|
|
69
|
+
installed: configured,
|
|
70
|
+
configured,
|
|
71
|
+
status: configured ? ".windsurfrules present" : "run `engram gen-windsurfrules` to add"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function detectContinue() {
|
|
75
|
+
const path = join(homedir(), ".continue", "config.json");
|
|
76
|
+
const installed = existsSync(path);
|
|
77
|
+
let configured = false;
|
|
78
|
+
if (installed) {
|
|
79
|
+
try {
|
|
80
|
+
configured = readFileSync(path, "utf-8").includes("engram");
|
|
81
|
+
} catch {
|
|
82
|
+
configured = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
name: "Continue.dev",
|
|
87
|
+
installed,
|
|
88
|
+
configured,
|
|
89
|
+
status: !installed ? "not detected" : configured ? "engram configured" : "detected \u2014 add engram MCP server to config"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function detectAider(projectRoot) {
|
|
93
|
+
const configured = existsSync(join(projectRoot, ".aider-context.md"));
|
|
94
|
+
const installed = configured || existsSync(join(homedir(), ".aider"));
|
|
95
|
+
return {
|
|
96
|
+
name: "Aider",
|
|
97
|
+
installed,
|
|
98
|
+
configured,
|
|
99
|
+
status: !installed ? "not detected" : configured ? ".aider-context.md present" : "detected \u2014 run `engram gen-aider`"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function detectAllIdes(projectRoot) {
|
|
103
|
+
return [
|
|
104
|
+
detectClaudeCode(projectRoot),
|
|
105
|
+
detectCursor(projectRoot),
|
|
106
|
+
detectWindsurf(projectRoot),
|
|
107
|
+
detectContinue(),
|
|
108
|
+
detectAider(projectRoot)
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/setup/wizard.ts
|
|
113
|
+
async function ask(rl, question, fallback) {
|
|
114
|
+
const prompt = `${question} ${fallback ? "[Y/n]" : "[y/N]"} `;
|
|
115
|
+
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
116
|
+
if (answer === "") return fallback;
|
|
117
|
+
return answer === "y" || answer === "yes";
|
|
118
|
+
}
|
|
119
|
+
function banner(line) {
|
|
120
|
+
console.log(chalk.bold(line));
|
|
121
|
+
}
|
|
122
|
+
function step(n, title) {
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(chalk.cyan(`\u2500\u2500 step ${n} \xB7 `) + chalk.bold(title));
|
|
125
|
+
}
|
|
126
|
+
function done(msg) {
|
|
127
|
+
console.log(chalk.green(" \u2713 ") + msg);
|
|
128
|
+
}
|
|
129
|
+
function skip(msg) {
|
|
130
|
+
console.log(chalk.dim(" \xB7 ") + chalk.dim(msg));
|
|
131
|
+
}
|
|
132
|
+
function warn(msg) {
|
|
133
|
+
console.log(chalk.yellow(" \u26A0 ") + msg);
|
|
134
|
+
}
|
|
135
|
+
async function ensureGraphInit(opts, rl) {
|
|
136
|
+
const root = pathResolve(opts.projectPath);
|
|
137
|
+
const dbPath = join2(root, ".engram", "graph.db");
|
|
138
|
+
if (existsSync2(dbPath)) {
|
|
139
|
+
skip("graph.db already exists at .engram/graph.db \u2014 skipping init");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (opts.dryRun) {
|
|
143
|
+
skip("[dry-run] would run `engram init`");
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const go = opts.yes || rl === null || await ask(rl, "Index this repository now?", true);
|
|
147
|
+
if (!go) {
|
|
148
|
+
skip("skipped by user");
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
console.log(chalk.dim(" \u2192 running engram init..."));
|
|
152
|
+
const result = await init(root);
|
|
153
|
+
done(
|
|
154
|
+
`${result.nodes} nodes, ${result.edges} edges from ${result.fileCount} files (${result.timeMs}ms)`
|
|
155
|
+
);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
async function ensureHookInstalled(opts, rl) {
|
|
159
|
+
const root = pathResolve(opts.projectPath);
|
|
160
|
+
const scope = opts.settingsScope ?? "local";
|
|
161
|
+
const settingsPath = scope === "user" ? join2(homedir2(), ".claude", "settings.json") : scope === "project" ? join2(root, ".claude", "settings.json") : join2(root, ".claude", "settings.local.json");
|
|
162
|
+
const existing = existsSync2(settingsPath) ? readFileSync2(settingsPath, "utf-8") : "";
|
|
163
|
+
if (existing.includes("engram intercept")) {
|
|
164
|
+
skip(`Sentinel hook already in ${scope}-scope settings`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (opts.dryRun) {
|
|
168
|
+
skip(`[dry-run] would install Sentinel hook (${scope} scope)`);
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const go = opts.yes || rl === null || await ask(rl, `Install Sentinel hook in ${scope} scope?`, true);
|
|
172
|
+
if (!go) {
|
|
173
|
+
skip("skipped by user");
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
let settings = {};
|
|
177
|
+
if (existing) {
|
|
178
|
+
try {
|
|
179
|
+
settings = JSON.parse(existing);
|
|
180
|
+
} catch {
|
|
181
|
+
warn(`settings file at ${settingsPath} is not valid JSON \u2014 aborting`);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const result = installEngramHooks(settings);
|
|
186
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
187
|
+
writeFileSync(settingsPath, JSON.stringify(result.updated, null, 2) + "\n", "utf-8");
|
|
188
|
+
done(`Sentinel hook installed (${scope} scope)`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
async function offerIdeAdapters(opts, rl) {
|
|
192
|
+
const root = pathResolve(opts.projectPath);
|
|
193
|
+
const detected = detectAllIdes(root);
|
|
194
|
+
const installedIdes = detected.filter((d) => d.installed);
|
|
195
|
+
if (installedIdes.length === 0) {
|
|
196
|
+
skip("no IDEs detected beyond Claude Code");
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
console.log(chalk.dim(" Detected:"));
|
|
200
|
+
for (const d of installedIdes) {
|
|
201
|
+
console.log(
|
|
202
|
+
chalk.dim(` \xB7 ${d.name.padEnd(14)} \u2014 ${d.status}`)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (opts.dryRun) {
|
|
206
|
+
skip("[dry-run] adapters left alone");
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
const unconfigured = installedIdes.filter((d) => !d.configured);
|
|
210
|
+
if (unconfigured.length === 0) {
|
|
211
|
+
done("all detected IDEs already have engram adapters");
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const suggest = {
|
|
215
|
+
Cursor: "engram gen-mdc",
|
|
216
|
+
Windsurf: "engram gen-windsurfrules",
|
|
217
|
+
Aider: "engram gen-aider"
|
|
218
|
+
};
|
|
219
|
+
const suggested = [];
|
|
220
|
+
for (const ide of unconfigured) {
|
|
221
|
+
const cmd = suggest[ide.name];
|
|
222
|
+
if (cmd) suggested.push({ name: ide.name, cmd });
|
|
223
|
+
}
|
|
224
|
+
if (suggested.length === 0) {
|
|
225
|
+
const claudeCode = unconfigured.find((d) => d.name === "Claude Code");
|
|
226
|
+
if (claudeCode) {
|
|
227
|
+
skip("Claude Code hook declined or missing \u2014 re-run `engram install-hook`");
|
|
228
|
+
} else {
|
|
229
|
+
done("no additional adapters needed");
|
|
230
|
+
}
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
console.log("");
|
|
234
|
+
console.log(chalk.dim(" Next steps for detected IDEs:"));
|
|
235
|
+
const run = [];
|
|
236
|
+
for (const s of suggested) {
|
|
237
|
+
console.log(chalk.white(` $ ${s.cmd}`));
|
|
238
|
+
run.push(s.name);
|
|
239
|
+
}
|
|
240
|
+
return run;
|
|
241
|
+
}
|
|
242
|
+
async function runSetup(opts) {
|
|
243
|
+
const root = pathResolve(opts.projectPath);
|
|
244
|
+
banner(`
|
|
245
|
+
\u26A1 engram setup \u2014 ${root}`);
|
|
246
|
+
console.log(
|
|
247
|
+
chalk.dim(
|
|
248
|
+
` Running ${opts.yes ? "non-interactively" : "interactively"}${opts.dryRun ? " (dry-run)" : ""}
|
|
249
|
+
`
|
|
250
|
+
)
|
|
251
|
+
);
|
|
252
|
+
const rl = opts.yes || opts.dryRun ? null : readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
253
|
+
let initRan = false;
|
|
254
|
+
let hookInstalled = false;
|
|
255
|
+
let ideAdapters = [];
|
|
256
|
+
try {
|
|
257
|
+
step(1, "graph");
|
|
258
|
+
initRan = await ensureGraphInit(opts, rl);
|
|
259
|
+
step(2, "hook");
|
|
260
|
+
hookInstalled = await ensureHookInstalled(opts, rl);
|
|
261
|
+
step(3, "adapters");
|
|
262
|
+
ideAdapters = await offerIdeAdapters(opts, rl);
|
|
263
|
+
step(4, "verify");
|
|
264
|
+
const report = buildReport(root, opts.engramVersion);
|
|
265
|
+
console.log(formatReport(report, false));
|
|
266
|
+
const exitCode = report.overallSeverity === "ok" ? 0 : report.overallSeverity === "warn" ? 1 : 2;
|
|
267
|
+
return { initRan, hookInstalled, ideAdaptersRun: ideAdapters, exitCode };
|
|
268
|
+
} finally {
|
|
269
|
+
rl?.close();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
export {
|
|
273
|
+
runSetup
|
|
274
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engramx",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The context spine for AI coding agents. 8 providers + pluggable context sources, 3-layer memory cache, web dashboard, multi-IDE support (Claude Code, Cursor, Continue, Zed, Aider, Windsurf, Neovim, Emacs). 88.1% measured session-level token savings. Local SQLite, zero native deps, zero cloud.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|