@echomem/mcp 1.4.15 → 1.4.17
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/billing-alert.js +39 -0
- package/dist/hud/diagnostics.js +7 -11
- package/dist/hud/server.js +91 -0
- package/dist/hud/web.js +140 -4
- package/dist/index.js +63 -1
- package/dist/setup-page/client-extraction.js +27 -6
- package/dist/setup-page/styles-extraction.js +98 -29
- package/dist/setup.js +6 -2
- package/dist/v1-contract.js +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { echoConfigDir } from "./keystore.js";
|
|
4
|
+
const ALERT_FILE = "billing-alert.json";
|
|
5
|
+
const DEFAULT_MAX_AGE_MS = 31 * 24 * 60 * 60 * 1000;
|
|
6
|
+
function alertPath() {
|
|
7
|
+
return path.join(echoConfigDir(), ALERT_FILE);
|
|
8
|
+
}
|
|
9
|
+
export function writeBillingAlert(alert) {
|
|
10
|
+
try {
|
|
11
|
+
fs.mkdirSync(echoConfigDir(), { recursive: true, mode: 0o700 });
|
|
12
|
+
fs.writeFileSync(alertPath(), JSON.stringify({ ...alert, updatedAt: new Date().toISOString() }, null, 2), { mode: 0o600 });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
/* best-effort: billing UI should never break search */
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function readBillingAlert(maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
19
|
+
try {
|
|
20
|
+
const alert = JSON.parse(fs.readFileSync(alertPath(), "utf8"));
|
|
21
|
+
const updatedAt = Date.parse(alert.updatedAt || "");
|
|
22
|
+
if (!updatedAt || Date.now() - updatedAt > maxAgeMs)
|
|
23
|
+
return null;
|
|
24
|
+
if (!alert.message || !alert.pricingUrl)
|
|
25
|
+
return null;
|
|
26
|
+
return alert;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function clearBillingAlert() {
|
|
33
|
+
try {
|
|
34
|
+
fs.unlinkSync(alertPath());
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* absent is fine */
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/hud/diagnostics.js
CHANGED
|
@@ -30,21 +30,17 @@ function normalizePlan(value) {
|
|
|
30
30
|
/** Parse the account bootstrap contract without importing app-only code into the npm package. */
|
|
31
31
|
export function accountDiagnosticsFromBootstrap(payload) {
|
|
32
32
|
const root = record(payload) ?? {};
|
|
33
|
-
const entitlements = record(root.entitlements) ?? {};
|
|
34
33
|
const usage = record(root.usage) ?? {};
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const searchesRemaining =
|
|
39
|
-
? undefined
|
|
40
|
-
: limit < 0
|
|
41
|
-
? null
|
|
42
|
-
: Math.max(0, limit - searchesThisMonth);
|
|
34
|
+
const quota = record(root.memorySearchQuota) ?? {};
|
|
35
|
+
const weeklySearchLimit = number(quota.limit);
|
|
36
|
+
const searchesThisWeek = number(quota.used);
|
|
37
|
+
const searchesRemaining = quota.remaining === null ? null : number(quota.remaining);
|
|
43
38
|
return {
|
|
44
39
|
plan: normalizePlan(root.plan),
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
weeklySearchLimit,
|
|
41
|
+
searchesThisWeek,
|
|
47
42
|
searchesRemaining,
|
|
43
|
+
resetAt: text(quota.resetAt),
|
|
48
44
|
memoryCount: number(usage.memories),
|
|
49
45
|
sourceCount: number(usage.sources),
|
|
50
46
|
};
|
package/dist/hud/server.js
CHANGED
|
@@ -10,7 +10,9 @@ import { loadHudDiagnostics } from "./diagnostics.js";
|
|
|
10
10
|
import { homePath, newestFile, walkFiles } from "./fs.js";
|
|
11
11
|
import { KeyStore } from "../keystore.js";
|
|
12
12
|
import { assembleCodex, assembleClaude } from "../migrate.js";
|
|
13
|
+
import { readBillingAlert } from "../billing-alert.js";
|
|
13
14
|
const API_BASE = (process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app").replace(/\/$/, "");
|
|
15
|
+
const PRICING_URL = (process.env.ECHO_PRICING_URL || "https://echoknows.com/pricing").replace(/\/$/, "");
|
|
14
16
|
export async function createHudServer(opts = {}) {
|
|
15
17
|
const mode = opts.mode || "auto";
|
|
16
18
|
const port = opts.port ?? 17377;
|
|
@@ -43,6 +45,32 @@ export async function createHudServer(opts = {}) {
|
|
|
43
45
|
res.end(JSON.stringify(latest, null, 2));
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
48
|
+
if (url.pathname === "/billing-status") {
|
|
49
|
+
handleBillingStatus(res).catch((error) => {
|
|
50
|
+
if (res.writableEnded)
|
|
51
|
+
return;
|
|
52
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
53
|
+
res.end(JSON.stringify({ ok: false, state: "unknown", reason: error instanceof Error ? error.message : "billing status failed" }));
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (url.pathname === "/open-external") {
|
|
58
|
+
const target = url.searchParams.get("url") || "";
|
|
59
|
+
let ok = false;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(target);
|
|
62
|
+
if ((parsed.protocol === "https:" || parsed.protocol === "http:") && typeof opts.onOpenExternal === "function") {
|
|
63
|
+
opts.onOpenExternal(parsed.toString());
|
|
64
|
+
ok = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
ok = false;
|
|
69
|
+
}
|
|
70
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
71
|
+
res.end(JSON.stringify({ ok, reason: ok ? undefined : opts.onOpenExternal ? "invalid_url" : "no_desktop" }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
46
74
|
if (url.pathname === "/diagnostics") {
|
|
47
75
|
loadHudDiagnostics().then((diagnostics) => {
|
|
48
76
|
if (res.writableEnded)
|
|
@@ -264,6 +292,69 @@ export async function createHudServer(opts = {}) {
|
|
|
264
292
|
}),
|
|
265
293
|
};
|
|
266
294
|
}
|
|
295
|
+
async function handleBillingStatus(res) {
|
|
296
|
+
const respond = (obj) => {
|
|
297
|
+
if (res.writableEnded)
|
|
298
|
+
return;
|
|
299
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
300
|
+
res.end(JSON.stringify(obj));
|
|
301
|
+
};
|
|
302
|
+
const pricingUrl = appendSource(PRICING_URL, "hud");
|
|
303
|
+
const alert = readBillingAlert();
|
|
304
|
+
const token = new KeyStore().getToken();
|
|
305
|
+
if (!token) {
|
|
306
|
+
return respond({ ok: false, state: "not_connected", pricingUrl });
|
|
307
|
+
}
|
|
308
|
+
let plan = "unknown";
|
|
309
|
+
let paid = false;
|
|
310
|
+
try {
|
|
311
|
+
const response = await fetch(`${API_BASE}/api/extension/account/bootstrap`, {
|
|
312
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
313
|
+
});
|
|
314
|
+
const data = (await response.json().catch(() => ({})));
|
|
315
|
+
if (response.ok) {
|
|
316
|
+
plan = typeof data.plan === "string" ? data.plan.toLowerCase() : "free";
|
|
317
|
+
paid = ["pro", "power", "team", "enterprise"].includes(plan);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
/* Local alert still gives the HUD something useful. */
|
|
322
|
+
}
|
|
323
|
+
if (alert && (alert.kind === "quota_exceeded" || !paid)) {
|
|
324
|
+
return respond({
|
|
325
|
+
ok: true,
|
|
326
|
+
state: alert.kind,
|
|
327
|
+
plan: alert.plan || plan,
|
|
328
|
+
message: alert.kind === "quota_exceeded" ? "Recall limit reached." : "Recall needs a plan.",
|
|
329
|
+
detail: alert.message,
|
|
330
|
+
code: alert.code,
|
|
331
|
+
pricingUrl: appendSource(alert.pricingUrl || pricingUrl, "hud"),
|
|
332
|
+
updatedAt: alert.updatedAt,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (!paid && plan === "free") {
|
|
336
|
+
return respond({
|
|
337
|
+
ok: true,
|
|
338
|
+
state: "plan_required",
|
|
339
|
+
plan,
|
|
340
|
+
message: "Recall needs a plan.",
|
|
341
|
+
detail: "Search and recall require Echo Pro or Power. Saving conversations keeps working.",
|
|
342
|
+
pricingUrl,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
respond({ ok: true, state: "ok", plan, paid, pricingUrl });
|
|
346
|
+
}
|
|
347
|
+
function appendSource(url, source) {
|
|
348
|
+
try {
|
|
349
|
+
const parsed = new URL(url);
|
|
350
|
+
if (!parsed.searchParams.has("source"))
|
|
351
|
+
parsed.searchParams.set("source", source);
|
|
352
|
+
return parsed.toString();
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return url;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
267
358
|
async function handleCheckpointStatus(latest, res) {
|
|
268
359
|
const respond = (obj) => {
|
|
269
360
|
if (res.writableEnded)
|
package/dist/hud/web.js
CHANGED
|
@@ -16,6 +16,10 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
16
16
|
--brick: #c7372f;
|
|
17
17
|
--green: #43d17a;
|
|
18
18
|
--amber: #e9b949;
|
|
19
|
+
--billing-free-bg: #fff8d9;
|
|
20
|
+
--billing-free-border: rgba(190, 128, 19, 0.36);
|
|
21
|
+
--billing-free: #c08412;
|
|
22
|
+
--billing-free-dark: #87560d;
|
|
19
23
|
--line: rgba(26, 58, 143, 0.22);
|
|
20
24
|
--track: rgba(26, 58, 143, 0.1);
|
|
21
25
|
--shadow: drop-shadow(0 2px 7px rgba(26, 58, 143, 0.12));
|
|
@@ -530,6 +534,75 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
530
534
|
background: rgba(199, 55, 47, 0.07);
|
|
531
535
|
color: #7a241e;
|
|
532
536
|
}
|
|
537
|
+
.billing-banner {
|
|
538
|
+
display: none;
|
|
539
|
+
margin: 0 0 12px;
|
|
540
|
+
border: 1.4px solid var(--billing-free-border);
|
|
541
|
+
border-radius: 12px;
|
|
542
|
+
background: linear-gradient(180deg, var(--billing-free-bg), #fff);
|
|
543
|
+
padding: 10px;
|
|
544
|
+
color: var(--muted);
|
|
545
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.82);
|
|
546
|
+
}
|
|
547
|
+
.billing-banner.limit {
|
|
548
|
+
border-color: rgba(255, 95, 87, 0.45);
|
|
549
|
+
background: linear-gradient(180deg, #fff3f2, #fff);
|
|
550
|
+
}
|
|
551
|
+
.billing-top {
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: center;
|
|
554
|
+
gap: 8px;
|
|
555
|
+
}
|
|
556
|
+
.billing-dot {
|
|
557
|
+
width: 10px;
|
|
558
|
+
height: 10px;
|
|
559
|
+
flex: 0 0 auto;
|
|
560
|
+
border-radius: 999px;
|
|
561
|
+
background: var(--billing-free);
|
|
562
|
+
box-shadow: 0 0 0 4px rgba(190, 128, 19, 0.12);
|
|
563
|
+
}
|
|
564
|
+
.billing-banner.limit .billing-dot {
|
|
565
|
+
background: #ff5f57;
|
|
566
|
+
box-shadow: 0 0 0 4px rgba(255, 95, 87, 0.12);
|
|
567
|
+
}
|
|
568
|
+
.billing-title {
|
|
569
|
+
min-width: 0;
|
|
570
|
+
flex: 1;
|
|
571
|
+
color: var(--billing-free-dark);
|
|
572
|
+
font-size: 13px;
|
|
573
|
+
font-weight: 900;
|
|
574
|
+
line-height: 1.15;
|
|
575
|
+
}
|
|
576
|
+
.billing-banner.limit .billing-title { color: #7a241e; }
|
|
577
|
+
.billing-copy {
|
|
578
|
+
display: block;
|
|
579
|
+
margin: 8px 0 0 18px;
|
|
580
|
+
font-size: 11.5px;
|
|
581
|
+
line-height: 1.35;
|
|
582
|
+
font-weight: 650;
|
|
583
|
+
}
|
|
584
|
+
.billing-link {
|
|
585
|
+
display: inline-flex;
|
|
586
|
+
align-items: center;
|
|
587
|
+
justify-content: center;
|
|
588
|
+
min-height: 28px;
|
|
589
|
+
border: 1px solid var(--billing-free-dark);
|
|
590
|
+
border-radius: 999px;
|
|
591
|
+
background: var(--billing-free);
|
|
592
|
+
color: #fff;
|
|
593
|
+
padding: 0 13px;
|
|
594
|
+
font-family: inherit;
|
|
595
|
+
font-size: 11px;
|
|
596
|
+
font-weight: 900;
|
|
597
|
+
cursor: pointer;
|
|
598
|
+
box-shadow: 0 12px 20px -16px rgba(135, 86, 13, 0.95);
|
|
599
|
+
}
|
|
600
|
+
.billing-banner.limit .billing-link {
|
|
601
|
+
border-color: #7a241e;
|
|
602
|
+
background: #7a241e;
|
|
603
|
+
box-shadow: 0 12px 20px -16px rgba(122, 36, 30, 0.95);
|
|
604
|
+
}
|
|
605
|
+
.billing-link:hover { filter: brightness(0.96); }
|
|
533
606
|
button.renew-btn {
|
|
534
607
|
display: none;
|
|
535
608
|
width: 100%;
|
|
@@ -697,6 +770,14 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
697
770
|
<button class="pin-chip" id="pinchip" type="button" title="Return to auto-follow">pinned x</button>
|
|
698
771
|
<button class="jump-chip" id="jumpchip" type="button" title="Open this conversation">↗</button>
|
|
699
772
|
</div>
|
|
773
|
+
<div class="billing-banner" id="billingbanner">
|
|
774
|
+
<div class="billing-top">
|
|
775
|
+
<span class="billing-dot"></span>
|
|
776
|
+
<strong class="billing-title" id="billingtitle"></strong>
|
|
777
|
+
<button class="billing-link" id="billinglink" type="button">Open pricing</button>
|
|
778
|
+
</div>
|
|
779
|
+
<span class="billing-copy" id="billingcopy"></span>
|
|
780
|
+
</div>
|
|
700
781
|
<div class="state-row">
|
|
701
782
|
<span class="state-dot" id="stateDot"></span>
|
|
702
783
|
<span class="state-word" id="stateWord">--%</span>
|
|
@@ -745,6 +826,10 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
745
826
|
const whyBtn = document.getElementById("whybtn");
|
|
746
827
|
const evidence = document.getElementById("evidence");
|
|
747
828
|
const nudge = document.getElementById("nudge");
|
|
829
|
+
const billingBanner = document.getElementById("billingbanner");
|
|
830
|
+
const billingTitle = document.getElementById("billingtitle");
|
|
831
|
+
const billingCopy = document.getElementById("billingcopy");
|
|
832
|
+
const billingLink = document.getElementById("billinglink");
|
|
748
833
|
const renewBtn = document.getElementById("renewbtn");
|
|
749
834
|
const renewResult = document.getElementById("renewresult");
|
|
750
835
|
const renewMsg = document.getElementById("renewmsg");
|
|
@@ -773,6 +858,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
773
858
|
let renewStartMs = 0;
|
|
774
859
|
let renewEta = null;
|
|
775
860
|
let renewEstimateSeq = 0;
|
|
861
|
+
let billingStatus = null;
|
|
776
862
|
let diagnosticsLoaded = false;
|
|
777
863
|
let diagnosticsInFlight = false;
|
|
778
864
|
const CHECKPOINT_CACHE_KEY = "echomemHudCheckpointCacheV2";
|
|
@@ -807,12 +893,13 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
807
893
|
const lines = [];
|
|
808
894
|
if (account.state === "connected") {
|
|
809
895
|
lines.push(diagnosticsLine("account", planLabel(account.plan) + " · authenticated"));
|
|
810
|
-
if (typeof account.
|
|
811
|
-
const limit = account.
|
|
896
|
+
if (typeof account.searchesThisWeek === "number" && typeof account.weeklySearchLimit === "number") {
|
|
897
|
+
const limit = account.weeklySearchLimit < 0 ? "Unlimited" : String(account.weeklySearchLimit);
|
|
812
898
|
const remaining = account.searchesRemaining === null ? "" : (typeof account.searchesRemaining === "number" ? " · " + account.searchesRemaining + " left" : "");
|
|
813
|
-
lines.push(diagnosticsLine("searches", account.
|
|
899
|
+
lines.push(diagnosticsLine("searches", account.searchesThisWeek + " / " + limit + " UTC week" + remaining));
|
|
900
|
+
if (account.resetAt) lines.push(diagnosticsLine("resets", account.resetAt));
|
|
814
901
|
} else {
|
|
815
|
-
lines.push(diagnosticsLine("searches", "
|
|
902
|
+
lines.push(diagnosticsLine("searches", "weekly quota not reported"));
|
|
816
903
|
}
|
|
817
904
|
} else if (account.state === "not_logged_in") {
|
|
818
905
|
lines.push(diagnosticsLine("account", "not logged in", true));
|
|
@@ -984,6 +1071,19 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
984
1071
|
e.stopPropagation();
|
|
985
1072
|
selectSession("auto");
|
|
986
1073
|
});
|
|
1074
|
+
if (billingLink) billingLink.addEventListener("click", (e) => {
|
|
1075
|
+
e.stopPropagation();
|
|
1076
|
+
const url = billingStatus && billingStatus.pricingUrl;
|
|
1077
|
+
if (!url) return;
|
|
1078
|
+
fetch("/open-external?url=" + encodeURIComponent(url))
|
|
1079
|
+
.then((res) => res.json())
|
|
1080
|
+
.then((data) => {
|
|
1081
|
+
if (!data || !data.ok) window.open(url, "_blank", "noopener");
|
|
1082
|
+
})
|
|
1083
|
+
.catch(() => {
|
|
1084
|
+
try { window.open(url, "_blank", "noopener"); } catch {}
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
987
1087
|
function selectSession(id) {
|
|
988
1088
|
if (!id) return;
|
|
989
1089
|
fetch("/select?id=" + encodeURIComponent(id))
|
|
@@ -1479,6 +1579,19 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1479
1579
|
const events = new EventSource("/events");
|
|
1480
1580
|
events.onmessage = (event) => render(JSON.parse(event.data));
|
|
1481
1581
|
fetch("/state").then((r) => r.json()).then(render).catch(() => {});
|
|
1582
|
+
refreshBillingStatus();
|
|
1583
|
+
setInterval(refreshBillingStatus, 30000);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function refreshBillingStatus() {
|
|
1587
|
+
try {
|
|
1588
|
+
const res = await fetch("/billing-status");
|
|
1589
|
+
billingStatus = await res.json();
|
|
1590
|
+
} catch {
|
|
1591
|
+
billingStatus = null;
|
|
1592
|
+
}
|
|
1593
|
+
renderBillingStatus();
|
|
1594
|
+
syncHeight();
|
|
1482
1595
|
}
|
|
1483
1596
|
|
|
1484
1597
|
function render(state) {
|
|
@@ -1536,6 +1649,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1536
1649
|
jumpChip.style.display = activeId ? "inline-block" : "none";
|
|
1537
1650
|
}
|
|
1538
1651
|
renderWhy(score, oldPct, repeatCount, sat, health, color);
|
|
1652
|
+
renderBillingStatus();
|
|
1539
1653
|
pathEl.textContent = "";
|
|
1540
1654
|
currentSrc = score.sourcePath || null;
|
|
1541
1655
|
if (revealEl) revealEl.style.display = !DEMO_CLEAN && currentSrc ? "inline-block" : "none";
|
|
@@ -1596,6 +1710,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1596
1710
|
whyOpen = false;
|
|
1597
1711
|
hud.classList.remove("show-why");
|
|
1598
1712
|
nudge.style.display = "none";
|
|
1713
|
+
renderBillingStatus();
|
|
1599
1714
|
if (renewBtn) renewBtn.style.display = "none";
|
|
1600
1715
|
clearRenewResult();
|
|
1601
1716
|
pathEl.textContent = "";
|
|
@@ -1617,6 +1732,27 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1617
1732
|
if (mrepoLine) mrepoLine.style.display = repo ? "flex" : "none";
|
|
1618
1733
|
}
|
|
1619
1734
|
|
|
1735
|
+
function renderBillingStatus() {
|
|
1736
|
+
if (!billingBanner) return;
|
|
1737
|
+
const state = billingStatus && billingStatus.state;
|
|
1738
|
+
const blocked = state === "plan_required" || state === "quota_exceeded" || state === "billing_attention";
|
|
1739
|
+
billingBanner.style.display = blocked ? "block" : "none";
|
|
1740
|
+
billingBanner.classList.toggle("limit", state === "quota_exceeded");
|
|
1741
|
+
if (!blocked) return;
|
|
1742
|
+
const title = state === "quota_exceeded"
|
|
1743
|
+
? "Recall limit reached"
|
|
1744
|
+
: state === "plan_required"
|
|
1745
|
+
? "Recall paused · Free plan"
|
|
1746
|
+
: billingStatus.message || "Billing needs attention";
|
|
1747
|
+
const detail = state === "quota_exceeded"
|
|
1748
|
+
? "You used all Pro searches this month. Upgrade or wait for reset."
|
|
1749
|
+
: state === "plan_required"
|
|
1750
|
+
? "Search memory requires Pro or Power. Saving still works."
|
|
1751
|
+
: billingStatus.detail || "Open pricing to restore memory search.";
|
|
1752
|
+
if (billingTitle) billingTitle.textContent = title;
|
|
1753
|
+
if (billingCopy) billingCopy.textContent = detail;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1620
1756
|
function renderWhy(score, oldPct, repeatCount, sat, health, color) {
|
|
1621
1757
|
const rows = [];
|
|
1622
1758
|
if (sat > 0) rows.push(["context", score.modelContextWindow ? sat + "% full · " + fmt(score.ctTokens) : fmt(score.ctTokens) + " tokens"]);
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,10 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
13
13
|
import { fetchEncryptionConfig, decryptMemoryFields } from "./encryption.js";
|
|
14
14
|
import { runCli } from "./setup.js";
|
|
15
15
|
import { MCP_PACKAGE_VERSION, MCP_SERVER_INSTRUCTIONS } from "./package-metadata.js";
|
|
16
|
+
import { clearBillingAlert, writeBillingAlert } from "./billing-alert.js";
|
|
16
17
|
import { checkLatestUpdateStatus, formatUpdateNotice, formatUpdateStatusText, startBackgroundUpdateCheck, } from "./update-check.js";
|
|
17
18
|
const ECHO_API_BASE_URL = process.env.ECHO_API_BASE_URL || "https://echo-mem-chrome.vercel.app";
|
|
19
|
+
const ECHO_PRICING_URL = process.env.ECHO_PRICING_URL || "https://echoknows.com/pricing";
|
|
18
20
|
/** Thrown when no API token is present yet — the model gets a "run login" nudge, not a hard error. */
|
|
19
21
|
class NoTokenError extends Error {
|
|
20
22
|
}
|
|
@@ -80,6 +82,59 @@ function describeError(error) {
|
|
|
80
82
|
const fallback = stringifyErrorValue(error);
|
|
81
83
|
return fallback || "Unknown error";
|
|
82
84
|
}
|
|
85
|
+
function appendPricingSource(url, source) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = new URL(url);
|
|
88
|
+
if (!parsed.searchParams.has("source")) {
|
|
89
|
+
parsed.searchParams.set("source", source);
|
|
90
|
+
}
|
|
91
|
+
return parsed.toString();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return url;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function formatUpgradeRequiredResult(error) {
|
|
98
|
+
if (!axios.isAxiosError(error))
|
|
99
|
+
return null;
|
|
100
|
+
const status = error.response?.status;
|
|
101
|
+
if (status !== 402 && status !== 403 && status !== 429)
|
|
102
|
+
return null;
|
|
103
|
+
const data = error.response?.data;
|
|
104
|
+
if (!isRecord(data))
|
|
105
|
+
return null;
|
|
106
|
+
const upgradeUrl = readString(data, "upgradeUrl")
|
|
107
|
+
?? readString(data, "pricingUrl")
|
|
108
|
+
?? readString(data, "url")
|
|
109
|
+
?? ECHO_PRICING_URL;
|
|
110
|
+
const message = readString(data, "message")
|
|
111
|
+
?? "Memory search and recall require an Echo Pro or Power plan.";
|
|
112
|
+
const code = readString(data, "error") ?? readString(data, "code");
|
|
113
|
+
const plan = readString(data, "plan");
|
|
114
|
+
const pricingUrl = appendPricingSource(upgradeUrl, "mcp_search");
|
|
115
|
+
const normalizedCode = String(code || "").toUpperCase();
|
|
116
|
+
const kind = status === 429 || normalizedCode.includes("QUOTA") || normalizedCode.includes("LIMIT")
|
|
117
|
+
? "quota_exceeded"
|
|
118
|
+
: status === 402 || normalizedCode.includes("REQUIRES_PLAN")
|
|
119
|
+
? "plan_required"
|
|
120
|
+
: "billing_attention";
|
|
121
|
+
writeBillingAlert({
|
|
122
|
+
kind,
|
|
123
|
+
code,
|
|
124
|
+
message,
|
|
125
|
+
plan,
|
|
126
|
+
pricingUrl,
|
|
127
|
+
});
|
|
128
|
+
return [
|
|
129
|
+
"ACTION REQUIRED: tell the user to start a 7-day Pro trial or choose a subscription at this exact URL:",
|
|
130
|
+
pricingUrl,
|
|
131
|
+
"When replying, include the exact URL above. Do not reply only with \"connect\" or \"upgrade\".",
|
|
132
|
+
"",
|
|
133
|
+
message,
|
|
134
|
+
"",
|
|
135
|
+
code ? `Reason: ${code}.` : "",
|
|
136
|
+
].filter(Boolean).join("\n");
|
|
137
|
+
}
|
|
83
138
|
/** Map a thrown error to the telemetry error_kind taxonomy. */
|
|
84
139
|
function classifyError(error) {
|
|
85
140
|
if (error instanceof NoTokenError)
|
|
@@ -108,7 +163,7 @@ function readNumber(record, key) {
|
|
|
108
163
|
function errorCodeFrom(value) {
|
|
109
164
|
if (!isRecord(value))
|
|
110
165
|
return undefined;
|
|
111
|
-
return readString(value, "code") || readString(value, "errorCode");
|
|
166
|
+
return readString(value, "error") || readString(value, "code") || readString(value, "errorCode");
|
|
112
167
|
}
|
|
113
168
|
function compactOneLine(value, maxLength) {
|
|
114
169
|
if (!value)
|
|
@@ -1150,6 +1205,12 @@ class EchoMemMCPServer {
|
|
|
1150
1205
|
if (error instanceof McpError) {
|
|
1151
1206
|
throw error;
|
|
1152
1207
|
}
|
|
1208
|
+
const upgradeRequired = formatUpgradeRequiredResult(error);
|
|
1209
|
+
if (upgradeRequired) {
|
|
1210
|
+
return {
|
|
1211
|
+
content: [{ type: "text", text: upgradeRequired }],
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1153
1214
|
return {
|
|
1154
1215
|
content: [{ type: "text", text: `Error: ${describeError(error)}` }],
|
|
1155
1216
|
isError: true,
|
|
@@ -1206,6 +1267,7 @@ class EchoMemMCPServer {
|
|
|
1206
1267
|
rec.http_status = trace.httpStatus;
|
|
1207
1268
|
rec.error_code = trace.errorCode;
|
|
1208
1269
|
});
|
|
1270
|
+
clearBillingAlert();
|
|
1209
1271
|
if (rec) {
|
|
1210
1272
|
// "Which memories were used": topic keys + delivered context size, for the comparison dataset.
|
|
1211
1273
|
const mems = Array.isArray(result?.memories) ? result.memories : [];
|
|
@@ -457,18 +457,24 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
457
457
|
function recallGateUnlocked() {
|
|
458
458
|
return recallGateSkipped || paidRecallPlan(billingStatus && billingStatus.plan);
|
|
459
459
|
}
|
|
460
|
+
function recallTrialAvailable() {
|
|
461
|
+
return !billingStatus || billingStatus.trialAvailable !== false;
|
|
462
|
+
}
|
|
460
463
|
function recallGateBlock() {
|
|
461
464
|
var unlocked = recallGateUnlocked();
|
|
465
|
+
var trialAvailable = recallTrialAvailable();
|
|
462
466
|
return '<div class="recallGateBlock">' +
|
|
463
467
|
'<section class="recallGate' + (unlocked ? " is-hidden" : "") + '" id="recallGate">' +
|
|
464
|
-
'<p class="recallEyebrow">
|
|
465
|
-
'<h3>
|
|
466
|
-
'<p>
|
|
468
|
+
'<p class="recallEyebrow"><i></i>Premium recall</p>' +
|
|
469
|
+
'<h3 id="recallGateTitle">' + (trialAvailable ? "Unlock memory search for your agents." : "Ready to turn recall back on?") + '</h3>' +
|
|
470
|
+
'<p id="recallGateBody">' + (trialAvailable ? "Start a 14-day trial and let Codex, Claude, and MCP clients search what Echo saved. Saving conversations keeps working either way." : "Choose Pro or Power to let Codex, Claude, and MCP clients search what Echo saved. Saving conversations keeps working either way.") + '</p>' +
|
|
471
|
+
'<div class="recallTrial' + (trialAvailable ? "" : " is-hidden") + '" id="recallTrialBadge" aria-label="14 days free"><strong>14</strong><span>days free<br>then choose what fits</span></div>' +
|
|
472
|
+
'<p class="recallUsedNote' + (trialAvailable ? " is-hidden" : "") + '" id="recallUsedNote">Your trial was already used. You can still unlock recall by choosing Pro or Power.</p>' +
|
|
467
473
|
'<div class="recallGateActions">' +
|
|
468
|
-
'<button type="button" class="primary" id="recallStartTrial">Compare
|
|
469
|
-
'<button type="button" class="
|
|
474
|
+
'<button type="button" class="primary" id="recallStartTrial">' + (trialAvailable ? "Compare Pro and Power" : "See pricing") + '</button>' +
|
|
475
|
+
'<button type="button" class="textButton" id="recallSkip">Skip recall for now</button>' +
|
|
470
476
|
'</div>' +
|
|
471
|
-
'<p class="recallFine">Card required for trial. Cancel anytime
|
|
477
|
+
'<p class="recallFine" id="recallFine">' + (trialAvailable ? "Card required for trial. Cancel anytime." : "Cancel anytime.") + '</p>' +
|
|
472
478
|
'<p class="launchStatus" id="recallGateStatus"></p>' +
|
|
473
479
|
'</section>' +
|
|
474
480
|
'<div id="recallTryWrap" class="recallTryWrap' + (unlocked ? "" : " is-hidden") + '">' +
|
|
@@ -489,6 +495,21 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
489
495
|
if (gate) gate.classList.toggle("is-hidden", unlocked);
|
|
490
496
|
if (wrap) wrap.classList.toggle("is-hidden", !unlocked);
|
|
491
497
|
if (skipNote) skipNote.classList.toggle("is-hidden", !recallGateSkipped);
|
|
498
|
+
var trialAvailable = recallTrialAvailable();
|
|
499
|
+
var badge = document.getElementById("recallTrialBadge");
|
|
500
|
+
if (badge) badge.classList.toggle("is-hidden", !trialAvailable);
|
|
501
|
+
var usedNote = document.getElementById("recallUsedNote");
|
|
502
|
+
if (usedNote) usedNote.classList.toggle("is-hidden", trialAvailable);
|
|
503
|
+
var title = document.getElementById("recallGateTitle");
|
|
504
|
+
if (title) title.textContent = trialAvailable ? "Unlock memory search for your agents." : "Ready to turn recall back on?";
|
|
505
|
+
var body = document.getElementById("recallGateBody");
|
|
506
|
+
if (body) body.textContent = trialAvailable
|
|
507
|
+
? "Start a 14-day trial and let Codex, Claude, and MCP clients search what Echo saved. Saving conversations keeps working either way."
|
|
508
|
+
: "Choose Pro or Power to let Codex, Claude, and MCP clients search what Echo saved. Saving conversations keeps working either way.";
|
|
509
|
+
var cta = document.getElementById("recallStartTrial");
|
|
510
|
+
if (cta) cta.textContent = trialAvailable ? "Compare Pro and Power" : "See pricing";
|
|
511
|
+
var fine = document.getElementById("recallFine");
|
|
512
|
+
if (fine) fine.textContent = trialAvailable ? "Card required for trial. Cancel anytime." : "Cancel anytime.";
|
|
492
513
|
}
|
|
493
514
|
async function refreshBillingStatus() {
|
|
494
515
|
try {
|
|
@@ -683,62 +683,118 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
683
683
|
.exOverdue { margin: 0; max-width: 620px; border: 1px solid rgba(196, 148, 42, 0.45); background: rgba(233, 185, 73, 0.12); border-radius: 8px; padding: 12px 14px; font-size: 13px; line-height: 1.5; color: var(--echo-ink-text); }
|
|
684
684
|
.exOverdue strong { display: block; margin-bottom: 4px; color: var(--echo-ink-primary); }
|
|
685
685
|
.is-hidden { display: none !important; }
|
|
686
|
-
.recallGateBlock { width: min(
|
|
686
|
+
.recallGateBlock { width: min(680px, 100%); margin: 2px auto 0; }
|
|
687
687
|
.recallGate {
|
|
688
688
|
width: 100%;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
689
|
+
position: relative;
|
|
690
|
+
overflow: hidden;
|
|
691
|
+
border: 1px solid rgba(33,64,154,0.42);
|
|
692
|
+
border-radius: 12px;
|
|
693
|
+
background:
|
|
694
|
+
linear-gradient(180deg, rgba(239,244,255,0.98) 0%, rgba(255,255,255,0.99) 46%),
|
|
695
|
+
var(--echo-paper-white);
|
|
696
|
+
padding: clamp(24px, 4vw, 36px);
|
|
697
|
+
box-shadow: 0 28px 60px -42px rgba(33,64,154,0.48);
|
|
694
698
|
text-align: left;
|
|
695
699
|
}
|
|
700
|
+
.recallGate::before {
|
|
701
|
+
content: "";
|
|
702
|
+
position: absolute;
|
|
703
|
+
inset: 0 0 auto 0;
|
|
704
|
+
height: 8px;
|
|
705
|
+
background: linear-gradient(90deg, #18317d, #4b6bd4, var(--echo-ink-primary));
|
|
706
|
+
}
|
|
696
707
|
.recallEyebrow {
|
|
697
|
-
|
|
708
|
+
display: inline-flex;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: 8px;
|
|
711
|
+
margin: 0 0 14px;
|
|
712
|
+
border: 1px solid rgba(33,64,154,0.18);
|
|
713
|
+
border-radius: 999px;
|
|
714
|
+
background: #eef3ff;
|
|
715
|
+
padding: 7px 11px;
|
|
698
716
|
font-family: var(--echo-font-mono);
|
|
699
717
|
font-size: 11px;
|
|
700
|
-
font-weight:
|
|
701
|
-
letter-spacing: 0.
|
|
718
|
+
font-weight: 900;
|
|
719
|
+
letter-spacing: 0.06em;
|
|
702
720
|
text-transform: uppercase;
|
|
703
|
-
color:
|
|
721
|
+
color: #18317d;
|
|
722
|
+
}
|
|
723
|
+
.recallEyebrow i {
|
|
724
|
+
width: 8px;
|
|
725
|
+
height: 8px;
|
|
726
|
+
border-radius: 999px;
|
|
727
|
+
background: var(--echo-ink-primary);
|
|
728
|
+
box-shadow: 0 0 0 4px rgba(33,64,154,0.1);
|
|
704
729
|
}
|
|
705
730
|
.recallGate h3 {
|
|
706
731
|
margin: 0;
|
|
707
|
-
max-width:
|
|
732
|
+
max-width: 520px;
|
|
708
733
|
font-family: var(--echo-font-brand);
|
|
709
|
-
font-size: clamp(
|
|
710
|
-
line-height:
|
|
734
|
+
font-size: clamp(34px, 4.6vw, 48px);
|
|
735
|
+
line-height: 1.02;
|
|
711
736
|
font-weight: 900;
|
|
712
|
-
color: var(--echo-ink-
|
|
737
|
+
color: var(--echo-ink-text);
|
|
713
738
|
letter-spacing: 0;
|
|
714
739
|
}
|
|
715
740
|
.recallGate p {
|
|
716
|
-
max-width:
|
|
741
|
+
max-width: 560px;
|
|
717
742
|
margin: 14px 0 0;
|
|
718
|
-
font-size:
|
|
743
|
+
font-size: 16px;
|
|
719
744
|
line-height: 1.55;
|
|
720
745
|
color: var(--echo-ink-mute);
|
|
721
746
|
}
|
|
722
|
-
.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
border
|
|
729
|
-
|
|
730
|
-
|
|
747
|
+
.recallTrial {
|
|
748
|
+
margin-top: 18px;
|
|
749
|
+
display: inline-grid;
|
|
750
|
+
grid-template-columns: auto 1fr;
|
|
751
|
+
gap: 10px;
|
|
752
|
+
align-items: center;
|
|
753
|
+
border: 1px solid rgba(33,64,154,0.18);
|
|
754
|
+
border-radius: 10px;
|
|
755
|
+
background: #f3f6ff;
|
|
756
|
+
padding: 10px 12px;
|
|
757
|
+
color: #18317d;
|
|
758
|
+
}
|
|
759
|
+
.recallTrial strong {
|
|
760
|
+
font-size: 28px;
|
|
761
|
+
line-height: 1;
|
|
762
|
+
color: #18317d;
|
|
763
|
+
}
|
|
764
|
+
.recallTrial span {
|
|
765
|
+
font-size: 13px;
|
|
766
|
+
line-height: 1.25;
|
|
767
|
+
font-weight: 800;
|
|
768
|
+
}
|
|
769
|
+
.recallUsedNote {
|
|
770
|
+
margin-top: 18px;
|
|
771
|
+
border-left: 4px solid var(--echo-ink-primary);
|
|
772
|
+
background: #f3f6ff;
|
|
773
|
+
padding: 11px 13px;
|
|
774
|
+
color: #18317d;
|
|
775
|
+
font-size: 13px;
|
|
776
|
+
line-height: 1.45;
|
|
777
|
+
font-weight: 700;
|
|
731
778
|
}
|
|
732
779
|
.recallGateActions {
|
|
733
780
|
display: flex;
|
|
734
781
|
flex-wrap: wrap;
|
|
735
|
-
gap:
|
|
782
|
+
gap: 14px;
|
|
736
783
|
align-items: center;
|
|
737
|
-
margin-top:
|
|
784
|
+
margin-top: 24px;
|
|
738
785
|
}
|
|
739
786
|
.recallGateActions button {
|
|
740
|
-
min-height:
|
|
741
|
-
|
|
787
|
+
min-height: 50px;
|
|
788
|
+
}
|
|
789
|
+
.recallGateActions .primary {
|
|
790
|
+
min-width: 0;
|
|
791
|
+
border-color: #18317d;
|
|
792
|
+
background: var(--echo-ink-primary);
|
|
793
|
+
color: #fff;
|
|
794
|
+
box-shadow: 0 18px 34px -26px rgba(33,64,154,0.85);
|
|
795
|
+
}
|
|
796
|
+
.recallGateActions .primary:hover {
|
|
797
|
+
background: #18317d;
|
|
742
798
|
}
|
|
743
799
|
.recallGate .recallFine {
|
|
744
800
|
margin-top: 10px;
|
|
@@ -746,6 +802,19 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
746
802
|
font-size: 11px;
|
|
747
803
|
color: var(--echo-ink-faint);
|
|
748
804
|
}
|
|
805
|
+
@media (max-width: 720px) {
|
|
806
|
+
.recallGateActions .primary { width: 100%; }
|
|
807
|
+
}
|
|
808
|
+
.recallGate code,
|
|
809
|
+
.recallSkipNote code {
|
|
810
|
+
font-family: var(--echo-font-mono);
|
|
811
|
+
font-size: 0.92em;
|
|
812
|
+
background: var(--echo-paper-soft);
|
|
813
|
+
border: 1px solid var(--echo-line-soft);
|
|
814
|
+
border-radius: 5px;
|
|
815
|
+
padding: 1px 5px;
|
|
816
|
+
color: var(--echo-ink-primary);
|
|
817
|
+
}
|
|
749
818
|
.recallTryWrap { width: 100%; }
|
|
750
819
|
.recallSkipNote {
|
|
751
820
|
margin: 0 0 12px;
|
package/dist/setup.js
CHANGED
|
@@ -1257,20 +1257,24 @@ export function startCallbackServer(opts = {}) {
|
|
|
1257
1257
|
const token = new KeyStore().getToken();
|
|
1258
1258
|
const pricingUrl = `${PRICING_URL}?source=mcp_onboarding`;
|
|
1259
1259
|
if (!token) {
|
|
1260
|
-
json(res, 200, { plan: "free", paid: false, pricingUrl });
|
|
1260
|
+
json(res, 200, { plan: "free", paid: false, trialAvailable: true, trialUsed: false, pricingUrl });
|
|
1261
1261
|
return;
|
|
1262
1262
|
}
|
|
1263
1263
|
try {
|
|
1264
1264
|
const response = await authedAxios(token).get("/api/extension/account/bootstrap", { timeout: 6000 });
|
|
1265
1265
|
const plan = (asString(response.data?.plan) || "free").toLowerCase();
|
|
1266
|
+
const trialUsed = response.data?.billing?.trialUsed === true;
|
|
1267
|
+
const trialAvailable = response.data?.billing?.trialAvailable !== false;
|
|
1266
1268
|
json(res, 200, {
|
|
1267
1269
|
plan,
|
|
1268
1270
|
paid: ["pro", "power", "team", "enterprise"].includes(plan),
|
|
1271
|
+
trialAvailable,
|
|
1272
|
+
trialUsed,
|
|
1269
1273
|
pricingUrl,
|
|
1270
1274
|
});
|
|
1271
1275
|
}
|
|
1272
1276
|
catch {
|
|
1273
|
-
json(res, 200, { plan: "unknown", paid: false, pricingUrl });
|
|
1277
|
+
json(res, 200, { plan: "unknown", paid: false, trialAvailable: true, trialUsed: false, pricingUrl });
|
|
1274
1278
|
}
|
|
1275
1279
|
return;
|
|
1276
1280
|
}
|
package/dist/v1-contract.js
CHANGED
|
@@ -109,6 +109,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
109
109
|
const map = opts.map?.trim();
|
|
110
110
|
const updateNotice = opts.updateNotice?.trim();
|
|
111
111
|
const paidRecallNote = "Requires Echo Pro or Power; saving conversations and memory extraction remain included.";
|
|
112
|
+
const searchBillingReplyInstruction = "If search returns an ACTION REQUIRED subscription message, tell the user to start their trial or subscription and include the exact URL from that result verbatim. Do not respond only with \"connect\" or \"upgrade\".";
|
|
112
113
|
const updateSection = updateNotice ? `\n\nUPDATE NOTICE: ${updateNotice}` : "";
|
|
113
114
|
const mapSection = map
|
|
114
115
|
? `\n\nThis user's EchoMem currently covers these topics (a relevance guide — recall when the task relates to one of them):\n${map}\n`
|
|
@@ -116,7 +117,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
116
117
|
return [
|
|
117
118
|
{
|
|
118
119
|
name: canonicalToolNames.search,
|
|
119
|
-
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${paidRecallNote}${mapSection}\nReturns the ranked memories; set includeAnswer=true only if you need the legacy synthesized answer. Current time: ${currentTime}.${updateSection}`),
|
|
120
|
+
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${paidRecallNote} ${searchBillingReplyInstruction}${mapSection}\nReturns the ranked memories; set includeAnswer=true only if you need the legacy synthesized answer. Current time: ${currentTime}.${updateSection}`),
|
|
120
121
|
inputSchema: {
|
|
121
122
|
type: "object",
|
|
122
123
|
properties: {
|
|
@@ -139,7 +140,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
139
140
|
},
|
|
140
141
|
{
|
|
141
142
|
name: "search_memories_by_description_semantic",
|
|
142
|
-
description: `Legacy alias for search_memories. ${paidRecallNote}`,
|
|
143
|
+
description: `Legacy alias for search_memories. ${paidRecallNote} ${searchBillingReplyInstruction}`,
|
|
143
144
|
inputSchema: {
|
|
144
145
|
type: "object",
|
|
145
146
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.17",
|
|
4
4
|
"description": "EchoMem MCP bridge: cloud-first memory tools, local context HUD, and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|