engrm 0.4.45 → 0.4.46
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 +396 -118
- package/dist/hooks/elicitation-result.js +81 -15
- package/dist/hooks/post-tool-use.js +250 -23
- package/dist/hooks/pre-compact.js +249 -23
- package/dist/hooks/sentinel.js +81 -15
- package/dist/hooks/session-start.js +105 -17
- package/dist/hooks/stop.js +311 -27
- package/dist/hooks/user-prompt-submit.js +81 -15
- package/dist/server.js +193 -34
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -29,17 +29,26 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
29
29
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
30
30
|
import { join } from "node:path";
|
|
31
31
|
import { createHash } from "node:crypto";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
function resolveConfigDir() {
|
|
33
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join(homedir(), ".engrm");
|
|
34
|
+
}
|
|
35
|
+
function resolveSettingsPath() {
|
|
36
|
+
return join(resolveConfigDir(), "settings.json");
|
|
37
|
+
}
|
|
38
|
+
function resolveDbPath() {
|
|
39
|
+
return join(resolveConfigDir(), "engrm.db");
|
|
40
|
+
}
|
|
41
|
+
function resolveAuthBackupPath() {
|
|
42
|
+
return join(resolveConfigDir(), "auth-backup.json");
|
|
43
|
+
}
|
|
35
44
|
function getConfigDir() {
|
|
36
|
-
return
|
|
45
|
+
return resolveConfigDir();
|
|
37
46
|
}
|
|
38
47
|
function getSettingsPath() {
|
|
39
|
-
return
|
|
48
|
+
return resolveSettingsPath();
|
|
40
49
|
}
|
|
41
50
|
function getDbPath() {
|
|
42
|
-
return
|
|
51
|
+
return resolveDbPath();
|
|
43
52
|
}
|
|
44
53
|
function generateDeviceId() {
|
|
45
54
|
const host = hostname().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -62,7 +71,7 @@ function generateDeviceId() {
|
|
|
62
71
|
return `${host}-${suffix}`;
|
|
63
72
|
}
|
|
64
73
|
function createDefaultConfig() {
|
|
65
|
-
|
|
74
|
+
const merged = {
|
|
66
75
|
candengo_url: "",
|
|
67
76
|
candengo_api_key: "",
|
|
68
77
|
site_id: "",
|
|
@@ -117,24 +126,26 @@ function createDefaultConfig() {
|
|
|
117
126
|
},
|
|
118
127
|
tool_profile: "full"
|
|
119
128
|
};
|
|
129
|
+
return merged;
|
|
120
130
|
}
|
|
121
131
|
function loadConfig() {
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
const settingsPath = resolveSettingsPath();
|
|
133
|
+
if (!existsSync(settingsPath)) {
|
|
134
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
124
135
|
}
|
|
125
|
-
const raw = readFileSync(
|
|
136
|
+
const raw = readFileSync(settingsPath, "utf-8");
|
|
126
137
|
let parsed;
|
|
127
138
|
try {
|
|
128
139
|
parsed = JSON.parse(raw);
|
|
129
140
|
} catch {
|
|
130
|
-
throw new Error(`Invalid JSON in ${
|
|
141
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
131
142
|
}
|
|
132
143
|
if (typeof parsed !== "object" || parsed === null) {
|
|
133
|
-
throw new Error(`Config at ${
|
|
144
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
134
145
|
}
|
|
135
146
|
const config = parsed;
|
|
136
147
|
const defaults = createDefaultConfig();
|
|
137
|
-
|
|
148
|
+
const merged = {
|
|
138
149
|
candengo_url: asString(config["candengo_url"], defaults.candengo_url),
|
|
139
150
|
candengo_api_key: asString(config["candengo_api_key"], defaults.candengo_api_key),
|
|
140
151
|
site_id: asString(config["site_id"], defaults.site_id),
|
|
@@ -189,16 +200,27 @@ function loadConfig() {
|
|
|
189
200
|
},
|
|
190
201
|
tool_profile: asToolProfile(config["tool_profile"], defaults.tool_profile)
|
|
191
202
|
};
|
|
203
|
+
if (looksLikePlaceholderAuth(merged)) {
|
|
204
|
+
return restoreAuthBackup(merged) ?? merged;
|
|
205
|
+
}
|
|
206
|
+
return merged;
|
|
192
207
|
}
|
|
193
208
|
function saveConfig(config) {
|
|
194
|
-
|
|
195
|
-
|
|
209
|
+
const configDir = resolveConfigDir();
|
|
210
|
+
const settingsPath = resolveSettingsPath();
|
|
211
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
212
|
+
if (!existsSync(configDir)) {
|
|
213
|
+
mkdirSync(configDir, { recursive: true });
|
|
196
214
|
}
|
|
197
|
-
writeFileSync(
|
|
215
|
+
writeFileSync(settingsPath, JSON.stringify(config, null, 2) + `
|
|
216
|
+
`, "utf-8");
|
|
217
|
+
if (!looksLikePlaceholderAuth(config)) {
|
|
218
|
+
writeFileSync(authBackupPath, JSON.stringify(extractAuthBackup(config), null, 2) + `
|
|
198
219
|
`, "utf-8");
|
|
220
|
+
}
|
|
199
221
|
}
|
|
200
222
|
function configExists() {
|
|
201
|
-
return existsSync(
|
|
223
|
+
return existsSync(resolveSettingsPath());
|
|
202
224
|
}
|
|
203
225
|
function asString(value, fallback) {
|
|
204
226
|
return typeof value === "string" ? value : fallback;
|
|
@@ -252,6 +274,50 @@ function asTeams(value, fallback) {
|
|
|
252
274
|
return fallback;
|
|
253
275
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
254
276
|
}
|
|
277
|
+
function looksLikePlaceholderAuth(config) {
|
|
278
|
+
const apiKey = config.candengo_api_key.trim();
|
|
279
|
+
const siteId = config.site_id.trim();
|
|
280
|
+
const namespace = config.namespace.trim();
|
|
281
|
+
const email = config.user_email.trim().toLowerCase();
|
|
282
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
283
|
+
return true;
|
|
284
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
285
|
+
return true;
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
function extractAuthBackup(config) {
|
|
289
|
+
return {
|
|
290
|
+
candengo_url: config.candengo_url,
|
|
291
|
+
candengo_api_key: config.candengo_api_key,
|
|
292
|
+
site_id: config.site_id,
|
|
293
|
+
namespace: config.namespace,
|
|
294
|
+
user_id: config.user_id,
|
|
295
|
+
user_email: config.user_email,
|
|
296
|
+
teams: config.teams
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function restoreAuthBackup(config) {
|
|
300
|
+
const authBackupPath = resolveAuthBackupPath();
|
|
301
|
+
if (!existsSync(authBackupPath))
|
|
302
|
+
return null;
|
|
303
|
+
try {
|
|
304
|
+
const raw = readFileSync(authBackupPath, "utf-8");
|
|
305
|
+
const parsed = JSON.parse(raw);
|
|
306
|
+
const restored = {
|
|
307
|
+
...config,
|
|
308
|
+
candengo_url: asString(parsed["candengo_url"], config.candengo_url),
|
|
309
|
+
candengo_api_key: asString(parsed["candengo_api_key"], config.candengo_api_key),
|
|
310
|
+
site_id: asString(parsed["site_id"], config.site_id),
|
|
311
|
+
namespace: asString(parsed["namespace"], config.namespace),
|
|
312
|
+
user_id: asString(parsed["user_id"], config.user_id),
|
|
313
|
+
user_email: asString(parsed["user_email"], config.user_email),
|
|
314
|
+
teams: asTeams(parsed["teams"], config.teams)
|
|
315
|
+
};
|
|
316
|
+
return looksLikePlaceholderAuth(restored) ? null : restored;
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
255
321
|
|
|
256
322
|
// src/storage/migrations.ts
|
|
257
323
|
var MIGRATIONS = [
|
|
@@ -3976,11 +4042,20 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
3976
4042
|
import { homedir as homedir3, hostname as hostname2, networkInterfaces as networkInterfaces2 } from "node:os";
|
|
3977
4043
|
import { join as join6 } from "node:path";
|
|
3978
4044
|
import { createHash as createHash3 } from "node:crypto";
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
4045
|
+
function resolveConfigDir2() {
|
|
4046
|
+
return process.env["ENGRM_CONFIG_DIR"]?.trim() || join6(homedir3(), ".engrm");
|
|
4047
|
+
}
|
|
4048
|
+
function resolveSettingsPath2() {
|
|
4049
|
+
return join6(resolveConfigDir2(), "settings.json");
|
|
4050
|
+
}
|
|
4051
|
+
function resolveDbPath2() {
|
|
4052
|
+
return join6(resolveConfigDir2(), "engrm.db");
|
|
4053
|
+
}
|
|
4054
|
+
function resolveAuthBackupPath2() {
|
|
4055
|
+
return join6(resolveConfigDir2(), "auth-backup.json");
|
|
4056
|
+
}
|
|
3982
4057
|
function getDbPath2() {
|
|
3983
|
-
return
|
|
4058
|
+
return resolveDbPath2();
|
|
3984
4059
|
}
|
|
3985
4060
|
function generateDeviceId2() {
|
|
3986
4061
|
const host = hostname2().toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
@@ -4003,7 +4078,7 @@ function generateDeviceId2() {
|
|
|
4003
4078
|
return `${host}-${suffix}`;
|
|
4004
4079
|
}
|
|
4005
4080
|
function createDefaultConfig2() {
|
|
4006
|
-
|
|
4081
|
+
const merged = {
|
|
4007
4082
|
candengo_url: "",
|
|
4008
4083
|
candengo_api_key: "",
|
|
4009
4084
|
site_id: "",
|
|
@@ -4058,24 +4133,26 @@ function createDefaultConfig2() {
|
|
|
4058
4133
|
},
|
|
4059
4134
|
tool_profile: "full"
|
|
4060
4135
|
};
|
|
4136
|
+
return merged;
|
|
4061
4137
|
}
|
|
4062
4138
|
function loadConfig2() {
|
|
4063
|
-
|
|
4064
|
-
|
|
4139
|
+
const settingsPath = resolveSettingsPath2();
|
|
4140
|
+
if (!existsSync6(settingsPath)) {
|
|
4141
|
+
throw new Error(`Config not found at ${settingsPath}. Run 'engrm init --manual' to configure.`);
|
|
4065
4142
|
}
|
|
4066
|
-
const raw = readFileSync6(
|
|
4143
|
+
const raw = readFileSync6(settingsPath, "utf-8");
|
|
4067
4144
|
let parsed;
|
|
4068
4145
|
try {
|
|
4069
4146
|
parsed = JSON.parse(raw);
|
|
4070
4147
|
} catch {
|
|
4071
|
-
throw new Error(`Invalid JSON in ${
|
|
4148
|
+
throw new Error(`Invalid JSON in ${settingsPath}`);
|
|
4072
4149
|
}
|
|
4073
4150
|
if (typeof parsed !== "object" || parsed === null) {
|
|
4074
|
-
throw new Error(`Config at ${
|
|
4151
|
+
throw new Error(`Config at ${settingsPath} is not a JSON object`);
|
|
4075
4152
|
}
|
|
4076
4153
|
const config = parsed;
|
|
4077
4154
|
const defaults = createDefaultConfig2();
|
|
4078
|
-
|
|
4155
|
+
const merged = {
|
|
4079
4156
|
candengo_url: asString2(config["candengo_url"], defaults.candengo_url),
|
|
4080
4157
|
candengo_api_key: asString2(config["candengo_api_key"], defaults.candengo_api_key),
|
|
4081
4158
|
site_id: asString2(config["site_id"], defaults.site_id),
|
|
@@ -4130,16 +4207,27 @@ function loadConfig2() {
|
|
|
4130
4207
|
},
|
|
4131
4208
|
tool_profile: asToolProfile2(config["tool_profile"], defaults.tool_profile)
|
|
4132
4209
|
};
|
|
4210
|
+
if (looksLikePlaceholderAuth2(merged)) {
|
|
4211
|
+
return restoreAuthBackup2(merged) ?? merged;
|
|
4212
|
+
}
|
|
4213
|
+
return merged;
|
|
4133
4214
|
}
|
|
4134
4215
|
function saveConfig2(config) {
|
|
4135
|
-
|
|
4136
|
-
|
|
4216
|
+
const configDir = resolveConfigDir2();
|
|
4217
|
+
const settingsPath = resolveSettingsPath2();
|
|
4218
|
+
const authBackupPath = resolveAuthBackupPath2();
|
|
4219
|
+
if (!existsSync6(configDir)) {
|
|
4220
|
+
mkdirSync3(configDir, { recursive: true });
|
|
4137
4221
|
}
|
|
4138
|
-
writeFileSync3(
|
|
4222
|
+
writeFileSync3(settingsPath, JSON.stringify(config, null, 2) + `
|
|
4223
|
+
`, "utf-8");
|
|
4224
|
+
if (!looksLikePlaceholderAuth2(config)) {
|
|
4225
|
+
writeFileSync3(authBackupPath, JSON.stringify(extractAuthBackup2(config), null, 2) + `
|
|
4139
4226
|
`, "utf-8");
|
|
4227
|
+
}
|
|
4140
4228
|
}
|
|
4141
4229
|
function configExists2() {
|
|
4142
|
-
return existsSync6(
|
|
4230
|
+
return existsSync6(resolveSettingsPath2());
|
|
4143
4231
|
}
|
|
4144
4232
|
function asString2(value, fallback) {
|
|
4145
4233
|
return typeof value === "string" ? value : fallback;
|
|
@@ -4193,6 +4281,50 @@ function asTeams2(value, fallback) {
|
|
|
4193
4281
|
return fallback;
|
|
4194
4282
|
return value.filter((t) => typeof t === "object" && t !== null && typeof t.id === "string" && typeof t.name === "string" && typeof t.namespace === "string");
|
|
4195
4283
|
}
|
|
4284
|
+
function looksLikePlaceholderAuth2(config) {
|
|
4285
|
+
const apiKey = config.candengo_api_key.trim();
|
|
4286
|
+
const siteId = config.site_id.trim();
|
|
4287
|
+
const namespace = config.namespace.trim();
|
|
4288
|
+
const email = config.user_email.trim().toLowerCase();
|
|
4289
|
+
if (apiKey === "cvk_org" && siteId === "site-1" && namespace === "org-ns")
|
|
4290
|
+
return true;
|
|
4291
|
+
if (siteId === "site-1" && namespace === "org-ns" && email.endsWith("@example.com"))
|
|
4292
|
+
return true;
|
|
4293
|
+
return false;
|
|
4294
|
+
}
|
|
4295
|
+
function extractAuthBackup2(config) {
|
|
4296
|
+
return {
|
|
4297
|
+
candengo_url: config.candengo_url,
|
|
4298
|
+
candengo_api_key: config.candengo_api_key,
|
|
4299
|
+
site_id: config.site_id,
|
|
4300
|
+
namespace: config.namespace,
|
|
4301
|
+
user_id: config.user_id,
|
|
4302
|
+
user_email: config.user_email,
|
|
4303
|
+
teams: config.teams
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
function restoreAuthBackup2(config) {
|
|
4307
|
+
const authBackupPath = resolveAuthBackupPath2();
|
|
4308
|
+
if (!existsSync6(authBackupPath))
|
|
4309
|
+
return null;
|
|
4310
|
+
try {
|
|
4311
|
+
const raw = readFileSync6(authBackupPath, "utf-8");
|
|
4312
|
+
const parsed = JSON.parse(raw);
|
|
4313
|
+
const restored = {
|
|
4314
|
+
...config,
|
|
4315
|
+
candengo_url: asString2(parsed["candengo_url"], config.candengo_url),
|
|
4316
|
+
candengo_api_key: asString2(parsed["candengo_api_key"], config.candengo_api_key),
|
|
4317
|
+
site_id: asString2(parsed["site_id"], config.site_id),
|
|
4318
|
+
namespace: asString2(parsed["namespace"], config.namespace),
|
|
4319
|
+
user_id: asString2(parsed["user_id"], config.user_id),
|
|
4320
|
+
user_email: asString2(parsed["user_email"], config.user_email),
|
|
4321
|
+
teams: asTeams2(parsed["teams"], config.teams)
|
|
4322
|
+
};
|
|
4323
|
+
return looksLikePlaceholderAuth2(restored) ? null : restored;
|
|
4324
|
+
} catch {
|
|
4325
|
+
return null;
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4196
4328
|
|
|
4197
4329
|
// src/tool-profiles.ts
|
|
4198
4330
|
var MEMORY_PROFILE_TOOLS = [
|
|
@@ -4291,13 +4423,8 @@ function getCaptureStatus(db, input = {}) {
|
|
|
4291
4423
|
FROM sessions s
|
|
4292
4424
|
WHERE COALESCE(s.completed_at_epoch, s.started_at_epoch, 0) >= ?
|
|
4293
4425
|
${input.user_id ? "AND s.user_id = ?" : ""}
|
|
4294
|
-
AND
|
|
4295
|
-
|
|
4296
|
-
OR (
|
|
4297
|
-
EXISTS (SELECT 1 FROM user_prompts up WHERE up.session_id = s.session_id)
|
|
4298
|
-
AND NOT EXISTS (SELECT 1 FROM tool_events te WHERE te.session_id = s.session_id)
|
|
4299
|
-
)
|
|
4300
|
-
)`).get(...params)?.count ?? 0;
|
|
4426
|
+
AND s.tool_calls_count > 0
|
|
4427
|
+
AND NOT EXISTS (SELECT 1 FROM tool_events te WHERE te.session_id = s.session_id)`).get(...params)?.count ?? 0;
|
|
4301
4428
|
const latestPromptEpoch = db.db.query(`SELECT created_at_epoch FROM user_prompts
|
|
4302
4429
|
WHERE 1 = 1${input.user_id ? " AND user_id = ?" : ""}
|
|
4303
4430
|
ORDER BY created_at_epoch DESC, prompt_number DESC
|
|
@@ -4478,6 +4605,10 @@ function resetStaleSyncingEntries(db, maxAgeSeconds = 300) {
|
|
|
4478
4605
|
|
|
4479
4606
|
// src/sync/auth.ts
|
|
4480
4607
|
var LEGACY_PUBLIC_HOSTS = new Set(["www.candengo.com", "candengo.com"]);
|
|
4608
|
+
var PLACEHOLDER_API_KEYS = new Set(["cvk_org"]);
|
|
4609
|
+
var PLACEHOLDER_SITE_IDS = new Set(["site-1"]);
|
|
4610
|
+
var PLACEHOLDER_NAMESPACES = new Set(["org-ns", "fleet-ns"]);
|
|
4611
|
+
var PLACEHOLDER_EMAIL_SUFFIXES = ["@example.com"];
|
|
4481
4612
|
function normalizeBaseUrl(url) {
|
|
4482
4613
|
const trimmed = url.trim();
|
|
4483
4614
|
if (!trimmed)
|
|
@@ -4496,7 +4627,7 @@ function getApiKey(config) {
|
|
|
4496
4627
|
const envKey = process.env.ENGRM_TOKEN;
|
|
4497
4628
|
if (envKey && envKey.startsWith("cvk_"))
|
|
4498
4629
|
return envKey;
|
|
4499
|
-
if (config.candengo_api_key && config.candengo_api_key.length > 0) {
|
|
4630
|
+
if (config.candengo_api_key && config.candengo_api_key.length > 0 && !looksLikePlaceholderConfig(config)) {
|
|
4500
4631
|
return config.candengo_api_key;
|
|
4501
4632
|
}
|
|
4502
4633
|
return null;
|
|
@@ -4517,18 +4648,37 @@ ${apiKey}
|
|
|
4517
4648
|
${config.namespace}
|
|
4518
4649
|
${config.site_id}`).digest("hex");
|
|
4519
4650
|
}
|
|
4520
|
-
function
|
|
4651
|
+
function looksLikePlaceholderConfig(config) {
|
|
4652
|
+
const apiKey = config.candengo_api_key?.trim() ?? "";
|
|
4653
|
+
const siteId = config.site_id?.trim() ?? "";
|
|
4654
|
+
const namespace = config.namespace?.trim() ?? "";
|
|
4655
|
+
const email = config.user_email?.trim().toLowerCase() ?? "";
|
|
4656
|
+
if (PLACEHOLDER_API_KEYS.has(apiKey) && PLACEHOLDER_SITE_IDS.has(siteId) && PLACEHOLDER_NAMESPACES.has(namespace)) {
|
|
4657
|
+
return true;
|
|
4658
|
+
}
|
|
4659
|
+
if (PLACEHOLDER_SITE_IDS.has(siteId) && PLACEHOLDER_NAMESPACES.has(namespace) && PLACEHOLDER_EMAIL_SUFFIXES.some((suffix) => email.endsWith(suffix))) {
|
|
4660
|
+
return true;
|
|
4661
|
+
}
|
|
4662
|
+
return false;
|
|
4663
|
+
}
|
|
4664
|
+
function clearSyncPushBlock(db) {
|
|
4665
|
+
db.setSyncState("sync_push_blocked_until", "0");
|
|
4666
|
+
db.setSyncState("sync_push_block_reason", "");
|
|
4667
|
+
}
|
|
4668
|
+
function resumeOutboxAfterValidatedAuth(db, config) {
|
|
4521
4669
|
const fingerprint = getAuthFingerprint(config);
|
|
4522
|
-
const
|
|
4523
|
-
const
|
|
4670
|
+
const failedReset = resetFailedEntries(db);
|
|
4671
|
+
const syncingReset = resetSyncingEntries(db);
|
|
4672
|
+
const staleSyncingReset = 0;
|
|
4524
4673
|
if (fingerprint) {
|
|
4525
4674
|
db.setSyncState("sync_auth_fingerprint", fingerprint);
|
|
4526
4675
|
}
|
|
4676
|
+
clearSyncPushBlock(db);
|
|
4527
4677
|
return {
|
|
4528
4678
|
fingerprintChanged: false,
|
|
4529
|
-
failedReset
|
|
4530
|
-
authFailedReset,
|
|
4531
|
-
syncingReset
|
|
4679
|
+
failedReset,
|
|
4680
|
+
authFailedReset: 0,
|
|
4681
|
+
syncingReset,
|
|
4532
4682
|
staleSyncingReset
|
|
4533
4683
|
};
|
|
4534
4684
|
}
|
|
@@ -4552,6 +4702,9 @@ switch (command) {
|
|
|
4552
4702
|
case "update":
|
|
4553
4703
|
handleUpdate();
|
|
4554
4704
|
break;
|
|
4705
|
+
case "sync":
|
|
4706
|
+
await handleSync(args.slice(1));
|
|
4707
|
+
break;
|
|
4555
4708
|
case "install-pack":
|
|
4556
4709
|
await handleInstallPack(args.slice(1));
|
|
4557
4710
|
break;
|
|
@@ -4710,31 +4863,7 @@ Authorization failed: ${error instanceof Error ? error.message : String(error)}`
|
|
|
4710
4863
|
}
|
|
4711
4864
|
function writeConfigFromProvision(baseUrl, result) {
|
|
4712
4865
|
ensureConfigDir();
|
|
4713
|
-
|
|
4714
|
-
let existingSentinel;
|
|
4715
|
-
let existingObserver;
|
|
4716
|
-
let existingTranscriptAnalysis;
|
|
4717
|
-
let existingHttp;
|
|
4718
|
-
let existingFleet;
|
|
4719
|
-
let existingToolProfile;
|
|
4720
|
-
let existingSync;
|
|
4721
|
-
let existingSearch;
|
|
4722
|
-
let existingScrubbing;
|
|
4723
|
-
if (configExists()) {
|
|
4724
|
-
try {
|
|
4725
|
-
const existing = loadConfig();
|
|
4726
|
-
existingDeviceId = existing.device_id;
|
|
4727
|
-
existingSentinel = existing.sentinel;
|
|
4728
|
-
existingObserver = existing.observer;
|
|
4729
|
-
existingTranscriptAnalysis = existing.transcript_analysis;
|
|
4730
|
-
existingHttp = existing.http;
|
|
4731
|
-
existingFleet = existing.fleet;
|
|
4732
|
-
existingToolProfile = existing.tool_profile;
|
|
4733
|
-
existingSync = existing.sync;
|
|
4734
|
-
existingSearch = existing.search;
|
|
4735
|
-
existingScrubbing = existing.scrubbing;
|
|
4736
|
-
} catch {}
|
|
4737
|
-
}
|
|
4866
|
+
const preserved = loadPreservedLocalConfig();
|
|
4738
4867
|
const config = {
|
|
4739
4868
|
candengo_url: baseUrl,
|
|
4740
4869
|
candengo_api_key: result.api_key,
|
|
@@ -4742,24 +4871,24 @@ function writeConfigFromProvision(baseUrl, result) {
|
|
|
4742
4871
|
namespace: result.namespace,
|
|
4743
4872
|
user_id: result.user_id,
|
|
4744
4873
|
user_email: result.user_email,
|
|
4745
|
-
device_id:
|
|
4874
|
+
device_id: preserved.device_id || generateDeviceId3(),
|
|
4746
4875
|
teams: result.teams ?? [],
|
|
4747
|
-
sync:
|
|
4876
|
+
sync: preserved.sync ?? {
|
|
4748
4877
|
enabled: true,
|
|
4749
4878
|
interval_seconds: 30,
|
|
4750
4879
|
batch_size: 50
|
|
4751
4880
|
},
|
|
4752
|
-
search:
|
|
4881
|
+
search: preserved.search ?? {
|
|
4753
4882
|
default_limit: 10,
|
|
4754
4883
|
local_boost: 1.2,
|
|
4755
4884
|
scope: "all"
|
|
4756
4885
|
},
|
|
4757
|
-
scrubbing:
|
|
4886
|
+
scrubbing: preserved.scrubbing ?? {
|
|
4758
4887
|
enabled: true,
|
|
4759
4888
|
custom_patterns: [],
|
|
4760
4889
|
default_sensitivity: "shared"
|
|
4761
4890
|
},
|
|
4762
|
-
sentinel:
|
|
4891
|
+
sentinel: preserved.sentinel ?? {
|
|
4763
4892
|
enabled: false,
|
|
4764
4893
|
mode: "advisory",
|
|
4765
4894
|
provider: "openai",
|
|
@@ -4770,33 +4899,54 @@ function writeConfigFromProvision(baseUrl, result) {
|
|
|
4770
4899
|
daily_limit: 100,
|
|
4771
4900
|
tier: "free"
|
|
4772
4901
|
},
|
|
4773
|
-
observer:
|
|
4902
|
+
observer: preserved.observer ?? {
|
|
4774
4903
|
enabled: true,
|
|
4775
4904
|
mode: "per_event",
|
|
4776
4905
|
model: "haiku"
|
|
4777
4906
|
},
|
|
4778
|
-
transcript_analysis:
|
|
4907
|
+
transcript_analysis: preserved.transcript_analysis ?? {
|
|
4779
4908
|
enabled: false
|
|
4780
4909
|
},
|
|
4781
|
-
http:
|
|
4910
|
+
http: preserved.http ?? {
|
|
4782
4911
|
enabled: false,
|
|
4783
4912
|
port: 3767,
|
|
4784
4913
|
bearer_tokens: []
|
|
4785
4914
|
},
|
|
4786
|
-
fleet:
|
|
4915
|
+
fleet: preserved.fleet ?? {
|
|
4787
4916
|
project_name: "shared-experience",
|
|
4788
4917
|
namespace: "",
|
|
4789
4918
|
api_key: ""
|
|
4790
4919
|
},
|
|
4791
|
-
tool_profile:
|
|
4920
|
+
tool_profile: preserved.tool_profile ?? "full"
|
|
4792
4921
|
};
|
|
4793
4922
|
saveConfig(config);
|
|
4794
4923
|
const db = new MemDatabase(getDbPath());
|
|
4795
|
-
|
|
4924
|
+
resumeOutboxAfterValidatedAuth(db, config);
|
|
4796
4925
|
db.close();
|
|
4797
4926
|
console.log(`Configuration saved to ${getSettingsPath()}`);
|
|
4798
4927
|
console.log(`Database initialised at ${getDbPath()}`);
|
|
4799
4928
|
}
|
|
4929
|
+
function loadPreservedLocalConfig() {
|
|
4930
|
+
if (!configExists())
|
|
4931
|
+
return {};
|
|
4932
|
+
try {
|
|
4933
|
+
const existing = loadConfig();
|
|
4934
|
+
return {
|
|
4935
|
+
device_id: existing.device_id,
|
|
4936
|
+
sync: existing.sync,
|
|
4937
|
+
search: existing.search,
|
|
4938
|
+
scrubbing: existing.scrubbing,
|
|
4939
|
+
sentinel: existing.sentinel,
|
|
4940
|
+
observer: existing.observer,
|
|
4941
|
+
transcript_analysis: existing.transcript_analysis,
|
|
4942
|
+
http: existing.http,
|
|
4943
|
+
fleet: existing.fleet,
|
|
4944
|
+
tool_profile: existing.tool_profile
|
|
4945
|
+
};
|
|
4946
|
+
} catch {
|
|
4947
|
+
return {};
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4800
4950
|
function initFromFile(configPath) {
|
|
4801
4951
|
if (!existsSync8(configPath)) {
|
|
4802
4952
|
console.error(`Config file not found: ${configPath}`);
|
|
@@ -4829,6 +4979,7 @@ function initFromFile(configPath) {
|
|
|
4829
4979
|
}
|
|
4830
4980
|
}
|
|
4831
4981
|
ensureConfigDir();
|
|
4982
|
+
const preserved = loadPreservedLocalConfig();
|
|
4832
4983
|
const config = {
|
|
4833
4984
|
candengo_url: input["candengo_url"].trim(),
|
|
4834
4985
|
candengo_api_key: input["candengo_api_key"].trim(),
|
|
@@ -4836,24 +4987,24 @@ function initFromFile(configPath) {
|
|
|
4836
4987
|
namespace: input["namespace"].trim(),
|
|
4837
4988
|
user_id: input["user_id"].trim(),
|
|
4838
4989
|
user_email: typeof input["user_email"] === "string" ? input["user_email"].trim() : "",
|
|
4839
|
-
device_id: typeof input["device_id"] === "string" ? input["device_id"] : generateDeviceId3(),
|
|
4990
|
+
device_id: typeof input["device_id"] === "string" ? input["device_id"] : preserved.device_id ?? generateDeviceId3(),
|
|
4840
4991
|
teams: [],
|
|
4841
|
-
sync: {
|
|
4992
|
+
sync: preserved.sync ?? {
|
|
4842
4993
|
enabled: true,
|
|
4843
4994
|
interval_seconds: 30,
|
|
4844
4995
|
batch_size: 50
|
|
4845
4996
|
},
|
|
4846
|
-
search: {
|
|
4997
|
+
search: preserved.search ?? {
|
|
4847
4998
|
default_limit: 10,
|
|
4848
4999
|
local_boost: 1.2,
|
|
4849
5000
|
scope: "all"
|
|
4850
5001
|
},
|
|
4851
|
-
scrubbing: {
|
|
5002
|
+
scrubbing: preserved.scrubbing ?? {
|
|
4852
5003
|
enabled: true,
|
|
4853
5004
|
custom_patterns: [],
|
|
4854
5005
|
default_sensitivity: "shared"
|
|
4855
5006
|
},
|
|
4856
|
-
sentinel: {
|
|
5007
|
+
sentinel: preserved.sentinel ?? {
|
|
4857
5008
|
enabled: false,
|
|
4858
5009
|
mode: "advisory",
|
|
4859
5010
|
provider: "openai",
|
|
@@ -4864,14 +5015,25 @@ function initFromFile(configPath) {
|
|
|
4864
5015
|
daily_limit: 100,
|
|
4865
5016
|
tier: "free"
|
|
4866
5017
|
},
|
|
4867
|
-
observer: {
|
|
5018
|
+
observer: preserved.observer ?? {
|
|
4868
5019
|
enabled: true,
|
|
4869
5020
|
mode: "per_event",
|
|
4870
5021
|
model: "haiku"
|
|
4871
5022
|
},
|
|
4872
|
-
transcript_analysis: {
|
|
5023
|
+
transcript_analysis: preserved.transcript_analysis ?? {
|
|
4873
5024
|
enabled: false
|
|
4874
|
-
}
|
|
5025
|
+
},
|
|
5026
|
+
http: preserved.http ?? {
|
|
5027
|
+
enabled: false,
|
|
5028
|
+
port: 3767,
|
|
5029
|
+
bearer_tokens: []
|
|
5030
|
+
},
|
|
5031
|
+
fleet: preserved.fleet ?? {
|
|
5032
|
+
project_name: "shared-experience",
|
|
5033
|
+
namespace: "",
|
|
5034
|
+
api_key: ""
|
|
5035
|
+
},
|
|
5036
|
+
tool_profile: preserved.tool_profile ?? "full"
|
|
4875
5037
|
};
|
|
4876
5038
|
saveConfig(config);
|
|
4877
5039
|
const db = new MemDatabase(getDbPath());
|
|
@@ -4902,6 +5064,7 @@ async function initManual() {
|
|
|
4902
5064
|
process.exit(1);
|
|
4903
5065
|
}
|
|
4904
5066
|
ensureConfigDir();
|
|
5067
|
+
const preserved = loadPreservedLocalConfig();
|
|
4905
5068
|
const config = {
|
|
4906
5069
|
candengo_url: candengoUrl.trim(),
|
|
4907
5070
|
candengo_api_key: apiKey.trim(),
|
|
@@ -4909,24 +5072,24 @@ async function initManual() {
|
|
|
4909
5072
|
namespace: namespace.trim(),
|
|
4910
5073
|
user_id: userId.trim(),
|
|
4911
5074
|
user_email: userEmail.trim(),
|
|
4912
|
-
device_id: generateDeviceId3(),
|
|
5075
|
+
device_id: preserved.device_id ?? generateDeviceId3(),
|
|
4913
5076
|
teams: [],
|
|
4914
|
-
sync: {
|
|
5077
|
+
sync: preserved.sync ?? {
|
|
4915
5078
|
enabled: true,
|
|
4916
5079
|
interval_seconds: 30,
|
|
4917
5080
|
batch_size: 50
|
|
4918
5081
|
},
|
|
4919
|
-
search: {
|
|
5082
|
+
search: preserved.search ?? {
|
|
4920
5083
|
default_limit: 10,
|
|
4921
5084
|
local_boost: 1.2,
|
|
4922
5085
|
scope: "all"
|
|
4923
5086
|
},
|
|
4924
|
-
scrubbing: {
|
|
5087
|
+
scrubbing: preserved.scrubbing ?? {
|
|
4925
5088
|
enabled: true,
|
|
4926
5089
|
custom_patterns: [],
|
|
4927
5090
|
default_sensitivity: "shared"
|
|
4928
5091
|
},
|
|
4929
|
-
sentinel: {
|
|
5092
|
+
sentinel: preserved.sentinel ?? {
|
|
4930
5093
|
enabled: false,
|
|
4931
5094
|
mode: "advisory",
|
|
4932
5095
|
provider: "openai",
|
|
@@ -4937,14 +5100,25 @@ async function initManual() {
|
|
|
4937
5100
|
daily_limit: 100,
|
|
4938
5101
|
tier: "free"
|
|
4939
5102
|
},
|
|
4940
|
-
observer: {
|
|
5103
|
+
observer: preserved.observer ?? {
|
|
4941
5104
|
enabled: true,
|
|
4942
5105
|
mode: "per_event",
|
|
4943
5106
|
model: "haiku"
|
|
4944
5107
|
},
|
|
4945
|
-
transcript_analysis: {
|
|
5108
|
+
transcript_analysis: preserved.transcript_analysis ?? {
|
|
4946
5109
|
enabled: false
|
|
4947
|
-
}
|
|
5110
|
+
},
|
|
5111
|
+
http: preserved.http ?? {
|
|
5112
|
+
enabled: false,
|
|
5113
|
+
port: 3767,
|
|
5114
|
+
bearer_tokens: []
|
|
5115
|
+
},
|
|
5116
|
+
fleet: preserved.fleet ?? {
|
|
5117
|
+
project_name: "shared-experience",
|
|
5118
|
+
namespace: "",
|
|
5119
|
+
api_key: ""
|
|
5120
|
+
},
|
|
5121
|
+
tool_profile: preserved.tool_profile ?? "full"
|
|
4948
5122
|
};
|
|
4949
5123
|
saveConfig(config);
|
|
4950
5124
|
const db = new MemDatabase(getDbPath());
|
|
@@ -5129,6 +5303,15 @@ function handleStatus() {
|
|
|
5129
5303
|
console.log(`
|
|
5130
5304
|
Sync`);
|
|
5131
5305
|
console.log(` Outbox: ${outbox["pending"] ?? 0} pending, ${outbox["failed"] ?? 0} failed, ${outbox["synced"] ?? 0} synced`);
|
|
5306
|
+
const syncBlock = getSyncBlockState(db);
|
|
5307
|
+
if (syncBlock.active) {
|
|
5308
|
+
console.log(` Push block: ${formatSyncBlock(syncBlock)}`);
|
|
5309
|
+
if (syncBlock.reason === "auth") {
|
|
5310
|
+
console.log(" Next step: Re-run `engrm init`, then `engrm sync resume`");
|
|
5311
|
+
} else if (syncBlock.reason === "rate_limit") {
|
|
5312
|
+
console.log(" Next step: Wait for the block to expire or retry later");
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5132
5315
|
const topFailures = getOutboxFailureSummaries(db, 2);
|
|
5133
5316
|
if (topFailures.length > 0) {
|
|
5134
5317
|
const failureSummary = topFailures.map((row) => `${classifyOutboxFailure(row.error)} ${row.count}`).join(", ");
|
|
@@ -5183,6 +5366,18 @@ function formatTimeAgo(epoch) {
|
|
|
5183
5366
|
return `${Math.floor(ago / 3600)}h ago`;
|
|
5184
5367
|
return `${Math.floor(ago / 86400)}d ago`;
|
|
5185
5368
|
}
|
|
5369
|
+
function formatTimeUntil(epoch) {
|
|
5370
|
+
const remaining = epoch - Math.floor(Date.now() / 1000);
|
|
5371
|
+
if (remaining <= 0)
|
|
5372
|
+
return "now";
|
|
5373
|
+
if (remaining < 60)
|
|
5374
|
+
return `in ${remaining}s`;
|
|
5375
|
+
if (remaining < 3600)
|
|
5376
|
+
return `in ${Math.floor(remaining / 60)}m`;
|
|
5377
|
+
if (remaining < 86400)
|
|
5378
|
+
return `in ${Math.floor(remaining / 3600)}h`;
|
|
5379
|
+
return `in ${Math.floor(remaining / 86400)}d`;
|
|
5380
|
+
}
|
|
5186
5381
|
function formatSyncTime(epochStr) {
|
|
5187
5382
|
if (!epochStr)
|
|
5188
5383
|
return "never";
|
|
@@ -5191,6 +5386,25 @@ function formatSyncTime(epochStr) {
|
|
|
5191
5386
|
return "never";
|
|
5192
5387
|
return formatTimeAgo(epoch);
|
|
5193
5388
|
}
|
|
5389
|
+
function getSyncBlockState(db) {
|
|
5390
|
+
const untilRaw = db.getSyncState("sync_push_blocked_until");
|
|
5391
|
+
const reason = db.getSyncState("sync_push_block_reason");
|
|
5392
|
+
const untilEpoch = untilRaw ? parseInt(untilRaw, 10) : NaN;
|
|
5393
|
+
if (!Number.isFinite(untilEpoch) || untilEpoch <= Math.floor(Date.now() / 1000)) {
|
|
5394
|
+
return { active: false, untilEpoch: null, reason: null };
|
|
5395
|
+
}
|
|
5396
|
+
return {
|
|
5397
|
+
active: true,
|
|
5398
|
+
untilEpoch,
|
|
5399
|
+
reason: reason && reason.length > 0 ? reason : null
|
|
5400
|
+
};
|
|
5401
|
+
}
|
|
5402
|
+
function formatSyncBlock(block) {
|
|
5403
|
+
const reasonLabel = block.reason === "auth" ? "waiting for re-auth" : block.reason === "rate_limit" ? "rate limited" : block.reason ?? "paused";
|
|
5404
|
+
if (!block.untilEpoch)
|
|
5405
|
+
return reasonLabel;
|
|
5406
|
+
return `${reasonLabel} until ${new Date(block.untilEpoch * 1000).toISOString()} (${formatTimeUntil(block.untilEpoch)})`;
|
|
5407
|
+
}
|
|
5194
5408
|
function ensureConfigDir() {
|
|
5195
5409
|
const dir = getConfigDir();
|
|
5196
5410
|
if (!existsSync8(dir)) {
|
|
@@ -5322,6 +5536,56 @@ Restart Claude Code or Codex to use the new version.`);
|
|
|
5322
5536
|
console.error("Try manually: npm install -g engrm@<version>");
|
|
5323
5537
|
}
|
|
5324
5538
|
}
|
|
5539
|
+
async function handleSync(flags) {
|
|
5540
|
+
const subcommand = flags[0];
|
|
5541
|
+
if (subcommand === "resume") {
|
|
5542
|
+
if (!configExists()) {
|
|
5543
|
+
console.error("Engrm is not configured. Run: engrm init");
|
|
5544
|
+
process.exit(1);
|
|
5545
|
+
}
|
|
5546
|
+
const config = loadConfig();
|
|
5547
|
+
if (!config.candengo_url || !config.candengo_api_key) {
|
|
5548
|
+
console.error("Authentication is not configured. Run: engrm init");
|
|
5549
|
+
process.exit(1);
|
|
5550
|
+
}
|
|
5551
|
+
try {
|
|
5552
|
+
const baseUrl = normalizeBaseUrl(config.candengo_url);
|
|
5553
|
+
const controller = new AbortController;
|
|
5554
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
5555
|
+
const res = await fetch(`${baseUrl}/v1/mem/user-settings`, {
|
|
5556
|
+
headers: { Authorization: `Bearer ${config.candengo_api_key}` },
|
|
5557
|
+
signal: controller.signal
|
|
5558
|
+
});
|
|
5559
|
+
clearTimeout(timeout);
|
|
5560
|
+
if (!res.ok) {
|
|
5561
|
+
if (res.status === 401 || res.status === 403) {
|
|
5562
|
+
console.error("Sync resume blocked: authentication is still invalid. Re-run `engrm init` first.");
|
|
5563
|
+
} else {
|
|
5564
|
+
console.error(`Sync resume blocked: authentication check returned HTTP ${res.status}.`);
|
|
5565
|
+
}
|
|
5566
|
+
process.exit(1);
|
|
5567
|
+
}
|
|
5568
|
+
const db = new MemDatabase(getDbPath());
|
|
5569
|
+
try {
|
|
5570
|
+
const recovery = resumeOutboxAfterValidatedAuth(db, config);
|
|
5571
|
+
const pending = getOutboxStats(db)["pending"] ?? 0;
|
|
5572
|
+
console.log("Sync queue resumed.");
|
|
5573
|
+
console.log(` Failed reset: ${recovery.failedReset}`);
|
|
5574
|
+
console.log(` Syncing reset: ${recovery.syncingReset}`);
|
|
5575
|
+
console.log(` Pending now: ${pending}`);
|
|
5576
|
+
} finally {
|
|
5577
|
+
db.close();
|
|
5578
|
+
}
|
|
5579
|
+
return;
|
|
5580
|
+
} catch (error) {
|
|
5581
|
+
console.error(`Sync resume failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5582
|
+
process.exit(1);
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
console.log(`Sync commands:
|
|
5586
|
+
`);
|
|
5587
|
+
console.log(" engrm sync resume Validate auth and unblock the paused sync queue");
|
|
5588
|
+
}
|
|
5325
5589
|
async function handleDoctor() {
|
|
5326
5590
|
const results = [];
|
|
5327
5591
|
const pass = (msg) => results.push({ symbol: "\u2713", message: msg, kind: "pass" });
|
|
@@ -5547,26 +5811,30 @@ async function handleDoctor() {
|
|
|
5547
5811
|
fail("Server URL not configured");
|
|
5548
5812
|
}
|
|
5549
5813
|
if (config.candengo_url && config.candengo_api_key) {
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5814
|
+
if (looksLikePlaceholderConfig(config)) {
|
|
5815
|
+
fail("Authentication is using placeholder credentials \u2014 re-run 'engrm init' to restore real account settings");
|
|
5816
|
+
} else {
|
|
5817
|
+
try {
|
|
5818
|
+
const baseUrl = normalizeBaseUrl(config.candengo_url);
|
|
5819
|
+
const controller = new AbortController;
|
|
5820
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
5821
|
+
const res = await fetch(`${baseUrl}/v1/mem/user-settings`, {
|
|
5822
|
+
headers: { Authorization: `Bearer ${config.candengo_api_key}` },
|
|
5823
|
+
signal: controller.signal
|
|
5824
|
+
});
|
|
5825
|
+
clearTimeout(timeout);
|
|
5826
|
+
if (res.ok) {
|
|
5827
|
+
const email = config.user_email ?? "configured";
|
|
5828
|
+
pass(`Authentication valid (${email})`);
|
|
5829
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
5830
|
+
fail("Authentication failed \u2014 API key may be expired");
|
|
5831
|
+
} else {
|
|
5832
|
+
fail(`Authentication check returned HTTP ${res.status}`);
|
|
5833
|
+
}
|
|
5834
|
+
} catch (err) {
|
|
5835
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5836
|
+
fail(`Authentication check failed: ${msg.includes("abort") ? "timeout (5s)" : msg}`);
|
|
5566
5837
|
}
|
|
5567
|
-
} catch (err) {
|
|
5568
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5569
|
-
fail(`Authentication check failed: ${msg.includes("abort") ? "timeout (5s)" : msg}`);
|
|
5570
5838
|
}
|
|
5571
5839
|
} else {
|
|
5572
5840
|
fail("Authentication not configured (missing URL or API key)");
|
|
@@ -5574,7 +5842,16 @@ async function handleDoctor() {
|
|
|
5574
5842
|
try {
|
|
5575
5843
|
const outbox = getOutboxStats(db);
|
|
5576
5844
|
const failedCount = outbox["failed"] ?? 0;
|
|
5577
|
-
|
|
5845
|
+
const syncBlock = getSyncBlockState(db);
|
|
5846
|
+
if (syncBlock.active) {
|
|
5847
|
+
const pending = outbox["pending"] ?? 0;
|
|
5848
|
+
warn(`Sync push is paused (${formatSyncBlock(syncBlock)}; ${pending} pending, ${failedCount} failed)`);
|
|
5849
|
+
if (syncBlock.reason === "auth") {
|
|
5850
|
+
info("Next step: re-run `engrm init`, then `engrm sync resume`");
|
|
5851
|
+
} else if (syncBlock.reason === "rate_limit") {
|
|
5852
|
+
info("Next step: wait for the pause window to expire, then try again");
|
|
5853
|
+
}
|
|
5854
|
+
} else if (failedCount > 10) {
|
|
5578
5855
|
warn(`Sync has stuck items (${failedCount} failed in outbox)`);
|
|
5579
5856
|
} else {
|
|
5580
5857
|
const pending = outbox["pending"] ?? 0;
|
|
@@ -5802,6 +6079,7 @@ function printUsage() {
|
|
|
5802
6079
|
console.log(" engrm init --config <file> Setup from JSON file");
|
|
5803
6080
|
console.log(" engrm status Show status");
|
|
5804
6081
|
console.log(" engrm update Update to latest version");
|
|
6082
|
+
console.log(" engrm sync resume Validate auth and resume paused sync queue");
|
|
5805
6083
|
console.log(" engrm packs List available starter packs");
|
|
5806
6084
|
console.log(" engrm install-pack <name> Install a starter pack");
|
|
5807
6085
|
console.log(" engrm doctor Run diagnostic checks");
|