engrm 0.4.43 → 0.4.45
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/cli.js +260 -24
- package/dist/hooks/elicitation-result.js +22 -10
- package/dist/hooks/post-tool-use.js +99 -36
- package/dist/hooks/pre-compact.js +35 -11
- package/dist/hooks/sentinel.js +22 -10
- package/dist/hooks/session-start.js +158 -14
- package/dist/hooks/stop.js +178 -41
- package/dist/hooks/user-prompt-submit.js +35 -1505
- package/dist/server.js +202 -27
- package/opencode/README.md +6 -6
- package/opencode/install-or-update-opencode-plugin.sh +7 -1
- package/opencode/opencode.example.json +2 -2
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -14409,6 +14409,7 @@ function ensureObservationTypes(db) {
|
|
|
14409
14409
|
DROP TABLE observations;
|
|
14410
14410
|
ALTER TABLE observations_repair RENAME TO observations;
|
|
14411
14411
|
CREATE INDEX IF NOT EXISTS idx_observations_project ON observations(project_id);
|
|
14412
|
+
CREATE INDEX IF NOT EXISTS idx_observations_project_lifecycle ON observations(project_id, lifecycle);
|
|
14412
14413
|
CREATE INDEX IF NOT EXISTS idx_observations_type ON observations(type);
|
|
14413
14414
|
CREATE INDEX IF NOT EXISTS idx_observations_created ON observations(created_at_epoch);
|
|
14414
14415
|
CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
|
|
@@ -14689,6 +14690,7 @@ class MemDatabase {
|
|
|
14689
14690
|
this.db = openDatabase(dbPath);
|
|
14690
14691
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
14691
14692
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
14693
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
14692
14694
|
this.vecAvailable = this.loadVecExtension();
|
|
14693
14695
|
runMigrations(this.db);
|
|
14694
14696
|
ensureObservationTypes(this.db);
|
|
@@ -14710,8 +14712,16 @@ class MemDatabase {
|
|
|
14710
14712
|
this.db.close();
|
|
14711
14713
|
}
|
|
14712
14714
|
upsertProject(project) {
|
|
14715
|
+
const canonicalId = project.canonical_id?.trim();
|
|
14716
|
+
const name = project.name?.trim();
|
|
14717
|
+
if (!canonicalId) {
|
|
14718
|
+
throw new Error("Project canonical_id is required");
|
|
14719
|
+
}
|
|
14720
|
+
if (!name) {
|
|
14721
|
+
throw new Error("Project name is required");
|
|
14722
|
+
}
|
|
14713
14723
|
const now = Math.floor(Date.now() / 1000);
|
|
14714
|
-
const existing = this.db.query("SELECT * FROM projects WHERE canonical_id = ?").get(
|
|
14724
|
+
const existing = this.db.query("SELECT * FROM projects WHERE canonical_id = ?").get(canonicalId);
|
|
14715
14725
|
if (existing) {
|
|
14716
14726
|
this.db.query(`UPDATE projects SET
|
|
14717
14727
|
local_path = COALESCE(?, local_path),
|
|
@@ -14726,7 +14736,7 @@ class MemDatabase {
|
|
|
14726
14736
|
};
|
|
14727
14737
|
}
|
|
14728
14738
|
const result = this.db.query(`INSERT INTO projects (canonical_id, name, local_path, remote_url, first_seen_epoch, last_active_epoch)
|
|
14729
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(
|
|
14739
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(canonicalId, name, project.local_path ?? null, project.remote_url ?? null, now, now);
|
|
14730
14740
|
return this.db.query("SELECT * FROM projects WHERE id = ?").get(Number(result.lastInsertRowid));
|
|
14731
14741
|
}
|
|
14732
14742
|
getProjectByCanonicalId(canonicalId) {
|
|
@@ -15822,10 +15832,11 @@ function readProjectConfigFile(directory) {
|
|
|
15822
15832
|
}
|
|
15823
15833
|
}
|
|
15824
15834
|
function detectProject(directory) {
|
|
15825
|
-
const
|
|
15835
|
+
const resolvedDirectory = resolve(directory);
|
|
15836
|
+
const remoteUrl = getGitRemoteUrl(resolvedDirectory);
|
|
15826
15837
|
if (remoteUrl) {
|
|
15827
15838
|
const canonicalId = normaliseGitRemoteUrl(remoteUrl);
|
|
15828
|
-
const repoRoot = getGitTopLevel(
|
|
15839
|
+
const repoRoot = getGitTopLevel(resolvedDirectory) ?? resolvedDirectory;
|
|
15829
15840
|
return {
|
|
15830
15841
|
canonical_id: canonicalId,
|
|
15831
15842
|
name: projectNameFromCanonicalId(canonicalId),
|
|
@@ -15833,21 +15844,22 @@ function detectProject(directory) {
|
|
|
15833
15844
|
local_path: repoRoot
|
|
15834
15845
|
};
|
|
15835
15846
|
}
|
|
15836
|
-
const configFile = readProjectConfigFile(
|
|
15847
|
+
const configFile = readProjectConfigFile(resolvedDirectory);
|
|
15837
15848
|
if (configFile) {
|
|
15838
15849
|
return {
|
|
15839
15850
|
canonical_id: configFile.project_id,
|
|
15840
15851
|
name: configFile.name ?? projectNameFromCanonicalId(configFile.project_id),
|
|
15841
15852
|
remote_url: null,
|
|
15842
|
-
local_path:
|
|
15853
|
+
local_path: resolvedDirectory
|
|
15843
15854
|
};
|
|
15844
15855
|
}
|
|
15845
|
-
const dirName = basename(
|
|
15856
|
+
const dirName = basename(resolvedDirectory);
|
|
15857
|
+
const safeDirName = !dirName || dirName === "/" || dirName === "." ? "root" : dirName;
|
|
15846
15858
|
return {
|
|
15847
|
-
canonical_id: `local/${
|
|
15848
|
-
name:
|
|
15859
|
+
canonical_id: `local/${safeDirName}`,
|
|
15860
|
+
name: safeDirName,
|
|
15849
15861
|
remote_url: null,
|
|
15850
|
-
local_path:
|
|
15862
|
+
local_path: resolvedDirectory
|
|
15851
15863
|
};
|
|
15852
15864
|
}
|
|
15853
15865
|
function detectProjectForPath(filePath, fallbackCwd) {
|
|
@@ -17537,7 +17549,19 @@ function formatTimestamp(nowMs) {
|
|
|
17537
17549
|
return `${yyyy}-${mm}-${dd} ${hh}:${mi}Z`;
|
|
17538
17550
|
}
|
|
17539
17551
|
function compactLine(value) {
|
|
17540
|
-
|
|
17552
|
+
if (value === null || value === undefined)
|
|
17553
|
+
return null;
|
|
17554
|
+
let text;
|
|
17555
|
+
if (typeof value === "string") {
|
|
17556
|
+
text = value;
|
|
17557
|
+
} else {
|
|
17558
|
+
try {
|
|
17559
|
+
text = JSON.stringify(value);
|
|
17560
|
+
} catch {
|
|
17561
|
+
text = String(value);
|
|
17562
|
+
}
|
|
17563
|
+
}
|
|
17564
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
17541
17565
|
if (!trimmed)
|
|
17542
17566
|
return null;
|
|
17543
17567
|
return trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed;
|
|
@@ -21217,11 +21241,12 @@ function getPendingEntries(db, limit = 50) {
|
|
|
21217
21241
|
LIMIT ?`).all(now, limit);
|
|
21218
21242
|
}
|
|
21219
21243
|
function markSyncing(db, entryId) {
|
|
21220
|
-
|
|
21244
|
+
const now = Math.floor(Date.now() / 1000);
|
|
21245
|
+
db.db.query("UPDATE sync_outbox SET status = 'syncing', next_retry_epoch = ? WHERE id = ?").run(now, entryId);
|
|
21221
21246
|
}
|
|
21222
21247
|
function markSynced(db, entryId) {
|
|
21223
21248
|
const now = Math.floor(Date.now() / 1000);
|
|
21224
|
-
db.db.query("UPDATE sync_outbox SET status = 'synced', synced_at_epoch =
|
|
21249
|
+
db.db.query("UPDATE sync_outbox SET status = 'synced', synced_at_epoch = ?, next_retry_epoch = NULL, last_error = NULL WHERE id = ?").run(now, entryId);
|
|
21225
21250
|
}
|
|
21226
21251
|
function markFailed(db, entryId, error48) {
|
|
21227
21252
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -21245,6 +21270,74 @@ function getOutboxStats(db) {
|
|
|
21245
21270
|
}
|
|
21246
21271
|
return stats;
|
|
21247
21272
|
}
|
|
21273
|
+
function getOutboxFailureSummaries(db, limit = 5) {
|
|
21274
|
+
return db.db.query(`SELECT COALESCE(last_error, '') as error, COUNT(*) as count
|
|
21275
|
+
FROM sync_outbox
|
|
21276
|
+
WHERE status = 'failed'
|
|
21277
|
+
GROUP BY COALESCE(last_error, '')
|
|
21278
|
+
ORDER BY count DESC, error ASC
|
|
21279
|
+
LIMIT ?`).all(limit).filter((row) => row.error.length > 0);
|
|
21280
|
+
}
|
|
21281
|
+
function classifyOutboxFailure(error48) {
|
|
21282
|
+
const normalized = error48.toLowerCase();
|
|
21283
|
+
if (normalized.includes("401") || normalized.includes("invalid or missing credentials")) {
|
|
21284
|
+
return "auth";
|
|
21285
|
+
}
|
|
21286
|
+
if (normalized.includes("429") || normalized.includes("rate limit")) {
|
|
21287
|
+
return "rate_limit";
|
|
21288
|
+
}
|
|
21289
|
+
if (normalized.includes("timeout") || normalized.includes("abort")) {
|
|
21290
|
+
return "timeout";
|
|
21291
|
+
}
|
|
21292
|
+
if (normalized.includes("network") || normalized.includes("fetch") || normalized.includes("econn")) {
|
|
21293
|
+
return "network";
|
|
21294
|
+
}
|
|
21295
|
+
if (normalized.includes("400") || normalized.includes("422") || normalized.includes("validation")) {
|
|
21296
|
+
return "validation";
|
|
21297
|
+
}
|
|
21298
|
+
return "other";
|
|
21299
|
+
}
|
|
21300
|
+
function resetFailedEntries(db) {
|
|
21301
|
+
const result = db.db.query(`UPDATE sync_outbox
|
|
21302
|
+
SET status = 'pending',
|
|
21303
|
+
retry_count = 0,
|
|
21304
|
+
last_error = NULL,
|
|
21305
|
+
next_retry_epoch = NULL
|
|
21306
|
+
WHERE status = 'failed'`).run();
|
|
21307
|
+
return result.changes;
|
|
21308
|
+
}
|
|
21309
|
+
function resetFailedEntriesMatching(db, predicate) {
|
|
21310
|
+
const rows = db.db.query(`SELECT id, last_error
|
|
21311
|
+
FROM sync_outbox
|
|
21312
|
+
WHERE status = 'failed'`).all();
|
|
21313
|
+
const matchingIds = rows.filter((row) => row.last_error && predicate(row.last_error)).map((row) => row.id);
|
|
21314
|
+
if (matchingIds.length === 0)
|
|
21315
|
+
return 0;
|
|
21316
|
+
const placeholders = matchingIds.map(() => "?").join(", ");
|
|
21317
|
+
const result = db.db.query(`UPDATE sync_outbox
|
|
21318
|
+
SET status = 'pending',
|
|
21319
|
+
retry_count = 0,
|
|
21320
|
+
last_error = NULL,
|
|
21321
|
+
next_retry_epoch = NULL
|
|
21322
|
+
WHERE id IN (${placeholders})`).run(...matchingIds);
|
|
21323
|
+
return result.changes;
|
|
21324
|
+
}
|
|
21325
|
+
function resetSyncingEntries(db) {
|
|
21326
|
+
const result = db.db.query(`UPDATE sync_outbox
|
|
21327
|
+
SET status = 'pending',
|
|
21328
|
+
next_retry_epoch = NULL
|
|
21329
|
+
WHERE status = 'syncing'`).run();
|
|
21330
|
+
return result.changes;
|
|
21331
|
+
}
|
|
21332
|
+
function resetStaleSyncingEntries(db, maxAgeSeconds = 300) {
|
|
21333
|
+
const cutoff = Math.floor(Date.now() / 1000) - maxAgeSeconds;
|
|
21334
|
+
const result = db.db.query(`UPDATE sync_outbox
|
|
21335
|
+
SET status = 'pending',
|
|
21336
|
+
next_retry_epoch = NULL
|
|
21337
|
+
WHERE status = 'syncing'
|
|
21338
|
+
AND (next_retry_epoch IS NULL OR next_retry_epoch <= ?)`).run(cutoff);
|
|
21339
|
+
return result.changes;
|
|
21340
|
+
}
|
|
21248
21341
|
|
|
21249
21342
|
// src/intelligence/value-signals.ts
|
|
21250
21343
|
var LESSON_TYPES = new Set(["bugfix", "decision", "pattern"]);
|
|
@@ -21381,7 +21474,12 @@ function getMemoryStats(db) {
|
|
|
21381
21474
|
recent_completed: insights.recent_completed,
|
|
21382
21475
|
next_steps: insights.next_steps,
|
|
21383
21476
|
installed_packs: db.getInstalledPacks(),
|
|
21384
|
-
outbox: getOutboxStats(db)
|
|
21477
|
+
outbox: getOutboxStats(db),
|
|
21478
|
+
outbox_failure_summary: getOutboxFailureSummaries(db).map((row) => ({
|
|
21479
|
+
category: classifyOutboxFailure(row.error),
|
|
21480
|
+
error: row.error,
|
|
21481
|
+
count: row.count
|
|
21482
|
+
}))
|
|
21385
21483
|
};
|
|
21386
21484
|
}
|
|
21387
21485
|
|
|
@@ -21564,6 +21662,7 @@ function isDue(db, key, interval, now) {
|
|
|
21564
21662
|
}
|
|
21565
21663
|
|
|
21566
21664
|
// src/sync/auth.ts
|
|
21665
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
21567
21666
|
var LEGACY_PUBLIC_HOSTS = new Set(["www.candengo.com", "candengo.com"]);
|
|
21568
21667
|
function normalizeBaseUrl(url2) {
|
|
21569
21668
|
const trimmed = url2.trim();
|
|
@@ -21594,6 +21693,36 @@ function getBaseUrl(config2) {
|
|
|
21594
21693
|
}
|
|
21595
21694
|
return null;
|
|
21596
21695
|
}
|
|
21696
|
+
function getAuthFingerprint(config2) {
|
|
21697
|
+
const apiKey = getApiKey(config2);
|
|
21698
|
+
const baseUrl = getBaseUrl(config2);
|
|
21699
|
+
if (!apiKey || !baseUrl)
|
|
21700
|
+
return null;
|
|
21701
|
+
return createHash4("sha256").update(`${baseUrl}
|
|
21702
|
+
${apiKey}
|
|
21703
|
+
${config2.namespace}
|
|
21704
|
+
${config2.site_id}`).digest("hex");
|
|
21705
|
+
}
|
|
21706
|
+
function recoverOutboxAfterAuthChange(db, config2) {
|
|
21707
|
+
const fingerprint = getAuthFingerprint(config2);
|
|
21708
|
+
if (!fingerprint) {
|
|
21709
|
+
const staleSyncingReset2 = resetStaleSyncingEntries(db);
|
|
21710
|
+
return { fingerprintChanged: false, failedReset: 0, authFailedReset: 0, syncingReset: 0, staleSyncingReset: staleSyncingReset2 };
|
|
21711
|
+
}
|
|
21712
|
+
const key = "sync_auth_fingerprint";
|
|
21713
|
+
const previous = db.getSyncState(key);
|
|
21714
|
+
const fingerprintChanged = previous !== fingerprint;
|
|
21715
|
+
if (!fingerprintChanged) {
|
|
21716
|
+
const authFailedReset = resetFailedEntriesMatching(db, (error48) => classifyOutboxFailure(error48) === "auth");
|
|
21717
|
+
const staleSyncingReset2 = resetStaleSyncingEntries(db);
|
|
21718
|
+
return { fingerprintChanged: false, failedReset: 0, authFailedReset, syncingReset: 0, staleSyncingReset: staleSyncingReset2 };
|
|
21719
|
+
}
|
|
21720
|
+
const failedReset = resetFailedEntries(db);
|
|
21721
|
+
const syncingReset = resetSyncingEntries(db);
|
|
21722
|
+
const staleSyncingReset = 0;
|
|
21723
|
+
db.setSyncState(key, fingerprint);
|
|
21724
|
+
return { fingerprintChanged: true, failedReset, authFailedReset: 0, syncingReset, staleSyncingReset };
|
|
21725
|
+
}
|
|
21597
21726
|
function buildSourceId(config2, localId, type = "obs") {
|
|
21598
21727
|
return `${config2.user_id}-${config2.device_id}-${type}-${localId}`;
|
|
21599
21728
|
}
|
|
@@ -21782,7 +21911,19 @@ function buildCurrentThread(latestRequest, recentOutcomes, hotFiles, recentToolN
|
|
|
21782
21911
|
return null;
|
|
21783
21912
|
}
|
|
21784
21913
|
function compactLine2(value) {
|
|
21785
|
-
|
|
21914
|
+
if (value === null || value === undefined)
|
|
21915
|
+
return null;
|
|
21916
|
+
let text;
|
|
21917
|
+
if (typeof value === "string") {
|
|
21918
|
+
text = value;
|
|
21919
|
+
} else {
|
|
21920
|
+
try {
|
|
21921
|
+
text = JSON.stringify(value);
|
|
21922
|
+
} catch {
|
|
21923
|
+
text = String(value);
|
|
21924
|
+
}
|
|
21925
|
+
}
|
|
21926
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
21786
21927
|
if (!trimmed)
|
|
21787
21928
|
return null;
|
|
21788
21929
|
return trimmed.length > 120 ? `${trimmed.slice(0, 117)}...` : trimmed;
|
|
@@ -22375,6 +22516,9 @@ async function pullSettings(client, config2) {
|
|
|
22375
22516
|
|
|
22376
22517
|
// src/sync/engine.ts
|
|
22377
22518
|
var DEFAULT_PULL_INTERVAL = 60000;
|
|
22519
|
+
function recordNowEpoch(db, key) {
|
|
22520
|
+
db.setSyncState(key, String(Math.floor(Date.now() / 1000)));
|
|
22521
|
+
}
|
|
22378
22522
|
|
|
22379
22523
|
class SyncEngine {
|
|
22380
22524
|
db;
|
|
@@ -22433,7 +22577,11 @@ class SyncEngine {
|
|
|
22433
22577
|
return;
|
|
22434
22578
|
this._pushing = true;
|
|
22435
22579
|
try {
|
|
22436
|
-
|
|
22580
|
+
recoverOutboxAfterAuthChange(this.db, this.config);
|
|
22581
|
+
const result = await pushOutbox(this.db, this.config, this.config.sync.batch_size);
|
|
22582
|
+
if (result.pushed > 0) {
|
|
22583
|
+
recordNowEpoch(this.db, "last_push_epoch");
|
|
22584
|
+
}
|
|
22437
22585
|
} finally {
|
|
22438
22586
|
this._pushing = false;
|
|
22439
22587
|
}
|
|
@@ -22443,11 +22591,16 @@ class SyncEngine {
|
|
|
22443
22591
|
return;
|
|
22444
22592
|
this._pulling = true;
|
|
22445
22593
|
try {
|
|
22446
|
-
await pullFromVector(this.db, this.client, this.config);
|
|
22594
|
+
const primary = await pullFromVector(this.db, this.client, this.config);
|
|
22595
|
+
let totalReceived = primary.received;
|
|
22447
22596
|
if (this.fleetClient) {
|
|
22448
|
-
await pullFromVector(this.db, this.fleetClient, this.config);
|
|
22597
|
+
const fleet = await pullFromVector(this.db, this.fleetClient, this.config);
|
|
22598
|
+
totalReceived += fleet.received;
|
|
22449
22599
|
}
|
|
22450
22600
|
await pullSettings(this.client, this.config);
|
|
22601
|
+
if (totalReceived > 0) {
|
|
22602
|
+
recordNowEpoch(this.db, "last_pull_epoch");
|
|
22603
|
+
}
|
|
22451
22604
|
} finally {
|
|
22452
22605
|
this._pulling = false;
|
|
22453
22606
|
}
|
|
@@ -23103,20 +23256,41 @@ function resolveAgentName(clientName) {
|
|
|
23103
23256
|
return clientName;
|
|
23104
23257
|
}
|
|
23105
23258
|
var syncEngine = null;
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
process.on("SIGTERM", () => {
|
|
23259
|
+
var shuttingDown = false;
|
|
23260
|
+
function shutdown(code = 0) {
|
|
23261
|
+
if (shuttingDown)
|
|
23262
|
+
return;
|
|
23263
|
+
shuttingDown = true;
|
|
23112
23264
|
syncEngine?.stop();
|
|
23113
23265
|
db.close();
|
|
23114
|
-
process.exit(
|
|
23115
|
-
}
|
|
23266
|
+
process.exit(code);
|
|
23267
|
+
}
|
|
23268
|
+
process.on("SIGINT", () => shutdown(0));
|
|
23269
|
+
process.on("SIGTERM", () => shutdown(0));
|
|
23270
|
+
function installStdioLivenessGuards() {
|
|
23271
|
+
process.stdin.on("end", () => shutdown(0));
|
|
23272
|
+
process.stdin.on("close", () => shutdown(0));
|
|
23273
|
+
process.stdin.on("error", () => shutdown(0));
|
|
23274
|
+
process.stdin.resume();
|
|
23275
|
+
const parentPid = process.ppid;
|
|
23276
|
+
if (!Number.isInteger(parentPid) || parentPid <= 1)
|
|
23277
|
+
return;
|
|
23278
|
+
const heartbeat = setInterval(() => {
|
|
23279
|
+
try {
|
|
23280
|
+
process.kill(parentPid, 0);
|
|
23281
|
+
} catch {
|
|
23282
|
+
shutdown(0);
|
|
23283
|
+
}
|
|
23284
|
+
if (process.ppid === 1) {
|
|
23285
|
+
shutdown(0);
|
|
23286
|
+
}
|
|
23287
|
+
}, 30000);
|
|
23288
|
+
heartbeat.unref();
|
|
23289
|
+
}
|
|
23116
23290
|
function buildServer() {
|
|
23117
23291
|
const server = new McpServer({
|
|
23118
23292
|
name: "engrm",
|
|
23119
|
-
version: "0.4.
|
|
23293
|
+
version: "0.4.45"
|
|
23120
23294
|
});
|
|
23121
23295
|
const enabledToolNames = getEnabledToolNames(config2.tool_profile);
|
|
23122
23296
|
const originalTool = server.tool.bind(server);
|
|
@@ -25127,6 +25301,7 @@ async function main() {
|
|
|
25127
25301
|
await startHttpServer();
|
|
25128
25302
|
return;
|
|
25129
25303
|
}
|
|
25304
|
+
installStdioLivenessGuards();
|
|
25130
25305
|
const transport = new StdioServerTransport;
|
|
25131
25306
|
await server.connect(transport);
|
|
25132
25307
|
}
|
package/opencode/README.md
CHANGED
|
@@ -31,12 +31,12 @@ The MCP entry written by the helper script matches:
|
|
|
31
31
|
{
|
|
32
32
|
"$schema": "https://opencode.ai/config.json",
|
|
33
33
|
"mcp": {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
"engrm": {
|
|
35
|
+
"type": "local",
|
|
36
|
+
"command": ["node", "/absolute/path/to/engrm/dist/server.js"],
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"timeout": 5000
|
|
39
|
+
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
```
|
|
@@ -13,6 +13,12 @@ import os
|
|
|
13
13
|
root = Path(os.environ["ENGRM_OPENCODE_SCRIPT_DIR"]).resolve()
|
|
14
14
|
repo = root.parent
|
|
15
15
|
plugin_source = repo / "opencode" / "plugin" / "engrm-opencode.js"
|
|
16
|
+
runtime = shutil.which("node") or shutil.which("bun") or "node"
|
|
17
|
+
dist_server = repo / "dist" / "server.js"
|
|
18
|
+
src_server = repo / "src" / "server.ts"
|
|
19
|
+
command = [runtime, str(dist_server if dist_server.exists() else src_server)]
|
|
20
|
+
if not dist_server.exists() and command[0].endswith("bun"):
|
|
21
|
+
command = [runtime, "run", str(src_server)]
|
|
16
22
|
config_dir = Path.home() / ".config" / "opencode"
|
|
17
23
|
plugins_dir = config_dir / "plugins"
|
|
18
24
|
config_path = config_dir / "opencode.json"
|
|
@@ -32,7 +38,7 @@ config.setdefault("$schema", "https://opencode.ai/config.json")
|
|
|
32
38
|
mcp = config.setdefault("mcp", {})
|
|
33
39
|
mcp["engrm"] = {
|
|
34
40
|
"type": "local",
|
|
35
|
-
"command":
|
|
41
|
+
"command": command,
|
|
36
42
|
"enabled": True,
|
|
37
43
|
"timeout": 5000,
|
|
38
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engrm",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.45",
|
|
4
4
|
"description": "Shared memory across devices, sessions, and agents, with thin MCP tools for durable capture, live continuity, and Hermes-ready remote MCP support",
|
|
5
5
|
"mcpName": "io.github.dr12hes/engrm",
|
|
6
6
|
"type": "module",
|