engrm 0.4.37 → 0.4.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -2
- package/dist/cli.js +446 -62
- package/dist/hooks/elicitation-result.js +57 -1
- package/dist/hooks/post-tool-use.js +57 -1
- package/dist/hooks/pre-compact.js +57 -1
- package/dist/hooks/sentinel.js +20 -0
- package/dist/hooks/session-start.js +82 -12
- package/dist/hooks/stop.js +305 -186
- package/dist/hooks/user-prompt-submit.js +57 -1
- package/dist/server.js +347 -77
- package/opencode/README.md +70 -0
- package/opencode/install-or-update-opencode-plugin.sh +46 -0
- package/opencode/opencode.example.json +14 -0
- package/opencode/plugin/engrm-opencode.js +61 -0
- package/package.json +10 -4
package/dist/cli.js
CHANGED
|
@@ -18,10 +18,10 @@ var __export = (target, all) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
|
-
import { existsSync as
|
|
22
|
-
import { hostname as
|
|
23
|
-
import { dirname as dirname5, join as
|
|
24
|
-
import { createHash as
|
|
21
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, statSync } from "fs";
|
|
22
|
+
import { hostname as hostname3, homedir as homedir5, networkInterfaces as networkInterfaces3 } from "os";
|
|
23
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
24
|
+
import { createHash as createHash4 } from "crypto";
|
|
25
25
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
26
26
|
|
|
27
27
|
// src/config.ts
|
|
@@ -104,6 +104,16 @@ function createDefaultConfig() {
|
|
|
104
104
|
},
|
|
105
105
|
transcript_analysis: {
|
|
106
106
|
enabled: false
|
|
107
|
+
},
|
|
108
|
+
http: {
|
|
109
|
+
enabled: false,
|
|
110
|
+
port: 3767,
|
|
111
|
+
bearer_tokens: []
|
|
112
|
+
},
|
|
113
|
+
fleet: {
|
|
114
|
+
project_name: "shared-experience",
|
|
115
|
+
namespace: "",
|
|
116
|
+
api_key: ""
|
|
107
117
|
}
|
|
108
118
|
};
|
|
109
119
|
}
|
|
@@ -165,6 +175,16 @@ function loadConfig() {
|
|
|
165
175
|
},
|
|
166
176
|
transcript_analysis: {
|
|
167
177
|
enabled: asBool(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
178
|
+
},
|
|
179
|
+
http: {
|
|
180
|
+
enabled: asBool(config["http"]?.["enabled"], defaults.http.enabled),
|
|
181
|
+
port: asNumber(config["http"]?.["port"], defaults.http.port),
|
|
182
|
+
bearer_tokens: asStringArray(config["http"]?.["bearer_tokens"], defaults.http.bearer_tokens)
|
|
183
|
+
},
|
|
184
|
+
fleet: {
|
|
185
|
+
project_name: asString(config["fleet"]?.["project_name"], defaults.fleet.project_name),
|
|
186
|
+
namespace: asString(config["fleet"]?.["namespace"], defaults.fleet.namespace),
|
|
187
|
+
api_key: asString(config["fleet"]?.["api_key"], defaults.fleet.api_key)
|
|
168
188
|
}
|
|
169
189
|
};
|
|
170
190
|
}
|
|
@@ -2583,7 +2603,7 @@ function errorPage(message) {
|
|
|
2583
2603
|
}
|
|
2584
2604
|
|
|
2585
2605
|
// src/register.ts
|
|
2586
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2606
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, copyFileSync } from "node:fs";
|
|
2587
2607
|
import { homedir as homedir2 } from "node:os";
|
|
2588
2608
|
import { join as join2, dirname } from "node:path";
|
|
2589
2609
|
import { fileURLToPath } from "node:url";
|
|
@@ -2591,6 +2611,8 @@ var CLAUDE_JSON = join2(homedir2(), ".claude.json");
|
|
|
2591
2611
|
var CLAUDE_SETTINGS = join2(homedir2(), ".claude", "settings.json");
|
|
2592
2612
|
var CODEX_CONFIG = join2(homedir2(), ".codex", "config.toml");
|
|
2593
2613
|
var CODEX_HOOKS = join2(homedir2(), ".codex", "hooks.json");
|
|
2614
|
+
var OPENCODE_CONFIG = join2(homedir2(), ".config", "opencode", "opencode.json");
|
|
2615
|
+
var OPENCODE_PLUGIN = join2(homedir2(), ".config", "opencode", "plugins", "engrm.js");
|
|
2594
2616
|
var LEGACY_CODEX_SERVER_NAME = `candengo-${"mem"}`;
|
|
2595
2617
|
function isBuiltDist() {
|
|
2596
2618
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -2780,6 +2802,26 @@ function registerCodexHooks() {
|
|
|
2780
2802
|
writeFileSync2(CODEX_HOOKS, content, "utf-8");
|
|
2781
2803
|
return { path: CODEX_HOOKS, added: true };
|
|
2782
2804
|
}
|
|
2805
|
+
function registerOpenCode() {
|
|
2806
|
+
const root = findPackageRoot();
|
|
2807
|
+
const pluginSource = join2(root, "opencode", "plugin", "engrm-opencode.js");
|
|
2808
|
+
const config = readJsonFile(OPENCODE_CONFIG);
|
|
2809
|
+
const mcp = config["mcp"] ?? {};
|
|
2810
|
+
mcp["engrm"] = {
|
|
2811
|
+
type: "local",
|
|
2812
|
+
command: ["engrm", "serve"],
|
|
2813
|
+
enabled: true,
|
|
2814
|
+
timeout: 5000
|
|
2815
|
+
};
|
|
2816
|
+
config["$schema"] = "https://opencode.ai/config.json";
|
|
2817
|
+
config["mcp"] = mcp;
|
|
2818
|
+
writeJsonFile(OPENCODE_CONFIG, config);
|
|
2819
|
+
ensureParentDir(OPENCODE_PLUGIN);
|
|
2820
|
+
if (existsSync2(pluginSource)) {
|
|
2821
|
+
copyFileSync(pluginSource, OPENCODE_PLUGIN);
|
|
2822
|
+
}
|
|
2823
|
+
return { path: OPENCODE_CONFIG, added: true, pluginPath: OPENCODE_PLUGIN };
|
|
2824
|
+
}
|
|
2783
2825
|
function registerHooks() {
|
|
2784
2826
|
const runtime = findRuntime();
|
|
2785
2827
|
const root = findPackageRoot();
|
|
@@ -2830,6 +2872,7 @@ function registerAll() {
|
|
|
2830
2872
|
let hooks = { path: CLAUDE_SETTINGS, added: false };
|
|
2831
2873
|
let codex = { path: CODEX_CONFIG, added: false };
|
|
2832
2874
|
let codexHooks = { path: CODEX_HOOKS, added: false };
|
|
2875
|
+
let opencode = { path: OPENCODE_CONFIG, added: false, pluginPath: OPENCODE_PLUGIN };
|
|
2833
2876
|
try {
|
|
2834
2877
|
mcp = registerMcpServer();
|
|
2835
2878
|
} catch {}
|
|
@@ -2842,11 +2885,15 @@ function registerAll() {
|
|
|
2842
2885
|
try {
|
|
2843
2886
|
codexHooks = registerCodexHooks();
|
|
2844
2887
|
} catch {}
|
|
2888
|
+
try {
|
|
2889
|
+
opencode = registerOpenCode();
|
|
2890
|
+
} catch {}
|
|
2845
2891
|
return {
|
|
2846
2892
|
mcp,
|
|
2847
2893
|
hooks,
|
|
2848
2894
|
codex,
|
|
2849
|
-
codexHooks
|
|
2895
|
+
codexHooks,
|
|
2896
|
+
opencode
|
|
2850
2897
|
};
|
|
2851
2898
|
}
|
|
2852
2899
|
|
|
@@ -2990,6 +3037,12 @@ function containsSecrets(text, customPatterns = []) {
|
|
|
2990
3037
|
}
|
|
2991
3038
|
return false;
|
|
2992
3039
|
}
|
|
3040
|
+
var FLEET_HOSTNAME_PATTERN = /\b(?=.{1,253}\b)(?!-)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}\b/gi;
|
|
3041
|
+
var FLEET_IP_PATTERN = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
|
|
3042
|
+
var FLEET_MAC_PATTERN = /\b(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}\b/gi;
|
|
3043
|
+
function scrubFleetIdentifiers(text) {
|
|
3044
|
+
return text.replace(FLEET_MAC_PATTERN, "[REDACTED_MAC]").replace(FLEET_IP_PATTERN, "[REDACTED_IP]").replace(FLEET_HOSTNAME_PATTERN, "[REDACTED_HOSTNAME]");
|
|
3045
|
+
}
|
|
2993
3046
|
|
|
2994
3047
|
// src/capture/quality.ts
|
|
2995
3048
|
var QUALITY_THRESHOLD = 0.1;
|
|
@@ -3577,6 +3630,35 @@ function narrativesConflict(narrative1, narrative2) {
|
|
|
3577
3630
|
return null;
|
|
3578
3631
|
}
|
|
3579
3632
|
|
|
3633
|
+
// src/sync/targets.ts
|
|
3634
|
+
function isFleetProjectName(projectName, config) {
|
|
3635
|
+
const fleetProjectName = config.fleet?.project_name ?? "shared-experience";
|
|
3636
|
+
if (!projectName || !fleetProjectName)
|
|
3637
|
+
return false;
|
|
3638
|
+
return projectName.trim().toLowerCase() === fleetProjectName.trim().toLowerCase();
|
|
3639
|
+
}
|
|
3640
|
+
function hasFleetTarget(config) {
|
|
3641
|
+
return Boolean(config.fleet?.namespace?.trim() && config.fleet?.api_key?.trim() && (config.fleet?.project_name ?? "shared-experience").trim());
|
|
3642
|
+
}
|
|
3643
|
+
function resolveSyncTarget(config, projectName) {
|
|
3644
|
+
if (isFleetProjectName(projectName, config) && hasFleetTarget(config)) {
|
|
3645
|
+
return {
|
|
3646
|
+
key: `fleet:${config.fleet.namespace}`,
|
|
3647
|
+
apiKey: config.fleet.api_key,
|
|
3648
|
+
namespace: config.fleet.namespace,
|
|
3649
|
+
siteId: config.site_id,
|
|
3650
|
+
isFleet: true
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
return {
|
|
3654
|
+
key: `default:${config.namespace}`,
|
|
3655
|
+
apiKey: config.candengo_api_key,
|
|
3656
|
+
namespace: config.namespace,
|
|
3657
|
+
siteId: config.site_id,
|
|
3658
|
+
isFleet: false
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3580
3662
|
// src/tools/save.ts
|
|
3581
3663
|
var VALID_TYPES = [
|
|
3582
3664
|
"bugfix",
|
|
@@ -3625,7 +3707,8 @@ async function saveObservation(db, config, input) {
|
|
|
3625
3707
|
const factsJson = structuredFacts.length > 0 ? config.scrubbing.enabled ? scrubSecrets(JSON.stringify(structuredFacts), customPatterns) : JSON.stringify(structuredFacts) : null;
|
|
3626
3708
|
const filesReadJson = filesRead ? JSON.stringify(filesRead) : null;
|
|
3627
3709
|
const filesModifiedJson = filesModified ? JSON.stringify(filesModified) : null;
|
|
3628
|
-
|
|
3710
|
+
const fleetProject = isFleetProjectName(project.name, config);
|
|
3711
|
+
let sensitivity = input.sensitivity ?? (fleetProject ? "shared" : config.scrubbing.default_sensitivity);
|
|
3629
3712
|
if (config.scrubbing.enabled && containsSecrets([input.title, input.narrative, JSON.stringify(input.facts)].filter(Boolean).join(" "), customPatterns)) {
|
|
3630
3713
|
if (sensitivity === "shared") {
|
|
3631
3714
|
sensitivity = "personal";
|
|
@@ -3835,26 +3918,250 @@ async function installRulePacks(db, config, packNames) {
|
|
|
3835
3918
|
}
|
|
3836
3919
|
|
|
3837
3920
|
// src/tools/capture-status.ts
|
|
3838
|
-
import { existsSync as
|
|
3839
|
-
import { homedir as
|
|
3921
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
3922
|
+
import { homedir as homedir4 } from "node:os";
|
|
3923
|
+
import { join as join7 } from "node:path";
|
|
3924
|
+
|
|
3925
|
+
// src/config.ts
|
|
3926
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3927
|
+
import { homedir as homedir3, hostname as hostname2, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
3840
3928
|
import { join as join6 } from "node:path";
|
|
3929
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3930
|
+
var CONFIG_DIR2 = join6(homedir3(), ".engrm");
|
|
3931
|
+
var SETTINGS_PATH2 = join6(CONFIG_DIR2, "settings.json");
|
|
3932
|
+
var DB_PATH2 = join6(CONFIG_DIR2, "engrm.db");
|
|
3933
|
+
function getDbPath2() {
|
|
3934
|
+
return DB_PATH2;
|
|
3935
|
+
}
|
|
3936
|
+
function generateDeviceId2() {
|
|
3937
|
+
const host = hostname2().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
3938
|
+
let mac = "";
|
|
3939
|
+
const ifaces = networkInterfaces2();
|
|
3940
|
+
for (const entries of Object.values(ifaces)) {
|
|
3941
|
+
if (!entries)
|
|
3942
|
+
continue;
|
|
3943
|
+
for (const entry of entries) {
|
|
3944
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
3945
|
+
mac = entry.mac;
|
|
3946
|
+
break;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
if (mac)
|
|
3950
|
+
break;
|
|
3951
|
+
}
|
|
3952
|
+
const material = `${host}:${mac || "no-mac"}`;
|
|
3953
|
+
const suffix = createHash3("sha256").update(material).digest("hex").slice(0, 8);
|
|
3954
|
+
return `${host}-${suffix}`;
|
|
3955
|
+
}
|
|
3956
|
+
function createDefaultConfig2() {
|
|
3957
|
+
return {
|
|
3958
|
+
candengo_url: "",
|
|
3959
|
+
candengo_api_key: "",
|
|
3960
|
+
site_id: "",
|
|
3961
|
+
namespace: "",
|
|
3962
|
+
user_id: "",
|
|
3963
|
+
user_email: "",
|
|
3964
|
+
device_id: generateDeviceId2(),
|
|
3965
|
+
teams: [],
|
|
3966
|
+
sync: {
|
|
3967
|
+
enabled: true,
|
|
3968
|
+
interval_seconds: 30,
|
|
3969
|
+
batch_size: 50
|
|
3970
|
+
},
|
|
3971
|
+
search: {
|
|
3972
|
+
default_limit: 10,
|
|
3973
|
+
local_boost: 1.2,
|
|
3974
|
+
scope: "all"
|
|
3975
|
+
},
|
|
3976
|
+
scrubbing: {
|
|
3977
|
+
enabled: true,
|
|
3978
|
+
custom_patterns: [],
|
|
3979
|
+
default_sensitivity: "shared"
|
|
3980
|
+
},
|
|
3981
|
+
sentinel: {
|
|
3982
|
+
enabled: false,
|
|
3983
|
+
mode: "advisory",
|
|
3984
|
+
provider: "openai",
|
|
3985
|
+
model: "gpt-4o-mini",
|
|
3986
|
+
api_key: "",
|
|
3987
|
+
base_url: "",
|
|
3988
|
+
skip_patterns: [],
|
|
3989
|
+
daily_limit: 100,
|
|
3990
|
+
tier: "free"
|
|
3991
|
+
},
|
|
3992
|
+
observer: {
|
|
3993
|
+
enabled: true,
|
|
3994
|
+
mode: "per_event",
|
|
3995
|
+
model: "sonnet"
|
|
3996
|
+
},
|
|
3997
|
+
transcript_analysis: {
|
|
3998
|
+
enabled: false
|
|
3999
|
+
},
|
|
4000
|
+
http: {
|
|
4001
|
+
enabled: false,
|
|
4002
|
+
port: 3767,
|
|
4003
|
+
bearer_tokens: []
|
|
4004
|
+
},
|
|
4005
|
+
fleet: {
|
|
4006
|
+
project_name: "shared-experience",
|
|
4007
|
+
namespace: "",
|
|
4008
|
+
api_key: ""
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
function loadConfig2() {
|
|
4013
|
+
if (!existsSync6(SETTINGS_PATH2)) {
|
|
4014
|
+
throw new Error(`Config not found at ${SETTINGS_PATH2}. Run 'engrm init --manual' to configure.`);
|
|
4015
|
+
}
|
|
4016
|
+
const raw = readFileSync6(SETTINGS_PATH2, "utf-8");
|
|
4017
|
+
let parsed;
|
|
4018
|
+
try {
|
|
4019
|
+
parsed = JSON.parse(raw);
|
|
4020
|
+
} catch {
|
|
4021
|
+
throw new Error(`Invalid JSON in ${SETTINGS_PATH2}`);
|
|
4022
|
+
}
|
|
4023
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
4024
|
+
throw new Error(`Config at ${SETTINGS_PATH2} is not a JSON object`);
|
|
4025
|
+
}
|
|
4026
|
+
const config = parsed;
|
|
4027
|
+
const defaults = createDefaultConfig2();
|
|
4028
|
+
return {
|
|
4029
|
+
candengo_url: asString2(config["candengo_url"], defaults.candengo_url),
|
|
4030
|
+
candengo_api_key: asString2(config["candengo_api_key"], defaults.candengo_api_key),
|
|
4031
|
+
site_id: asString2(config["site_id"], defaults.site_id),
|
|
4032
|
+
namespace: asString2(config["namespace"], defaults.namespace),
|
|
4033
|
+
user_id: asString2(config["user_id"], defaults.user_id),
|
|
4034
|
+
user_email: asString2(config["user_email"], defaults.user_email),
|
|
4035
|
+
device_id: asString2(config["device_id"], defaults.device_id),
|
|
4036
|
+
teams: asTeams2(config["teams"], defaults.teams),
|
|
4037
|
+
sync: {
|
|
4038
|
+
enabled: asBool2(config["sync"]?.["enabled"], defaults.sync.enabled),
|
|
4039
|
+
interval_seconds: asNumber2(config["sync"]?.["interval_seconds"], defaults.sync.interval_seconds),
|
|
4040
|
+
batch_size: asNumber2(config["sync"]?.["batch_size"], defaults.sync.batch_size)
|
|
4041
|
+
},
|
|
4042
|
+
search: {
|
|
4043
|
+
default_limit: asNumber2(config["search"]?.["default_limit"], defaults.search.default_limit),
|
|
4044
|
+
local_boost: asNumber2(config["search"]?.["local_boost"], defaults.search.local_boost),
|
|
4045
|
+
scope: asScope2(config["search"]?.["scope"], defaults.search.scope)
|
|
4046
|
+
},
|
|
4047
|
+
scrubbing: {
|
|
4048
|
+
enabled: asBool2(config["scrubbing"]?.["enabled"], defaults.scrubbing.enabled),
|
|
4049
|
+
custom_patterns: asStringArray2(config["scrubbing"]?.["custom_patterns"], defaults.scrubbing.custom_patterns),
|
|
4050
|
+
default_sensitivity: asSensitivity2(config["scrubbing"]?.["default_sensitivity"], defaults.scrubbing.default_sensitivity)
|
|
4051
|
+
},
|
|
4052
|
+
sentinel: {
|
|
4053
|
+
enabled: asBool2(config["sentinel"]?.["enabled"], defaults.sentinel.enabled),
|
|
4054
|
+
mode: asSentinelMode2(config["sentinel"]?.["mode"], defaults.sentinel.mode),
|
|
4055
|
+
provider: asLlmProvider2(config["sentinel"]?.["provider"], defaults.sentinel.provider),
|
|
4056
|
+
model: asString2(config["sentinel"]?.["model"], defaults.sentinel.model),
|
|
4057
|
+
api_key: asString2(config["sentinel"]?.["api_key"], defaults.sentinel.api_key),
|
|
4058
|
+
base_url: asString2(config["sentinel"]?.["base_url"], defaults.sentinel.base_url),
|
|
4059
|
+
skip_patterns: asStringArray2(config["sentinel"]?.["skip_patterns"], defaults.sentinel.skip_patterns),
|
|
4060
|
+
daily_limit: asNumber2(config["sentinel"]?.["daily_limit"], defaults.sentinel.daily_limit),
|
|
4061
|
+
tier: asTier2(config["sentinel"]?.["tier"], defaults.sentinel.tier)
|
|
4062
|
+
},
|
|
4063
|
+
observer: {
|
|
4064
|
+
enabled: asBool2(config["observer"]?.["enabled"], defaults.observer.enabled),
|
|
4065
|
+
mode: asObserverMode2(config["observer"]?.["mode"], defaults.observer.mode),
|
|
4066
|
+
model: asString2(config["observer"]?.["model"], defaults.observer.model)
|
|
4067
|
+
},
|
|
4068
|
+
transcript_analysis: {
|
|
4069
|
+
enabled: asBool2(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
4070
|
+
},
|
|
4071
|
+
http: {
|
|
4072
|
+
enabled: asBool2(config["http"]?.["enabled"], defaults.http.enabled),
|
|
4073
|
+
port: asNumber2(config["http"]?.["port"], defaults.http.port),
|
|
4074
|
+
bearer_tokens: asStringArray2(config["http"]?.["bearer_tokens"], defaults.http.bearer_tokens)
|
|
4075
|
+
},
|
|
4076
|
+
fleet: {
|
|
4077
|
+
project_name: asString2(config["fleet"]?.["project_name"], defaults.fleet.project_name),
|
|
4078
|
+
namespace: asString2(config["fleet"]?.["namespace"], defaults.fleet.namespace),
|
|
4079
|
+
api_key: asString2(config["fleet"]?.["api_key"], defaults.fleet.api_key)
|
|
4080
|
+
}
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
function saveConfig2(config) {
|
|
4084
|
+
if (!existsSync6(CONFIG_DIR2)) {
|
|
4085
|
+
mkdirSync3(CONFIG_DIR2, { recursive: true });
|
|
4086
|
+
}
|
|
4087
|
+
writeFileSync3(SETTINGS_PATH2, JSON.stringify(config, null, 2) + `
|
|
4088
|
+
`, "utf-8");
|
|
4089
|
+
}
|
|
4090
|
+
function configExists2() {
|
|
4091
|
+
return existsSync6(SETTINGS_PATH2);
|
|
4092
|
+
}
|
|
4093
|
+
function asString2(value, fallback) {
|
|
4094
|
+
return typeof value === "string" ? value : fallback;
|
|
4095
|
+
}
|
|
4096
|
+
function asNumber2(value, fallback) {
|
|
4097
|
+
return typeof value === "number" && !Number.isNaN(value) ? value : fallback;
|
|
4098
|
+
}
|
|
4099
|
+
function asBool2(value, fallback) {
|
|
4100
|
+
return typeof value === "boolean" ? value : fallback;
|
|
4101
|
+
}
|
|
4102
|
+
function asStringArray2(value, fallback) {
|
|
4103
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? value : fallback;
|
|
4104
|
+
}
|
|
4105
|
+
function asScope2(value, fallback) {
|
|
4106
|
+
if (value === "personal" || value === "team" || value === "all")
|
|
4107
|
+
return value;
|
|
4108
|
+
return fallback;
|
|
4109
|
+
}
|
|
4110
|
+
function asSensitivity2(value, fallback) {
|
|
4111
|
+
if (value === "shared" || value === "personal" || value === "secret")
|
|
4112
|
+
return value;
|
|
4113
|
+
return fallback;
|
|
4114
|
+
}
|
|
4115
|
+
function asSentinelMode2(value, fallback) {
|
|
4116
|
+
if (value === "advisory" || value === "blocking")
|
|
4117
|
+
return value;
|
|
4118
|
+
return fallback;
|
|
4119
|
+
}
|
|
4120
|
+
function asLlmProvider2(value, fallback) {
|
|
4121
|
+
if (value === "openai" || value === "anthropic" || value === "ollama" || value === "custom")
|
|
4122
|
+
return value;
|
|
4123
|
+
return fallback;
|
|
4124
|
+
}
|
|
4125
|
+
function asTier2(value, fallback) {
|
|
4126
|
+
if (value === "free" || value === "vibe" || value === "solo" || value === "pro" || value === "team" || value === "enterprise")
|
|
4127
|
+
return value;
|
|
4128
|
+
return fallback;
|
|
4129
|
+
}
|
|
4130
|
+
function asObserverMode2(value, fallback) {
|
|
4131
|
+
if (value === "per_event" || value === "per_session")
|
|
4132
|
+
return value;
|
|
4133
|
+
return fallback;
|
|
4134
|
+
}
|
|
4135
|
+
function asTeams2(value, fallback) {
|
|
4136
|
+
if (!Array.isArray(value))
|
|
4137
|
+
return fallback;
|
|
4138
|
+
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
// src/tools/capture-status.ts
|
|
3841
4142
|
var LEGACY_CODEX_SERVER_NAME2 = `candengo-${"mem"}`;
|
|
3842
4143
|
function getCaptureStatus(db, input = {}) {
|
|
3843
4144
|
const hours = Math.max(1, Math.min(input.lookback_hours ?? 24, 24 * 30));
|
|
3844
4145
|
const sinceEpoch = Math.floor(Date.now() / 1000) - hours * 3600;
|
|
3845
|
-
const home = input.home_dir ??
|
|
3846
|
-
const claudeJson =
|
|
3847
|
-
const claudeSettings =
|
|
3848
|
-
const codexConfig =
|
|
3849
|
-
const codexHooks =
|
|
3850
|
-
const
|
|
3851
|
-
const
|
|
3852
|
-
const
|
|
3853
|
-
const
|
|
4146
|
+
const home = input.home_dir ?? homedir4();
|
|
4147
|
+
const claudeJson = join7(home, ".claude.json");
|
|
4148
|
+
const claudeSettings = join7(home, ".claude", "settings.json");
|
|
4149
|
+
const codexConfig = join7(home, ".codex", "config.toml");
|
|
4150
|
+
const codexHooks = join7(home, ".codex", "hooks.json");
|
|
4151
|
+
const opencodeConfig = join7(home, ".config", "opencode", "opencode.json");
|
|
4152
|
+
const opencodePlugin = join7(home, ".config", "opencode", "plugins", "engrm.js");
|
|
4153
|
+
const config = configExists2() ? loadConfig2() : null;
|
|
4154
|
+
const claudeJsonContent = existsSync7(claudeJson) ? readFileSync7(claudeJson, "utf-8") : "";
|
|
4155
|
+
const claudeSettingsContent = existsSync7(claudeSettings) ? readFileSync7(claudeSettings, "utf-8") : "";
|
|
4156
|
+
const codexConfigContent = existsSync7(codexConfig) ? readFileSync7(codexConfig, "utf-8") : "";
|
|
4157
|
+
const codexHooksContent = existsSync7(codexHooks) ? readFileSync7(codexHooks, "utf-8") : "";
|
|
4158
|
+
const opencodeConfigContent = existsSync7(opencodeConfig) ? readFileSync7(opencodeConfig, "utf-8") : "";
|
|
3854
4159
|
const claudeMcpRegistered = claudeJsonContent.includes('"engrm"');
|
|
3855
4160
|
const claudeHooksRegistered = claudeSettingsContent.includes("engrm") || claudeSettingsContent.includes("session-start") || claudeSettingsContent.includes("user-prompt-submit");
|
|
3856
4161
|
const codexMcpRegistered = codexConfigContent.includes("[mcp_servers.engrm]") || codexConfigContent.includes(`[mcp_servers.${LEGACY_CODEX_SERVER_NAME2}]`);
|
|
3857
4162
|
const codexHooksRegistered = codexHooksContent.includes('"SessionStart"') && codexHooksContent.includes('"Stop"');
|
|
4163
|
+
const opencodeMcpRegistered = opencodeConfigContent.includes('"engrm"') && opencodeConfigContent.includes('"type"') && opencodeConfigContent.includes('"local"');
|
|
4164
|
+
const opencodePluginRegistered = existsSync7(opencodePlugin);
|
|
3858
4165
|
let claudeHookCount = 0;
|
|
3859
4166
|
let claudeSessionStartHook = false;
|
|
3860
4167
|
let claudeUserPromptHook = false;
|
|
@@ -3927,6 +4234,11 @@ function getCaptureStatus(db, input = {}) {
|
|
|
3927
4234
|
return {
|
|
3928
4235
|
schema_version: schemaVersion,
|
|
3929
4236
|
schema_current: schemaVersion >= LATEST_SCHEMA_VERSION,
|
|
4237
|
+
http_enabled: Boolean(config?.http?.enabled || process.env.ENGRM_HTTP_PORT),
|
|
4238
|
+
http_port: config?.http?.port ?? (process.env.ENGRM_HTTP_PORT ? Number(process.env.ENGRM_HTTP_PORT) : null),
|
|
4239
|
+
http_bearer_token_count: config?.http?.bearer_tokens?.length ?? 0,
|
|
4240
|
+
fleet_project_name: config?.fleet?.project_name ?? null,
|
|
4241
|
+
fleet_configured: Boolean(config?.fleet?.namespace && config?.fleet?.api_key),
|
|
3930
4242
|
claude_mcp_registered: claudeMcpRegistered,
|
|
3931
4243
|
claude_hooks_registered: claudeHooksRegistered,
|
|
3932
4244
|
claude_hook_count: claudeHookCount,
|
|
@@ -3939,6 +4251,8 @@ function getCaptureStatus(db, input = {}) {
|
|
|
3939
4251
|
codex_session_start_hook: codexSessionStartHook,
|
|
3940
4252
|
codex_stop_hook: codexStopHook,
|
|
3941
4253
|
codex_raw_chronology_supported: false,
|
|
4254
|
+
opencode_mcp_registered: opencodeMcpRegistered,
|
|
4255
|
+
opencode_plugin_registered: opencodePluginRegistered,
|
|
3942
4256
|
recent_user_prompts: recentUserPrompts,
|
|
3943
4257
|
recent_tool_events: recentToolEvents,
|
|
3944
4258
|
recent_sessions_with_raw_capture: recentSessionsWithRawCapture,
|
|
@@ -4096,7 +4410,7 @@ async function initWithToken(baseUrl, token) {
|
|
|
4096
4410
|
try {
|
|
4097
4411
|
const result = await provision(baseUrl, {
|
|
4098
4412
|
token,
|
|
4099
|
-
device_name:
|
|
4413
|
+
device_name: hostname3()
|
|
4100
4414
|
});
|
|
4101
4415
|
writeConfigFromProvision(baseUrl, result);
|
|
4102
4416
|
console.log(`
|
|
@@ -4122,7 +4436,7 @@ async function initWithBrowser(baseUrl) {
|
|
|
4122
4436
|
console.log("Exchanging authorization code...");
|
|
4123
4437
|
const result = await provision(baseUrl, {
|
|
4124
4438
|
code,
|
|
4125
|
-
device_name:
|
|
4439
|
+
device_name: hostname3()
|
|
4126
4440
|
});
|
|
4127
4441
|
writeConfigFromProvision(baseUrl, result);
|
|
4128
4442
|
console.log(`
|
|
@@ -4159,7 +4473,7 @@ function writeConfigFromProvision(baseUrl, result) {
|
|
|
4159
4473
|
namespace: result.namespace,
|
|
4160
4474
|
user_id: result.user_id,
|
|
4161
4475
|
user_email: result.user_email,
|
|
4162
|
-
device_id: existingDeviceId ||
|
|
4476
|
+
device_id: existingDeviceId || generateDeviceId3(),
|
|
4163
4477
|
teams: result.teams ?? [],
|
|
4164
4478
|
sync: {
|
|
4165
4479
|
enabled: true,
|
|
@@ -4203,13 +4517,13 @@ function writeConfigFromProvision(baseUrl, result) {
|
|
|
4203
4517
|
console.log(`Database initialised at ${getDbPath()}`);
|
|
4204
4518
|
}
|
|
4205
4519
|
function initFromFile(configPath) {
|
|
4206
|
-
if (!
|
|
4520
|
+
if (!existsSync8(configPath)) {
|
|
4207
4521
|
console.error(`Config file not found: ${configPath}`);
|
|
4208
4522
|
process.exit(1);
|
|
4209
4523
|
}
|
|
4210
4524
|
let parsed;
|
|
4211
4525
|
try {
|
|
4212
|
-
const raw =
|
|
4526
|
+
const raw = readFileSync8(configPath, "utf-8");
|
|
4213
4527
|
parsed = JSON.parse(raw);
|
|
4214
4528
|
} catch {
|
|
4215
4529
|
console.error(`Invalid JSON in ${configPath}`);
|
|
@@ -4241,7 +4555,7 @@ function initFromFile(configPath) {
|
|
|
4241
4555
|
namespace: input["namespace"].trim(),
|
|
4242
4556
|
user_id: input["user_id"].trim(),
|
|
4243
4557
|
user_email: typeof input["user_email"] === "string" ? input["user_email"].trim() : "",
|
|
4244
|
-
device_id: typeof input["device_id"] === "string" ? input["device_id"] :
|
|
4558
|
+
device_id: typeof input["device_id"] === "string" ? input["device_id"] : generateDeviceId3(),
|
|
4245
4559
|
teams: [],
|
|
4246
4560
|
sync: {
|
|
4247
4561
|
enabled: true,
|
|
@@ -4314,7 +4628,7 @@ async function initManual() {
|
|
|
4314
4628
|
namespace: namespace.trim(),
|
|
4315
4629
|
user_id: userId.trim(),
|
|
4316
4630
|
user_email: userEmail.trim(),
|
|
4317
|
-
device_id:
|
|
4631
|
+
device_id: generateDeviceId3(),
|
|
4318
4632
|
teams: [],
|
|
4319
4633
|
sync: {
|
|
4320
4634
|
enabled: true,
|
|
@@ -4391,17 +4705,26 @@ function handleStatus() {
|
|
|
4391
4705
|
Integration`);
|
|
4392
4706
|
console.log(` Server: ${config.candengo_url ? normalizeBaseUrl(config.candengo_url) : "(not set)"}`);
|
|
4393
4707
|
console.log(` Sync: ${config.sync.enabled ? "enabled" : "disabled"}`);
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
const
|
|
4399
|
-
const
|
|
4400
|
-
const
|
|
4401
|
-
const
|
|
4708
|
+
console.log(` HTTP MCP: ${config.http.enabled ? `enabled (:${config.http.port})` : "disabled"}`);
|
|
4709
|
+
console.log(` HTTP tokens: ${config.http.bearer_tokens.length}`);
|
|
4710
|
+
console.log(` Fleet project: ${config.fleet.project_name || "(not set)"}`);
|
|
4711
|
+
console.log(` Fleet sync: ${config.fleet.namespace && config.fleet.api_key ? "configured" : "not configured"}`);
|
|
4712
|
+
const claudeJson = join8(homedir5(), ".claude.json");
|
|
4713
|
+
const claudeSettings = join8(homedir5(), ".claude", "settings.json");
|
|
4714
|
+
const codexConfig = join8(homedir5(), ".codex", "config.toml");
|
|
4715
|
+
const codexHooks = join8(homedir5(), ".codex", "hooks.json");
|
|
4716
|
+
const opencodeConfig = join8(homedir5(), ".config", "opencode", "opencode.json");
|
|
4717
|
+
const opencodePlugin = join8(homedir5(), ".config", "opencode", "plugins", "engrm.js");
|
|
4718
|
+
const mcpRegistered = existsSync8(claudeJson) && readFileSync8(claudeJson, "utf-8").includes('"engrm"');
|
|
4719
|
+
const settingsContent = existsSync8(claudeSettings) ? readFileSync8(claudeSettings, "utf-8") : "";
|
|
4720
|
+
const codexContent = existsSync8(codexConfig) ? readFileSync8(codexConfig, "utf-8") : "";
|
|
4721
|
+
const codexHooksContent = existsSync8(codexHooks) ? readFileSync8(codexHooks, "utf-8") : "";
|
|
4722
|
+
const opencodeConfigContent = existsSync8(opencodeConfig) ? readFileSync8(opencodeConfig, "utf-8") : "";
|
|
4402
4723
|
const hooksRegistered = settingsContent.includes("engrm") || settingsContent.includes("session-start") || settingsContent.includes("user-prompt-submit");
|
|
4403
4724
|
const codexRegistered = codexContent.includes("[mcp_servers.engrm]") || codexContent.includes(`[mcp_servers.${LEGACY_CODEX_SERVER_NAME3}]`);
|
|
4404
4725
|
const codexHooksRegistered = codexHooksContent.includes('"SessionStart"') && codexHooksContent.includes('"Stop"');
|
|
4726
|
+
const opencodeRegistered = opencodeConfigContent.includes('"engrm"') && opencodeConfigContent.includes('"local"');
|
|
4727
|
+
const opencodePluginRegistered = existsSync8(opencodePlugin);
|
|
4405
4728
|
let hookCount = 0;
|
|
4406
4729
|
if (hooksRegistered) {
|
|
4407
4730
|
try {
|
|
@@ -4421,8 +4744,10 @@ function handleStatus() {
|
|
|
4421
4744
|
}
|
|
4422
4745
|
console.log(` MCP server: ${mcpRegistered ? "registered" : "not registered"}`);
|
|
4423
4746
|
console.log(` Codex MCP: ${codexRegistered ? "registered" : "not registered"}`);
|
|
4747
|
+
console.log(` OpenCode MCP: ${opencodeRegistered ? "registered" : "not registered"}`);
|
|
4424
4748
|
console.log(` Hooks: ${hooksRegistered ? `registered (${hookCount || "?"} hooks)` : "not registered"}`);
|
|
4425
4749
|
console.log(` Codex hooks: ${codexHooksRegistered ? "registered (2 hooks)" : "not registered"}`);
|
|
4750
|
+
console.log(` OpenCode plug: ${opencodePluginRegistered ? "registered" : "not registered"}`);
|
|
4426
4751
|
if (config.sentinel?.enabled) {
|
|
4427
4752
|
console.log(`
|
|
4428
4753
|
Sentinel`);
|
|
@@ -4431,7 +4756,7 @@ function handleStatus() {
|
|
|
4431
4756
|
if (config.sentinel.provider) {
|
|
4432
4757
|
console.log(` Provider: ${config.sentinel.provider}${config.sentinel.model ? ` (${config.sentinel.model})` : ""}`);
|
|
4433
4758
|
}
|
|
4434
|
-
if (
|
|
4759
|
+
if (existsSync8(getDbPath())) {
|
|
4435
4760
|
try {
|
|
4436
4761
|
const db = new MemDatabase(getDbPath());
|
|
4437
4762
|
const todayStart = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000);
|
|
@@ -4444,7 +4769,7 @@ function handleStatus() {
|
|
|
4444
4769
|
console.log(`
|
|
4445
4770
|
Sentinel: disabled`);
|
|
4446
4771
|
}
|
|
4447
|
-
if (
|
|
4772
|
+
if (existsSync8(getDbPath())) {
|
|
4448
4773
|
try {
|
|
4449
4774
|
const db = new MemDatabase(getDbPath());
|
|
4450
4775
|
const obsCount = db.getActiveObservationCount();
|
|
@@ -4549,8 +4874,10 @@ function handleStatus() {
|
|
|
4549
4874
|
Files`);
|
|
4550
4875
|
console.log(` Config: ${getSettingsPath()}`);
|
|
4551
4876
|
console.log(` Database: ${getDbPath()}`);
|
|
4552
|
-
console.log(` Codex config: ${
|
|
4553
|
-
console.log(` Codex hooks: ${
|
|
4877
|
+
console.log(` Codex config: ${join8(homedir5(), ".codex", "config.toml")}`);
|
|
4878
|
+
console.log(` Codex hooks: ${join8(homedir5(), ".codex", "hooks.json")}`);
|
|
4879
|
+
console.log(` OpenCode cfg: ${join8(homedir5(), ".config", "opencode", "opencode.json")}`);
|
|
4880
|
+
console.log(` OpenCode plug: ${join8(homedir5(), ".config", "opencode", "plugins", "engrm.js")}`);
|
|
4554
4881
|
}
|
|
4555
4882
|
function formatTimeAgo(epoch) {
|
|
4556
4883
|
const ago = Math.floor(Date.now() / 1000) - epoch;
|
|
@@ -4572,14 +4899,14 @@ function formatSyncTime(epochStr) {
|
|
|
4572
4899
|
}
|
|
4573
4900
|
function ensureConfigDir() {
|
|
4574
4901
|
const dir = getConfigDir();
|
|
4575
|
-
if (!
|
|
4576
|
-
|
|
4902
|
+
if (!existsSync8(dir)) {
|
|
4903
|
+
mkdirSync4(dir, { recursive: true });
|
|
4577
4904
|
}
|
|
4578
4905
|
}
|
|
4579
|
-
function
|
|
4580
|
-
const host =
|
|
4906
|
+
function generateDeviceId3() {
|
|
4907
|
+
const host = hostname3().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
4581
4908
|
let mac = "";
|
|
4582
|
-
const ifaces =
|
|
4909
|
+
const ifaces = networkInterfaces3();
|
|
4583
4910
|
for (const entries of Object.values(ifaces)) {
|
|
4584
4911
|
if (!entries)
|
|
4585
4912
|
continue;
|
|
@@ -4593,7 +4920,7 @@ function generateDeviceId2() {
|
|
|
4593
4920
|
break;
|
|
4594
4921
|
}
|
|
4595
4922
|
const material = `${host}:${mac || "no-mac"}`;
|
|
4596
|
-
const suffix =
|
|
4923
|
+
const suffix = createHash4("sha256").update(material).digest("hex").slice(0, 8);
|
|
4597
4924
|
return `${host}-${suffix}`;
|
|
4598
4925
|
}
|
|
4599
4926
|
async function handleInstallPack(flags) {
|
|
@@ -4723,6 +5050,22 @@ async function handleDoctor() {
|
|
|
4723
5050
|
printDoctorReport(results);
|
|
4724
5051
|
return;
|
|
4725
5052
|
}
|
|
5053
|
+
if (config.http.enabled) {
|
|
5054
|
+
if (config.http.bearer_tokens.length > 0) {
|
|
5055
|
+
pass(`HTTP MCP enabled on port ${config.http.port} with ${config.http.bearer_tokens.length} bearer token(s)`);
|
|
5056
|
+
} else {
|
|
5057
|
+
warn("HTTP MCP is enabled but no bearer tokens are configured");
|
|
5058
|
+
}
|
|
5059
|
+
} else {
|
|
5060
|
+
info("HTTP MCP disabled");
|
|
5061
|
+
}
|
|
5062
|
+
if (config.fleet.project_name) {
|
|
5063
|
+
if (config.fleet.namespace && config.fleet.api_key) {
|
|
5064
|
+
pass(`Fleet project '${config.fleet.project_name}' is configured`);
|
|
5065
|
+
} else {
|
|
5066
|
+
info(`Fleet project '${config.fleet.project_name}' is reserved but not fully configured`);
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
4726
5069
|
let db = null;
|
|
4727
5070
|
try {
|
|
4728
5071
|
db = new MemDatabase(getDbPath());
|
|
@@ -4742,10 +5085,10 @@ async function handleDoctor() {
|
|
|
4742
5085
|
} catch {
|
|
4743
5086
|
warn("Could not check database schema version");
|
|
4744
5087
|
}
|
|
4745
|
-
const claudeJson =
|
|
5088
|
+
const claudeJson = join8(homedir5(), ".claude.json");
|
|
4746
5089
|
try {
|
|
4747
|
-
if (
|
|
4748
|
-
const content =
|
|
5090
|
+
if (existsSync8(claudeJson)) {
|
|
5091
|
+
const content = readFileSync8(claudeJson, "utf-8");
|
|
4749
5092
|
if (content.includes('"engrm"')) {
|
|
4750
5093
|
pass("MCP server registered in Claude Code");
|
|
4751
5094
|
} else {
|
|
@@ -4757,10 +5100,10 @@ async function handleDoctor() {
|
|
|
4757
5100
|
} catch {
|
|
4758
5101
|
warn("Could not check MCP server registration");
|
|
4759
5102
|
}
|
|
4760
|
-
const claudeSettings =
|
|
5103
|
+
const claudeSettings = join8(homedir5(), ".claude", "settings.json");
|
|
4761
5104
|
try {
|
|
4762
|
-
if (
|
|
4763
|
-
const content =
|
|
5105
|
+
if (existsSync8(claudeSettings)) {
|
|
5106
|
+
const content = readFileSync8(claudeSettings, "utf-8");
|
|
4764
5107
|
let hookCount = 0;
|
|
4765
5108
|
let hasSessionStart = false;
|
|
4766
5109
|
let hasUserPrompt = false;
|
|
@@ -4806,10 +5149,10 @@ async function handleDoctor() {
|
|
|
4806
5149
|
} catch {
|
|
4807
5150
|
warn("Could not check hooks registration");
|
|
4808
5151
|
}
|
|
4809
|
-
const codexConfig =
|
|
5152
|
+
const codexConfig = join8(homedir5(), ".codex", "config.toml");
|
|
4810
5153
|
try {
|
|
4811
|
-
if (
|
|
4812
|
-
const content =
|
|
5154
|
+
if (existsSync8(codexConfig)) {
|
|
5155
|
+
const content = readFileSync8(codexConfig, "utf-8");
|
|
4813
5156
|
if (content.includes("[mcp_servers.engrm]") || content.includes(`[mcp_servers.${LEGACY_CODEX_SERVER_NAME3}]`)) {
|
|
4814
5157
|
pass("MCP server registered in Codex");
|
|
4815
5158
|
} else {
|
|
@@ -4821,10 +5164,10 @@ async function handleDoctor() {
|
|
|
4821
5164
|
} catch {
|
|
4822
5165
|
warn("Could not check Codex MCP registration");
|
|
4823
5166
|
}
|
|
4824
|
-
const codexHooks =
|
|
5167
|
+
const codexHooks = join8(homedir5(), ".codex", "hooks.json");
|
|
4825
5168
|
try {
|
|
4826
|
-
if (
|
|
4827
|
-
const content =
|
|
5169
|
+
if (existsSync8(codexHooks)) {
|
|
5170
|
+
const content = readFileSync8(codexHooks, "utf-8");
|
|
4828
5171
|
if (content.includes('"SessionStart"') && content.includes('"Stop"')) {
|
|
4829
5172
|
pass("Hooks registered in Codex");
|
|
4830
5173
|
} else {
|
|
@@ -4836,6 +5179,27 @@ async function handleDoctor() {
|
|
|
4836
5179
|
} catch {
|
|
4837
5180
|
warn("Could not check Codex hooks registration");
|
|
4838
5181
|
}
|
|
5182
|
+
const opencodeConfig = join8(homedir5(), ".config", "opencode", "opencode.json");
|
|
5183
|
+
try {
|
|
5184
|
+
if (existsSync8(opencodeConfig)) {
|
|
5185
|
+
const content = readFileSync8(opencodeConfig, "utf-8");
|
|
5186
|
+
if (content.includes('"engrm"') && content.includes('"local"')) {
|
|
5187
|
+
pass("MCP server registered in OpenCode");
|
|
5188
|
+
} else {
|
|
5189
|
+
warn("MCP server not registered in OpenCode");
|
|
5190
|
+
}
|
|
5191
|
+
} else {
|
|
5192
|
+
warn("OpenCode config not found (~/.config/opencode/opencode.json)");
|
|
5193
|
+
}
|
|
5194
|
+
} catch {
|
|
5195
|
+
warn("Could not check OpenCode MCP registration");
|
|
5196
|
+
}
|
|
5197
|
+
const opencodePlugin = join8(homedir5(), ".config", "opencode", "plugins", "engrm.js");
|
|
5198
|
+
if (existsSync8(opencodePlugin)) {
|
|
5199
|
+
pass("Plugin installed in OpenCode");
|
|
5200
|
+
} else {
|
|
5201
|
+
warn("OpenCode plugin not installed (~/.config/opencode/plugins/engrm.js)");
|
|
5202
|
+
}
|
|
4839
5203
|
if (config.candengo_url) {
|
|
4840
5204
|
try {
|
|
4841
5205
|
const baseUrl = normalizeBaseUrl(config.candengo_url);
|
|
@@ -4946,7 +5310,7 @@ async function handleDoctor() {
|
|
|
4946
5310
|
}
|
|
4947
5311
|
try {
|
|
4948
5312
|
const dbPath = getDbPath();
|
|
4949
|
-
if (
|
|
5313
|
+
if (existsSync8(dbPath)) {
|
|
4950
5314
|
const stats = statSync(dbPath);
|
|
4951
5315
|
const sizeMB = stats.size / (1024 * 1024);
|
|
4952
5316
|
const sizeStr = sizeMB >= 1 ? `${sizeMB.toFixed(1)} MB` : `${(stats.size / 1024).toFixed(0)} KB`;
|
|
@@ -5011,16 +5375,18 @@ Registering with Claude Code and Codex...`);
|
|
|
5011
5375
|
console.log(` Claude hooks registered \u2192 ${result.hooks.path}`);
|
|
5012
5376
|
console.log(` Codex MCP registered \u2192 ${result.codex.path}`);
|
|
5013
5377
|
console.log(` Codex hooks registered \u2192 ${result.codexHooks.path}`);
|
|
5378
|
+
console.log(` OpenCode MCP registered \u2192 ${result.opencode.path}`);
|
|
5379
|
+
console.log(` OpenCode plugin installed \u2192 ${result.opencode.pluginPath}`);
|
|
5014
5380
|
console.log(`
|
|
5015
|
-
Engrm is ready! Start a new Claude Code or
|
|
5381
|
+
Engrm is ready! Start a new Claude Code, Codex, or OpenCode session to use memory.`);
|
|
5016
5382
|
} catch (error) {
|
|
5017
|
-
const packageRoot =
|
|
5383
|
+
const packageRoot = join8(THIS_DIR, "..");
|
|
5018
5384
|
const runtime = IS_BUILT_DIST ? process.execPath : "bun";
|
|
5019
|
-
const serverArgs = IS_BUILT_DIST ? [
|
|
5020
|
-
const sessionStartCommand = IS_BUILT_DIST ? `${process.execPath} ${
|
|
5021
|
-
const codexStopCommand = IS_BUILT_DIST ? `${process.execPath} ${
|
|
5385
|
+
const serverArgs = IS_BUILT_DIST ? [join8(packageRoot, "dist", "server.js")] : ["run", join8(packageRoot, "src", "server.ts")];
|
|
5386
|
+
const sessionStartCommand = IS_BUILT_DIST ? `${process.execPath} ${join8(packageRoot, "dist", "hooks", "session-start.js")}` : `bun run ${join8(packageRoot, "hooks", "session-start.ts")}`;
|
|
5387
|
+
const codexStopCommand = IS_BUILT_DIST ? `${process.execPath} ${join8(packageRoot, "dist", "hooks", "codex-stop.js")}` : `bun run ${join8(packageRoot, "hooks", "codex-stop.ts")}`;
|
|
5022
5388
|
console.log(`
|
|
5023
|
-
Could not auto-register with Claude Code and
|
|
5389
|
+
Could not auto-register with Claude Code, Codex, and OpenCode.`);
|
|
5024
5390
|
console.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
5025
5391
|
console.log(`
|
|
5026
5392
|
Manual setup \u2014 add to ~/.claude.json:`);
|
|
@@ -5074,6 +5440,24 @@ And add to ~/.codex/hooks.json:`);
|
|
|
5074
5440
|
}
|
|
5075
5441
|
}
|
|
5076
5442
|
`);
|
|
5443
|
+
console.log(`
|
|
5444
|
+
And add to ~/.config/opencode/opencode.json:`);
|
|
5445
|
+
console.log(`
|
|
5446
|
+
{
|
|
5447
|
+
"$schema": "https://opencode.ai/config.json",
|
|
5448
|
+
"mcp": {
|
|
5449
|
+
"engrm": {
|
|
5450
|
+
"type": "local",
|
|
5451
|
+
"command": ["engrm", "serve"],
|
|
5452
|
+
"enabled": true,
|
|
5453
|
+
"timeout": 5000
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
`);
|
|
5458
|
+
console.log(`
|
|
5459
|
+
And copy the OpenCode plugin file to ~/.config/opencode/plugins/engrm.js:`);
|
|
5460
|
+
console.log(` ${join8(packageRoot, "opencode", "plugin", "engrm-opencode.js")}`);
|
|
5077
5461
|
}
|
|
5078
5462
|
}
|
|
5079
5463
|
function formatTomlArray(values) {
|