engrm 0.3.4 → 0.4.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/dist/cli.js +631 -11
- package/dist/hooks/elicitation-result.js +32 -4
- package/dist/hooks/post-tool-use.js +287 -4
- package/dist/hooks/pre-compact.js +55 -10
- package/dist/hooks/sentinel.js +32 -4
- package/dist/hooks/session-start.js +291 -11
- package/dist/hooks/stop.js +852 -4
- package/dist/server.js +102 -10
- package/package.json +1 -1
|
@@ -5,9 +5,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { homedir, hostname } from "node:os";
|
|
8
|
+
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
11
|
var CONFIG_DIR = join(homedir(), ".engrm");
|
|
12
12
|
var SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
|
|
13
13
|
var DB_PATH = join(CONFIG_DIR, "engrm.db");
|
|
@@ -16,7 +16,22 @@ function getDbPath() {
|
|
|
16
16
|
}
|
|
17
17
|
function generateDeviceId() {
|
|
18
18
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
19
|
-
|
|
19
|
+
let mac = "";
|
|
20
|
+
const ifaces = networkInterfaces();
|
|
21
|
+
for (const entries of Object.values(ifaces)) {
|
|
22
|
+
if (!entries)
|
|
23
|
+
continue;
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
26
|
+
mac = entry.mac;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (mac)
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const material = `${host}:${mac || "no-mac"}`;
|
|
34
|
+
const suffix = createHash("sha256").update(material).digest("hex").slice(0, 8);
|
|
20
35
|
return `${host}-${suffix}`;
|
|
21
36
|
}
|
|
22
37
|
function createDefaultConfig() {
|
|
@@ -58,7 +73,10 @@ function createDefaultConfig() {
|
|
|
58
73
|
observer: {
|
|
59
74
|
enabled: true,
|
|
60
75
|
mode: "per_event",
|
|
61
|
-
model: "
|
|
76
|
+
model: "sonnet"
|
|
77
|
+
},
|
|
78
|
+
transcript_analysis: {
|
|
79
|
+
enabled: false
|
|
62
80
|
}
|
|
63
81
|
};
|
|
64
82
|
}
|
|
@@ -117,6 +135,9 @@ function loadConfig() {
|
|
|
117
135
|
enabled: asBool(config["observer"]?.["enabled"], defaults.observer.enabled),
|
|
118
136
|
mode: asObserverMode(config["observer"]?.["mode"], defaults.observer.mode),
|
|
119
137
|
model: asString(config["observer"]?.["model"], defaults.observer.model)
|
|
138
|
+
},
|
|
139
|
+
transcript_analysis: {
|
|
140
|
+
enabled: asBool(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
120
141
|
}
|
|
121
142
|
};
|
|
122
143
|
}
|
|
@@ -1109,7 +1130,7 @@ function estimateTokens(text) {
|
|
|
1109
1130
|
}
|
|
1110
1131
|
function buildSessionContext(db, cwd, options = {}) {
|
|
1111
1132
|
const opts = typeof options === "number" ? { maxCount: options } : options;
|
|
1112
|
-
const tokenBudget = opts.tokenBudget ??
|
|
1133
|
+
const tokenBudget = opts.tokenBudget ?? 3000;
|
|
1113
1134
|
const maxCount = opts.maxCount;
|
|
1114
1135
|
const detected = detectProject(cwd);
|
|
1115
1136
|
const project = db.getProjectByCanonicalId(detected.canonical_id);
|
|
@@ -1131,6 +1152,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1131
1152
|
AND superseded_by IS NULL
|
|
1132
1153
|
ORDER BY quality DESC, created_at_epoch DESC
|
|
1133
1154
|
LIMIT ?`).all(project.id, MAX_PINNED);
|
|
1155
|
+
const MAX_RECENT = 5;
|
|
1156
|
+
const recent = db.db.query(`SELECT * FROM observations
|
|
1157
|
+
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
1158
|
+
AND superseded_by IS NULL
|
|
1159
|
+
ORDER BY created_at_epoch DESC
|
|
1160
|
+
LIMIT ?`).all(project.id, MAX_RECENT);
|
|
1134
1161
|
const candidateLimit = maxCount ?? 50;
|
|
1135
1162
|
const candidates = db.db.query(`SELECT * FROM observations
|
|
1136
1163
|
WHERE project_id = ? AND lifecycle IN ('active', 'aging')
|
|
@@ -1158,6 +1185,12 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1158
1185
|
});
|
|
1159
1186
|
}
|
|
1160
1187
|
const seenIds = new Set(pinned.map((o) => o.id));
|
|
1188
|
+
const dedupedRecent = recent.filter((o) => {
|
|
1189
|
+
if (seenIds.has(o.id))
|
|
1190
|
+
return false;
|
|
1191
|
+
seenIds.add(o.id);
|
|
1192
|
+
return true;
|
|
1193
|
+
});
|
|
1161
1194
|
const deduped = candidates.filter((o) => !seenIds.has(o.id));
|
|
1162
1195
|
for (const obs of crossProjectCandidates) {
|
|
1163
1196
|
if (!seenIds.has(obs.id)) {
|
|
@@ -1174,8 +1207,8 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1174
1207
|
return scoreB - scoreA;
|
|
1175
1208
|
});
|
|
1176
1209
|
if (maxCount !== undefined) {
|
|
1177
|
-
const remaining = Math.max(0, maxCount - pinned.length);
|
|
1178
|
-
const all = [...pinned, ...sorted.slice(0, remaining)];
|
|
1210
|
+
const remaining = Math.max(0, maxCount - pinned.length - dedupedRecent.length);
|
|
1211
|
+
const all = [...pinned, ...dedupedRecent, ...sorted.slice(0, remaining)];
|
|
1179
1212
|
return {
|
|
1180
1213
|
project_name: project.name,
|
|
1181
1214
|
canonical_id: project.canonical_id,
|
|
@@ -1191,6 +1224,11 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1191
1224
|
remainingBudget -= cost;
|
|
1192
1225
|
selected.push(obs);
|
|
1193
1226
|
}
|
|
1227
|
+
for (const obs of dedupedRecent) {
|
|
1228
|
+
const cost = estimateObservationTokens(obs, selected.length);
|
|
1229
|
+
remainingBudget -= cost;
|
|
1230
|
+
selected.push(obs);
|
|
1231
|
+
}
|
|
1194
1232
|
for (const obs of sorted) {
|
|
1195
1233
|
const cost = estimateObservationTokens(obs, selected.length);
|
|
1196
1234
|
if (remainingBudget - cost < 0 && selected.length > 0)
|
|
@@ -1198,7 +1236,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1198
1236
|
remainingBudget -= cost;
|
|
1199
1237
|
selected.push(obs);
|
|
1200
1238
|
}
|
|
1201
|
-
const summaries = db.getRecentSummaries(project.id,
|
|
1239
|
+
const summaries = db.getRecentSummaries(project.id, 5);
|
|
1202
1240
|
let securityFindings = [];
|
|
1203
1241
|
try {
|
|
1204
1242
|
const weekAgo = Math.floor(Date.now() / 1000) - 7 * 86400;
|
|
@@ -1218,7 +1256,7 @@ function buildSessionContext(db, cwd, options = {}) {
|
|
|
1218
1256
|
};
|
|
1219
1257
|
}
|
|
1220
1258
|
function estimateObservationTokens(obs, index) {
|
|
1221
|
-
const DETAILED_THRESHOLD =
|
|
1259
|
+
const DETAILED_THRESHOLD = 5;
|
|
1222
1260
|
const titleCost = estimateTokens(`- **[${obs.type}]** ${obs.title} (2026-01-01, q=0.5)`);
|
|
1223
1261
|
if (index >= DETAILED_THRESHOLD) {
|
|
1224
1262
|
return titleCost;
|
|
@@ -1230,7 +1268,7 @@ function formatContextForInjection(context) {
|
|
|
1230
1268
|
if (context.observations.length === 0) {
|
|
1231
1269
|
return `Project: ${context.project_name} (no prior observations)`;
|
|
1232
1270
|
}
|
|
1233
|
-
const DETAILED_COUNT =
|
|
1271
|
+
const DETAILED_COUNT = 5;
|
|
1234
1272
|
const lines = [
|
|
1235
1273
|
`## Project Memory: ${context.project_name}`,
|
|
1236
1274
|
`${context.session_count} relevant observation(s) from prior sessions:`,
|
|
@@ -1517,6 +1555,202 @@ function recommendPacks(stacks, installedPacks) {
|
|
|
1517
1555
|
return recommendations;
|
|
1518
1556
|
}
|
|
1519
1557
|
|
|
1558
|
+
// src/config.ts
|
|
1559
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1560
|
+
import { homedir as homedir2, hostname as hostname2, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
1561
|
+
import { join as join5 } from "node:path";
|
|
1562
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1563
|
+
var CONFIG_DIR2 = join5(homedir2(), ".engrm");
|
|
1564
|
+
var SETTINGS_PATH2 = join5(CONFIG_DIR2, "settings.json");
|
|
1565
|
+
var DB_PATH2 = join5(CONFIG_DIR2, "engrm.db");
|
|
1566
|
+
function getDbPath2() {
|
|
1567
|
+
return DB_PATH2;
|
|
1568
|
+
}
|
|
1569
|
+
function generateDeviceId2() {
|
|
1570
|
+
const host = hostname2().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
1571
|
+
let mac = "";
|
|
1572
|
+
const ifaces = networkInterfaces2();
|
|
1573
|
+
for (const entries of Object.values(ifaces)) {
|
|
1574
|
+
if (!entries)
|
|
1575
|
+
continue;
|
|
1576
|
+
for (const entry of entries) {
|
|
1577
|
+
if (!entry.internal && entry.mac && entry.mac !== "00:00:00:00:00:00") {
|
|
1578
|
+
mac = entry.mac;
|
|
1579
|
+
break;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (mac)
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
const material = `${host}:${mac || "no-mac"}`;
|
|
1586
|
+
const suffix = createHash2("sha256").update(material).digest("hex").slice(0, 8);
|
|
1587
|
+
return `${host}-${suffix}`;
|
|
1588
|
+
}
|
|
1589
|
+
function createDefaultConfig2() {
|
|
1590
|
+
return {
|
|
1591
|
+
candengo_url: "",
|
|
1592
|
+
candengo_api_key: "",
|
|
1593
|
+
site_id: "",
|
|
1594
|
+
namespace: "",
|
|
1595
|
+
user_id: "",
|
|
1596
|
+
user_email: "",
|
|
1597
|
+
device_id: generateDeviceId2(),
|
|
1598
|
+
teams: [],
|
|
1599
|
+
sync: {
|
|
1600
|
+
enabled: true,
|
|
1601
|
+
interval_seconds: 30,
|
|
1602
|
+
batch_size: 50
|
|
1603
|
+
},
|
|
1604
|
+
search: {
|
|
1605
|
+
default_limit: 10,
|
|
1606
|
+
local_boost: 1.2,
|
|
1607
|
+
scope: "all"
|
|
1608
|
+
},
|
|
1609
|
+
scrubbing: {
|
|
1610
|
+
enabled: true,
|
|
1611
|
+
custom_patterns: [],
|
|
1612
|
+
default_sensitivity: "shared"
|
|
1613
|
+
},
|
|
1614
|
+
sentinel: {
|
|
1615
|
+
enabled: false,
|
|
1616
|
+
mode: "advisory",
|
|
1617
|
+
provider: "openai",
|
|
1618
|
+
model: "gpt-4o-mini",
|
|
1619
|
+
api_key: "",
|
|
1620
|
+
base_url: "",
|
|
1621
|
+
skip_patterns: [],
|
|
1622
|
+
daily_limit: 100,
|
|
1623
|
+
tier: "free"
|
|
1624
|
+
},
|
|
1625
|
+
observer: {
|
|
1626
|
+
enabled: true,
|
|
1627
|
+
mode: "per_event",
|
|
1628
|
+
model: "sonnet"
|
|
1629
|
+
},
|
|
1630
|
+
transcript_analysis: {
|
|
1631
|
+
enabled: false
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function loadConfig2() {
|
|
1636
|
+
if (!existsSync5(SETTINGS_PATH2)) {
|
|
1637
|
+
throw new Error(`Config not found at ${SETTINGS_PATH2}. Run 'engrm init --manual' to configure.`);
|
|
1638
|
+
}
|
|
1639
|
+
const raw = readFileSync4(SETTINGS_PATH2, "utf-8");
|
|
1640
|
+
let parsed;
|
|
1641
|
+
try {
|
|
1642
|
+
parsed = JSON.parse(raw);
|
|
1643
|
+
} catch {
|
|
1644
|
+
throw new Error(`Invalid JSON in ${SETTINGS_PATH2}`);
|
|
1645
|
+
}
|
|
1646
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
1647
|
+
throw new Error(`Config at ${SETTINGS_PATH2} is not a JSON object`);
|
|
1648
|
+
}
|
|
1649
|
+
const config = parsed;
|
|
1650
|
+
const defaults = createDefaultConfig2();
|
|
1651
|
+
return {
|
|
1652
|
+
candengo_url: asString2(config["candengo_url"], defaults.candengo_url),
|
|
1653
|
+
candengo_api_key: asString2(config["candengo_api_key"], defaults.candengo_api_key),
|
|
1654
|
+
site_id: asString2(config["site_id"], defaults.site_id),
|
|
1655
|
+
namespace: asString2(config["namespace"], defaults.namespace),
|
|
1656
|
+
user_id: asString2(config["user_id"], defaults.user_id),
|
|
1657
|
+
user_email: asString2(config["user_email"], defaults.user_email),
|
|
1658
|
+
device_id: asString2(config["device_id"], defaults.device_id),
|
|
1659
|
+
teams: asTeams2(config["teams"], defaults.teams),
|
|
1660
|
+
sync: {
|
|
1661
|
+
enabled: asBool2(config["sync"]?.["enabled"], defaults.sync.enabled),
|
|
1662
|
+
interval_seconds: asNumber2(config["sync"]?.["interval_seconds"], defaults.sync.interval_seconds),
|
|
1663
|
+
batch_size: asNumber2(config["sync"]?.["batch_size"], defaults.sync.batch_size)
|
|
1664
|
+
},
|
|
1665
|
+
search: {
|
|
1666
|
+
default_limit: asNumber2(config["search"]?.["default_limit"], defaults.search.default_limit),
|
|
1667
|
+
local_boost: asNumber2(config["search"]?.["local_boost"], defaults.search.local_boost),
|
|
1668
|
+
scope: asScope2(config["search"]?.["scope"], defaults.search.scope)
|
|
1669
|
+
},
|
|
1670
|
+
scrubbing: {
|
|
1671
|
+
enabled: asBool2(config["scrubbing"]?.["enabled"], defaults.scrubbing.enabled),
|
|
1672
|
+
custom_patterns: asStringArray2(config["scrubbing"]?.["custom_patterns"], defaults.scrubbing.custom_patterns),
|
|
1673
|
+
default_sensitivity: asSensitivity2(config["scrubbing"]?.["default_sensitivity"], defaults.scrubbing.default_sensitivity)
|
|
1674
|
+
},
|
|
1675
|
+
sentinel: {
|
|
1676
|
+
enabled: asBool2(config["sentinel"]?.["enabled"], defaults.sentinel.enabled),
|
|
1677
|
+
mode: asSentinelMode2(config["sentinel"]?.["mode"], defaults.sentinel.mode),
|
|
1678
|
+
provider: asLlmProvider2(config["sentinel"]?.["provider"], defaults.sentinel.provider),
|
|
1679
|
+
model: asString2(config["sentinel"]?.["model"], defaults.sentinel.model),
|
|
1680
|
+
api_key: asString2(config["sentinel"]?.["api_key"], defaults.sentinel.api_key),
|
|
1681
|
+
base_url: asString2(config["sentinel"]?.["base_url"], defaults.sentinel.base_url),
|
|
1682
|
+
skip_patterns: asStringArray2(config["sentinel"]?.["skip_patterns"], defaults.sentinel.skip_patterns),
|
|
1683
|
+
daily_limit: asNumber2(config["sentinel"]?.["daily_limit"], defaults.sentinel.daily_limit),
|
|
1684
|
+
tier: asTier2(config["sentinel"]?.["tier"], defaults.sentinel.tier)
|
|
1685
|
+
},
|
|
1686
|
+
observer: {
|
|
1687
|
+
enabled: asBool2(config["observer"]?.["enabled"], defaults.observer.enabled),
|
|
1688
|
+
mode: asObserverMode2(config["observer"]?.["mode"], defaults.observer.mode),
|
|
1689
|
+
model: asString2(config["observer"]?.["model"], defaults.observer.model)
|
|
1690
|
+
},
|
|
1691
|
+
transcript_analysis: {
|
|
1692
|
+
enabled: asBool2(config["transcript_analysis"]?.["enabled"], defaults.transcript_analysis.enabled)
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function saveConfig(config) {
|
|
1697
|
+
if (!existsSync5(CONFIG_DIR2)) {
|
|
1698
|
+
mkdirSync2(CONFIG_DIR2, { recursive: true });
|
|
1699
|
+
}
|
|
1700
|
+
writeFileSync2(SETTINGS_PATH2, JSON.stringify(config, null, 2) + `
|
|
1701
|
+
`, "utf-8");
|
|
1702
|
+
}
|
|
1703
|
+
function configExists2() {
|
|
1704
|
+
return existsSync5(SETTINGS_PATH2);
|
|
1705
|
+
}
|
|
1706
|
+
function asString2(value, fallback) {
|
|
1707
|
+
return typeof value === "string" ? value : fallback;
|
|
1708
|
+
}
|
|
1709
|
+
function asNumber2(value, fallback) {
|
|
1710
|
+
return typeof value === "number" && !Number.isNaN(value) ? value : fallback;
|
|
1711
|
+
}
|
|
1712
|
+
function asBool2(value, fallback) {
|
|
1713
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1714
|
+
}
|
|
1715
|
+
function asStringArray2(value, fallback) {
|
|
1716
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string") ? value : fallback;
|
|
1717
|
+
}
|
|
1718
|
+
function asScope2(value, fallback) {
|
|
1719
|
+
if (value === "personal" || value === "team" || value === "all")
|
|
1720
|
+
return value;
|
|
1721
|
+
return fallback;
|
|
1722
|
+
}
|
|
1723
|
+
function asSensitivity2(value, fallback) {
|
|
1724
|
+
if (value === "shared" || value === "personal" || value === "secret")
|
|
1725
|
+
return value;
|
|
1726
|
+
return fallback;
|
|
1727
|
+
}
|
|
1728
|
+
function asSentinelMode2(value, fallback) {
|
|
1729
|
+
if (value === "advisory" || value === "blocking")
|
|
1730
|
+
return value;
|
|
1731
|
+
return fallback;
|
|
1732
|
+
}
|
|
1733
|
+
function asLlmProvider2(value, fallback) {
|
|
1734
|
+
if (value === "openai" || value === "anthropic" || value === "ollama" || value === "custom")
|
|
1735
|
+
return value;
|
|
1736
|
+
return fallback;
|
|
1737
|
+
}
|
|
1738
|
+
function asTier2(value, fallback) {
|
|
1739
|
+
if (value === "free" || value === "vibe" || value === "solo" || value === "pro" || value === "team" || value === "enterprise")
|
|
1740
|
+
return value;
|
|
1741
|
+
return fallback;
|
|
1742
|
+
}
|
|
1743
|
+
function asObserverMode2(value, fallback) {
|
|
1744
|
+
if (value === "per_event" || value === "per_session")
|
|
1745
|
+
return value;
|
|
1746
|
+
return fallback;
|
|
1747
|
+
}
|
|
1748
|
+
function asTeams2(value, fallback) {
|
|
1749
|
+
if (!Array.isArray(value))
|
|
1750
|
+
return fallback;
|
|
1751
|
+
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1520
1754
|
// src/sync/auth.ts
|
|
1521
1755
|
function getApiKey(config) {
|
|
1522
1756
|
const envKey = process.env.ENGRM_TOKEN;
|
|
@@ -1702,6 +1936,44 @@ function extractNarrative(content) {
|
|
|
1702
1936
|
`).trim();
|
|
1703
1937
|
return narrative.length > 0 ? narrative : null;
|
|
1704
1938
|
}
|
|
1939
|
+
async function pullSettings(client, config) {
|
|
1940
|
+
try {
|
|
1941
|
+
const settings = await client.fetchSettings();
|
|
1942
|
+
if (!settings)
|
|
1943
|
+
return false;
|
|
1944
|
+
let changed = false;
|
|
1945
|
+
if (settings.transcript_analysis !== undefined) {
|
|
1946
|
+
const ta = settings.transcript_analysis;
|
|
1947
|
+
if (typeof ta === "object" && ta !== null) {
|
|
1948
|
+
const taObj = ta;
|
|
1949
|
+
if (taObj.enabled !== undefined && taObj.enabled !== config.transcript_analysis.enabled) {
|
|
1950
|
+
config.transcript_analysis.enabled = !!taObj.enabled;
|
|
1951
|
+
changed = true;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
if (settings.observer !== undefined) {
|
|
1956
|
+
const obs = settings.observer;
|
|
1957
|
+
if (typeof obs === "object" && obs !== null) {
|
|
1958
|
+
const obsObj = obs;
|
|
1959
|
+
if (obsObj.enabled !== undefined && obsObj.enabled !== config.observer.enabled) {
|
|
1960
|
+
config.observer.enabled = !!obsObj.enabled;
|
|
1961
|
+
changed = true;
|
|
1962
|
+
}
|
|
1963
|
+
if (obsObj.model !== undefined && typeof obsObj.model === "string" && obsObj.model !== config.observer.model) {
|
|
1964
|
+
config.observer.model = obsObj.model;
|
|
1965
|
+
changed = true;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
if (changed) {
|
|
1970
|
+
saveConfig(config);
|
|
1971
|
+
}
|
|
1972
|
+
return changed;
|
|
1973
|
+
} catch {
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1705
1977
|
|
|
1706
1978
|
// src/sync/client.ts
|
|
1707
1979
|
class VectorClient {
|
|
@@ -1756,6 +2028,13 @@ class VectorClient {
|
|
|
1756
2028
|
async sendTelemetry(beacon) {
|
|
1757
2029
|
await this.request("POST", "/v1/mem/telemetry", beacon);
|
|
1758
2030
|
}
|
|
2031
|
+
async fetchSettings() {
|
|
2032
|
+
try {
|
|
2033
|
+
return await this.request("GET", "/v1/mem/user-settings");
|
|
2034
|
+
} catch {
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
1759
2038
|
async health() {
|
|
1760
2039
|
try {
|
|
1761
2040
|
await this.request("GET", "/health");
|
|
@@ -1827,11 +2106,12 @@ async function main() {
|
|
|
1827
2106
|
try {
|
|
1828
2107
|
if (config.sync.enabled && config.candengo_api_key) {
|
|
1829
2108
|
try {
|
|
1830
|
-
const client = new VectorClient(config
|
|
2109
|
+
const client = new VectorClient(config);
|
|
1831
2110
|
const pullResult = await pullFromVector(db, client, config, 50);
|
|
1832
2111
|
if (pullResult.merged > 0) {
|
|
1833
2112
|
console.error(`Engrm: synced ${pullResult.merged} observation(s) from server`);
|
|
1834
2113
|
}
|
|
2114
|
+
await pullSettings(client, config);
|
|
1835
2115
|
} catch {}
|
|
1836
2116
|
}
|
|
1837
2117
|
const context = buildSessionContext(db, event.cwd, {
|