@echomem/mcp 1.4.44 → 1.4.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -25
- package/dist/city/README.md +9 -0
- package/dist/city/echo-ai-city-only.html +2232 -0
- package/dist/city/echo-extraction-plate.html +330 -0
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +1417 -0
- package/dist/city/vendor/RoundedBoxGeometry.js +155 -0
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +8139 -0
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +6 -0
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/forensics.js +1531 -0
- package/dist/hud/hooks.js +31 -43
- package/dist/index.js +79 -15
- package/dist/local-data-paths.js +38 -0
- package/dist/migrate.js +140 -70
- package/dist/report.js +721 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +372 -18
- package/dist/setup-page/client-extraction.js +204 -37
- package/dist/setup-page/client-lifecycle.js +101 -31
- package/dist/setup-page/client-report-audit.js +819 -0
- package/dist/setup-page/client-report-city.js +356 -0
- package/dist/setup-page/client-report.js +6 -0
- package/dist/setup-page/client.js +2 -0
- package/dist/setup-page/styles-city-report.js +880 -0
- package/dist/setup-page/styles-context-audit.js +470 -0
- package/dist/setup-page/styles-extraction.js +31 -1
- package/dist/setup-page/styles-foundation.js +89 -0
- package/dist/setup-page/styles-mvp.js +155 -10
- package/dist/setup-page/styles-website-alignment.js +204 -0
- package/dist/setup-page/styles.js +4 -0
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +212 -4
- package/dist/setup.js +702 -321
- package/dist/source-session.js +3 -9
- package/dist/v1-contract.js +8 -0
- package/package.json +7 -9
- package/dist/config-files.js +0 -63
- package/dist/local-jsonl.js +0 -87
- package/dist/onboarding-stats.js +0 -16
package/dist/source-session.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import { resolveClaudeProjectsDir, resolveCodexSessionRoots } from "./local-data-paths.js";
|
|
3
|
+
import { resolveClaudeCoworkSessionsDir, resolveClaudeProjectsDir, resolveCodexSessionRoots, } from "./local-data-paths.js";
|
|
5
4
|
export const SOURCE_SESSION_BINDING_EVIDENCE = "local_jsonl_tool_call";
|
|
6
5
|
export const SOURCE_SESSION_HOOK_EVIDENCE = "local_session_start_hook";
|
|
7
6
|
export const SOURCE_SESSION_MCP_METADATA_EVIDENCE = "local_mcp_session_metadata";
|
|
@@ -81,18 +80,13 @@ function readableDirectory(candidate) {
|
|
|
81
80
|
return false;
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
|
-
function coworkRoot() {
|
|
85
|
-
const supportRoot = process.env.CLAUDE_DESKTOP_SUPPORT_DIR?.trim()
|
|
86
|
-
|| path.join(os.homedir(), "Library", "Application Support", "Claude");
|
|
87
|
-
return path.join(supportRoot, "local-agent-mode-sessions");
|
|
88
|
-
}
|
|
89
83
|
function defaultRoots() {
|
|
90
84
|
const claudeProjects = resolveClaudeProjectsDir();
|
|
91
|
-
const desktopRoot =
|
|
85
|
+
const desktopRoot = resolveClaudeCoworkSessionsDir();
|
|
92
86
|
return {
|
|
93
87
|
codex: resolveCodexSessionRoots().map((root) => root.path),
|
|
94
88
|
claudeCode: claudeProjects ? [claudeProjects] : [],
|
|
95
|
-
cowork:
|
|
89
|
+
cowork: desktopRoot ? [desktopRoot] : [],
|
|
96
90
|
};
|
|
97
91
|
}
|
|
98
92
|
function walkJsonlFiles(roots) {
|
package/dist/v1-contract.js
CHANGED
|
@@ -25,6 +25,7 @@ export const canonicalToolNames = {
|
|
|
25
25
|
completeGroupPublication: "complete_group_publication",
|
|
26
26
|
publishToGroup: "publish_memory_to_group",
|
|
27
27
|
publishBatchToGroup: "publish_memories_to_group",
|
|
28
|
+
report: "echomem_usage_report",
|
|
28
29
|
updateStatus: "echomem_update_status",
|
|
29
30
|
contextHealth: "echo_context_health",
|
|
30
31
|
recompose: "echo_recompose",
|
|
@@ -53,6 +54,7 @@ const READ_ONLY_TOOL_NAMES = new Set([
|
|
|
53
54
|
canonicalToolNames.requestGroupSessionSharing,
|
|
54
55
|
canonicalToolNames.getByContext,
|
|
55
56
|
canonicalToolNames.checkpointByContext,
|
|
57
|
+
canonicalToolNames.report,
|
|
56
58
|
canonicalToolNames.updateStatus,
|
|
57
59
|
canonicalToolNames.contextHealth,
|
|
58
60
|
canonicalToolNames.recompose,
|
|
@@ -82,6 +84,7 @@ const ALWAYS_LOAD_TOOL_NAMES = new Set([
|
|
|
82
84
|
canonicalToolNames.save,
|
|
83
85
|
]);
|
|
84
86
|
const LOCAL_ONLY_TOOL_NAMES = new Set([
|
|
87
|
+
canonicalToolNames.report,
|
|
85
88
|
canonicalToolNames.contextHealth,
|
|
86
89
|
canonicalToolNames.recompose,
|
|
87
90
|
]);
|
|
@@ -1020,6 +1023,11 @@ export function listToolSpecs(opts = {}) {
|
|
|
1020
1023
|
required: ["contextId"],
|
|
1021
1024
|
},
|
|
1022
1025
|
},
|
|
1026
|
+
{
|
|
1027
|
+
name: canonicalToolNames.report,
|
|
1028
|
+
description: "Show the user a one-screen audit of THEIR OWN AI coding usage — computed locally from their Codex/Claude Code logs ($0, nothing uploaded): how many tokens their agents spent, how much was re-reading context, reads vs memory recalls, and what changes with EchoMem. Call this when the user asks about their usage, token spend, cost, how much they're wasting, or wants a summary of their agent activity — and you may offer it once right after EchoMem is first connected. Returns formatted text to show the user verbatim. Needs no login.",
|
|
1029
|
+
inputSchema: { type: "object", properties: {} },
|
|
1030
|
+
},
|
|
1023
1031
|
{
|
|
1024
1032
|
name: canonicalToolNames.updateStatus,
|
|
1025
1033
|
description: `Check whether this installed EchoMem MCP bridge is behind the latest version. Works without login, uploads no user transcript, and normal background checks are cached. ${MCP_DESKTOP_MANAGED ? "Echo Desktop owns updates for this runtime; direct the user back to the desktop app." : "If it reports an update, tell the user and offer to run the returned update command."} After updating, the user must start a new agent/MCP session.${updateSection}`,
|
package/package.json
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"description": "EchoMem MCP bridge
|
|
3
|
+
"version": "1.4.45",
|
|
4
|
+
"description": "EchoMem MCP bridge: cloud-first memory tools and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=18"
|
|
9
|
-
},
|
|
10
7
|
"echoDesktop": {
|
|
11
8
|
"minimumVersion": "1.0.0",
|
|
12
9
|
"runtimeLayout": 1
|
|
@@ -22,21 +19,22 @@
|
|
|
22
19
|
"README.md"
|
|
23
20
|
],
|
|
24
21
|
"scripts": {
|
|
25
|
-
"build": "
|
|
22
|
+
"build": "tsc",
|
|
26
23
|
"start": "node dist/index.js",
|
|
27
24
|
"predev": "npm run build",
|
|
28
25
|
"dev": "node dist/index.js",
|
|
29
26
|
"smoke": "node smoke.mjs",
|
|
30
27
|
"preview:extraction": "npm run build && node scripts/preview-extraction.mjs",
|
|
28
|
+
"stress:long-history": "npm run build && node scripts/stress-long-history.mjs",
|
|
29
|
+
"stress:long-claude-history": "npm run build && node scripts/stress-long-claude-history.mjs",
|
|
31
30
|
"test:artifact": "npm run build && node test/package-artifact.test.mjs",
|
|
32
31
|
"test:registry": "node test/registry-artifact.test.mjs",
|
|
33
32
|
"test:registry-ui": "npm run build && node test/registry-ui.test.mjs",
|
|
34
33
|
"test:ui": "npm run build && node test/setup-ui.test.mjs",
|
|
35
34
|
"test:onboarding-resilience": "npm run build && node test/onboarding-resilience.test.mjs",
|
|
36
|
-
"test:config-safety": "npm run build && node test/config-safety.test.mjs",
|
|
37
35
|
"test:billing-ui": "npm run build && node test/setup-ui.test.mjs billing",
|
|
38
|
-
"test": "npm run build && node test/source-session.test.mjs && node test/source-session-hook.test.mjs && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/onboarding-resilience.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/workspace-selector.test.mjs && node test/group-map.test.mjs && node test/update-check.test.mjs && node test/headless-runtime.test.mjs && node test/claude-code-config.test.mjs && node test/
|
|
39
|
-
"prepack": "npm run build"
|
|
36
|
+
"test": "npm run build && node test/source-session.test.mjs && node test/source-session-hook.test.mjs && node test/local-data-paths.test.mjs && node test/crypto.test.mjs && node test/integration.test.mjs && node test/onboarding-resilience.test.mjs && node test/local-auth.test.mjs && node test/retrieval-only.test.mjs && node test/no-restart.test.mjs && node test/report.test.mjs && node test/forensics.test.mjs && node test/canonical-golden.test.mjs && node test/tools.test.mjs && node test/workspace-selector.test.mjs && node test/group-map.test.mjs && node test/update-check.test.mjs && node test/headless-runtime.test.mjs && node test/claude-code-config.test.mjs && node test/delete.test.mjs && node test/low-touch-tools.test.mjs && node test/migrate.test.mjs && node test/restart-recovery.test.mjs && node test/save-checkpoint-hook.test.mjs",
|
|
37
|
+
"prepack": "npm run build && node scripts/bundle-city.mjs"
|
|
40
38
|
},
|
|
41
39
|
"dependencies": {
|
|
42
40
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
package/dist/config-files.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
function isErrno(error, code) {
|
|
5
|
-
return error instanceof Error
|
|
6
|
-
&& "code" in error
|
|
7
|
-
&& error.code === code;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Read a user-owned JSON configuration without treating corruption as an empty file.
|
|
11
|
-
* Setup must fail closed here: replacing malformed JSON would destroy unrelated client settings.
|
|
12
|
-
*/
|
|
13
|
-
export function readJsonObjectFile(file, label = "configuration") {
|
|
14
|
-
let raw;
|
|
15
|
-
try {
|
|
16
|
-
raw = fs.readFileSync(file, "utf8");
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
if (isErrno(error, "ENOENT"))
|
|
20
|
-
return {};
|
|
21
|
-
throw new Error(`Could not read ${label} at ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
-
}
|
|
23
|
-
let parsed;
|
|
24
|
-
try {
|
|
25
|
-
parsed = JSON.parse(raw);
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
throw new Error(`Could not safely update ${label} at ${file} because the existing file is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
29
|
-
}
|
|
30
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
31
|
-
throw new Error(`Could not safely update ${label} at ${file} because the existing JSON root is not an object.`);
|
|
32
|
-
}
|
|
33
|
-
return parsed;
|
|
34
|
-
}
|
|
35
|
-
/** Write beside the destination and rename only after the complete replacement is durable locally. */
|
|
36
|
-
export function atomicWriteTextFile(file, content) {
|
|
37
|
-
const directory = path.dirname(file);
|
|
38
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
39
|
-
const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
40
|
-
let mode;
|
|
41
|
-
try {
|
|
42
|
-
mode = fs.statSync(file).mode;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* A fresh file uses the process default mode. */
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
fs.writeFileSync(temporary, content, mode === undefined ? undefined : { mode });
|
|
49
|
-
fs.renameSync(temporary, file);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
try {
|
|
53
|
-
fs.rmSync(temporary, { force: true });
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
/* Preserve the original error. */
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export function atomicWriteJsonObject(file, value) {
|
|
62
|
-
atomicWriteTextFile(file, JSON.stringify(value, null, 2));
|
|
63
|
-
}
|
package/dist/local-jsonl.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { StringDecoder } from "node:string_decoder";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
/**
|
|
5
|
-
* Stream a JSONL file without loading large agent transcripts into one string.
|
|
6
|
-
* Malformed lines and unreadable files are skipped so local discovery remains recoverable.
|
|
7
|
-
*/
|
|
8
|
-
export function eachJsonLine(file, visit) {
|
|
9
|
-
let descriptor;
|
|
10
|
-
try {
|
|
11
|
-
descriptor = fs.openSync(file, "r");
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
const decoder = new StringDecoder("utf8");
|
|
18
|
-
const buffer = Buffer.allocUnsafe(1 << 20);
|
|
19
|
-
let remainder = "";
|
|
20
|
-
let bytesRead;
|
|
21
|
-
const consume = (chunk) => {
|
|
22
|
-
const lines = (remainder + chunk).split("\n");
|
|
23
|
-
remainder = lines.pop() ?? "";
|
|
24
|
-
for (const line of lines) {
|
|
25
|
-
const value = line.trim();
|
|
26
|
-
if (!value)
|
|
27
|
-
continue;
|
|
28
|
-
try {
|
|
29
|
-
visit(JSON.parse(value));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
/* Ignore malformed or partially-written rows. */
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
while ((bytesRead = fs.readSync(descriptor, buffer, 0, buffer.length, null)) > 0) {
|
|
37
|
-
consume(decoder.write(buffer.subarray(0, bytesRead)));
|
|
38
|
-
}
|
|
39
|
-
const finalValue = (remainder + decoder.end()).trim();
|
|
40
|
-
if (finalValue) {
|
|
41
|
-
try {
|
|
42
|
-
visit(JSON.parse(finalValue));
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
/* Ignore a malformed or partially-written final row. */
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
try {
|
|
51
|
-
fs.closeSync(descriptor);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
/* Best effort. */
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/** Recursively list matching regular files without following child symlinks. */
|
|
59
|
-
export function walkLocalFiles(directory, matches, skipDirectory, output = []) {
|
|
60
|
-
let entries;
|
|
61
|
-
try {
|
|
62
|
-
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return output;
|
|
66
|
-
}
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
const absolute = path.join(directory, entry.name);
|
|
69
|
-
let stat;
|
|
70
|
-
try {
|
|
71
|
-
stat = fs.lstatSync(absolute);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (stat.isSymbolicLink())
|
|
77
|
-
continue;
|
|
78
|
-
if (stat.isDirectory()) {
|
|
79
|
-
if (!skipDirectory(entry.name))
|
|
80
|
-
walkLocalFiles(absolute, matches, skipDirectory, output);
|
|
81
|
-
}
|
|
82
|
-
else if (stat.isFile() && matches(absolute)) {
|
|
83
|
-
output.push(absolute);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return output;
|
|
87
|
-
}
|
package/dist/onboarding-stats.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The onboarding page only needs session discovery and import state. Usage-audit calculations
|
|
3
|
-
* (tokens, cost, context reads, recall ratios, and memory totals) are intentionally excluded.
|
|
4
|
-
*/
|
|
5
|
-
export function buildOnboardingStatsPayload(input) {
|
|
6
|
-
return {
|
|
7
|
-
schemaVersion: 1,
|
|
8
|
-
...(input.partial ? { partial: true } : {}),
|
|
9
|
-
discovery: input.discovery,
|
|
10
|
-
generatedFrom: ["~/.codex/sessions", "~/.claude/projects"],
|
|
11
|
-
llmCallsUsed: 0,
|
|
12
|
-
transcriptsUploaded: false,
|
|
13
|
-
sessions: input.sessions,
|
|
14
|
-
migratable: input.migratable,
|
|
15
|
-
};
|
|
16
|
-
}
|