@cybermem/dashboard 0.14.15 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @cybermem/dashboard
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#140](https://github.com/mikhailkogan17/cybermem/pull/140) [`bcf40e2`](https://github.com/mikhailkogan17/cybermem/commit/bcf40e2173ad66214cfc6089d45481966255581d) Thanks [@mikhailkogan17-antigravity](https://github.com/mikhailkogan17-antigravity)! - Moved server tools usage to FastMCP
8
+
9
+ ## 0.15.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#138](https://github.com/mikhailkogan17/cybermem/pull/138) [`2a201d4`](https://github.com/mikhailkogan17/cybermem/commit/2a201d4c6086dfd3f37dd2fadce75ffe940c4113) Thanks [@mikhailkogan17-antigravity](https://github.com/mikhailkogan17-antigravity)! - Moved server tools usage to FastMCP
14
+
3
15
  ## 0.14.15
4
16
 
5
17
  ### Patch Changes
@@ -91,17 +91,21 @@ export async function GET(request: Request) {
91
91
  status = "Error";
92
92
  else if (statusCode >= 300) status = "Warning";
93
93
 
94
- let operation = log.operation.toLowerCase();
95
- if (operation === "create") operation = "Write";
96
- else if (operation === "read") operation = "Read";
97
- else if (operation === "update") operation = "Update";
98
- else if (operation === "delete") operation = "Delete";
99
- else operation = operation.charAt(0).toUpperCase() + operation.slice(1);
94
+ const rawTool = log.tool ?? "unknown";
95
+ let toolDisplayName = String(rawTool).toLowerCase();
96
+ // Friendly labels for core tools if needed
97
+ if (toolDisplayName === "add_memory") toolDisplayName = "Write";
98
+ else if (toolDisplayName === "query_memory") toolDisplayName = "Read";
99
+ else if (toolDisplayName === "update_memory") toolDisplayName = "Update";
100
+ else if (toolDisplayName === "delete_memory") toolDisplayName = "Delete";
101
+ else
102
+ toolDisplayName =
103
+ toolDisplayName.charAt(0).toUpperCase() + toolDisplayName.slice(1);
100
104
 
101
105
  return {
102
106
  timestamp: log.timestamp,
103
107
  client: normalizeClientName(log.client_name),
104
- operation: operation,
108
+ tool: toolDisplayName,
105
109
  status: status,
106
110
  method: log.method,
107
111
  description: log.endpoint,
@@ -101,10 +101,10 @@ export async function GET(request: Request) {
101
101
  "SELECT COUNT(*) as count FROM cybermem_access_log WHERE is_error = 1",
102
102
  );
103
103
  const lastWrite = await db.get(
104
- "SELECT client_name, timestamp FROM cybermem_access_log WHERE operation = 'create' ORDER BY timestamp DESC LIMIT 1",
104
+ "SELECT client_name, timestamp FROM cybermem_access_log WHERE tool = 'add_memory' ORDER BY timestamp DESC LIMIT 1",
105
105
  );
106
106
  const lastRead = await db.get(
107
- "SELECT client_name, timestamp FROM cybermem_access_log WHERE operation = 'read' ORDER BY timestamp DESC LIMIT 1",
107
+ "SELECT client_name, timestamp FROM cybermem_access_log WHERE tool = 'query_memory' ORDER BY timestamp DESC LIMIT 1",
108
108
  );
109
109
  const uniqueClients = await db.get(
110
110
  "SELECT COUNT(DISTINCT client_name) as count FROM cybermem_access_log",
@@ -135,10 +135,10 @@ export async function GET(request: Request) {
135
135
 
136
136
  // Top activity
137
137
  const topWriter = await db.get(
138
- "SELECT client_name, COUNT(*) as count FROM cybermem_access_log WHERE operation = 'create' GROUP BY client_name ORDER BY count DESC LIMIT 1",
138
+ "SELECT client_name, COUNT(*) as count FROM cybermem_access_log WHERE tool = 'add_memory' GROUP BY client_name ORDER BY count DESC LIMIT 1",
139
139
  );
140
140
  const topReader = await db.get(
141
- "SELECT client_name, COUNT(*) as count FROM cybermem_access_log WHERE operation = 'read' GROUP BY client_name ORDER BY count DESC LIMIT 1",
141
+ "SELECT client_name, COUNT(*) as count FROM cybermem_access_log WHERE tool = 'query_memory' GROUP BY client_name ORDER BY count DESC LIMIT 1",
142
142
  );
143
143
 
144
144
  if (topWriter)
@@ -164,27 +164,27 @@ export async function GET(request: Request) {
164
164
 
165
165
  // Strip any potential native proxy bits from sqlite3 results
166
166
  const rawAllLogs = await db.all(
167
- `SELECT timestamp, operation, client_name FROM cybermem_access_log WHERE timestamp > ? ORDER BY timestamp ASC`,
167
+ `SELECT timestamp, tool, client_name FROM cybermem_access_log WHERE timestamp > ? ORDER BY timestamp ASC`,
168
168
  [startTime],
169
169
  );
170
170
  const allLogs = JSON.parse(JSON.stringify(rawAllLogs || []));
171
171
 
172
172
  const rawBaseCounts = await db.all(
173
- `SELECT operation, client_name, COUNT(*) as count FROM cybermem_access_log WHERE timestamp <= ? GROUP BY 1, 2`,
173
+ `SELECT tool, client_name, COUNT(*) as count FROM cybermem_access_log WHERE timestamp <= ? GROUP BY 1, 2`,
174
174
  [startTime],
175
175
  );
176
176
  const baseCounts = JSON.parse(JSON.stringify(rawBaseCounts || []));
177
177
 
178
- const buildBeautifulSeries = (targetOp: string) => {
178
+ const buildBeautifulSeries = (targetTool: string) => {
179
179
  const clientTotals: Record<string, number> = {};
180
180
  baseCounts
181
- .filter((b: any) => b.operation === targetOp)
181
+ .filter((b: any) => b.tool === targetTool)
182
182
  .forEach((b: any) => {
183
183
  clientTotals[b.client_name] = b.count;
184
184
  });
185
185
 
186
186
  const series: any[] = [];
187
- const opLogs = allLogs.filter((l: any) => l.operation === targetOp);
187
+ const toolLogs = allLogs.filter((l: any) => l.tool === targetTool);
188
188
  const SAMPLES = 60;
189
189
  const interval = (now - startTime) / SAMPLES;
190
190
  let currentLogIdx = 0;
@@ -192,10 +192,10 @@ export async function GET(request: Request) {
192
192
  for (let i = 0; i <= SAMPLES; i++) {
193
193
  const timePoint = startTime + i * interval;
194
194
  while (
195
- currentLogIdx < opLogs.length &&
196
- opLogs[currentLogIdx].timestamp <= timePoint
195
+ currentLogIdx < toolLogs.length &&
196
+ toolLogs[currentLogIdx].timestamp <= timePoint
197
197
  ) {
198
- const log = opLogs[currentLogIdx];
198
+ const log = toolLogs[currentLogIdx];
199
199
  clientTotals[log.client_name] =
200
200
  (clientTotals[log.client_name] || 0) + 1;
201
201
  currentLogIdx++;
@@ -208,10 +208,10 @@ export async function GET(request: Request) {
208
208
  return series;
209
209
  };
210
210
 
211
- timeseries.creates = buildBeautifulSeries("create");
212
- timeseries.reads = buildBeautifulSeries("read");
213
- timeseries.updates = buildBeautifulSeries("update");
214
- timeseries.deletes = buildBeautifulSeries("delete");
211
+ timeseries.creates = buildBeautifulSeries("add_memory");
212
+ timeseries.reads = buildBeautifulSeries("query_memory");
213
+ timeseries.updates = buildBeautifulSeries("update_memory");
214
+ timeseries.deletes = buildBeautifulSeries("delete_memory");
215
215
 
216
216
  console.error(`[STATS-API] SQLite + Charts processed successfully.`);
217
217
  } catch (dbErr) {
@@ -87,20 +87,14 @@ export default function LogViewer({
87
87
  };
88
88
 
89
89
  const exportToCSV = () => {
90
- const headers = [
91
- "Timestamp",
92
- "Client",
93
- "Operation",
94
- "Description",
95
- "Status",
96
- ];
90
+ const headers = ["Timestamp", "Client", "Tool", "Description", "Status"];
97
91
  const csvContent = [
98
92
  headers.join(","),
99
93
  ...logs.map((log) =>
100
94
  [
101
95
  `"${log.date}"`,
102
96
  `"${getClientDisplayName(log.client)}"`,
103
- `"${log.operation}"`,
97
+ `"${log.tool}"`,
104
98
  `"${log.description}"`,
105
99
  `"${log.status}"`,
106
100
  ].join(","),
@@ -122,7 +116,7 @@ export default function LogViewer({
122
116
  logs.map((log) => ({
123
117
  timestamp: log.date,
124
118
  client: getClientDisplayName(log.client),
125
- operation: log.operation,
119
+ tool: log.tool,
126
120
  description: log.description,
127
121
  status: log.status,
128
122
  })),
@@ -134,7 +128,9 @@ export default function LogViewer({
134
128
  const url = URL.createObjectURL(blob);
135
129
  const a = document.createElement("a");
136
130
  a.href = url;
137
- a.download = `cybermem-audit-${new Date().toISOString().split("T")[0]}.json`;
131
+ a.download = `cybermem-audit-${
132
+ new Date().toISOString().split("T")[0]
133
+ }.json`;
138
134
  a.click();
139
135
  URL.revokeObjectURL(url);
140
136
  setShowExportMenu(false);
@@ -198,7 +194,7 @@ export default function LogViewer({
198
194
  {[
199
195
  { label: "Timestamp", key: "date", width: "w-[180px]" },
200
196
  { label: "Client", key: "client", width: "w-[200px]" },
201
- { label: "Operation", key: "operation", width: "w-[100px]" },
197
+ { label: "Tool", key: "tool", width: "w-[100px]" },
202
198
  { label: "Description", key: "description", width: "flex-1" },
203
199
  { label: "Status", key: "status", width: "w-[100px]" },
204
200
  ].map((header) => (
@@ -281,7 +277,7 @@ export default function LogViewer({
281
277
  </div>
282
278
  </td>
283
279
  <td className="py-4 px-3 text-neutral-300">
284
- {log.operation}
280
+ {log.tool}
285
281
  </td>
286
282
  <td className="py-4 px-3 text-neutral-400">
287
283
  {log.description}
package/e2e/api.spec.ts CHANGED
@@ -44,6 +44,114 @@ function runCLI(cmd: string): { stdout: string; success: boolean } {
44
44
  }
45
45
  }
46
46
 
47
+ // MCP JSON-RPC helper using Node fetch (handles SSE responses from FastMCP)
48
+ async function mcpRpc(
49
+ method: string,
50
+ params: any = {},
51
+ id: number | null = 1,
52
+ sessionId?: string,
53
+ clientName: string = "antigravity-client",
54
+ ): Promise<{ body: any; status: number; sessionId?: string }> {
55
+ const headers: Record<string, string> = {
56
+ "Content-Type": "application/json",
57
+ Accept: "application/json, text/event-stream",
58
+ "X-Client-Name": clientName,
59
+ };
60
+ if (!isLocalhost && CYBERMEM_TOKEN) {
61
+ headers["X-API-Key"] = CYBERMEM_TOKEN;
62
+ }
63
+ if (sessionId) headers["Mcp-Session-Id"] = sessionId;
64
+
65
+ const payload: any = { jsonrpc: "2.0", method, params: params || {} };
66
+ if (id !== null) payload.id = id;
67
+
68
+ const resp = await fetch(`${MCP_API_URL}/mcp`, {
69
+ method: "POST",
70
+ headers,
71
+ body: JSON.stringify(payload),
72
+ });
73
+
74
+ const newSessionId = resp.headers.get("mcp-session-id") || sessionId;
75
+ const contentType = resp.headers.get("content-type") || "";
76
+
77
+ // For notifications (no id), the server returns 202 with no body
78
+ if (id === null || resp.status === 202) {
79
+ return { body: {}, status: resp.status, sessionId: newSessionId };
80
+ }
81
+
82
+ // If JSON, parse directly
83
+ if (contentType.includes("application/json")) {
84
+ const body = await resp.json();
85
+ return { body, status: resp.status, sessionId: newSessionId };
86
+ }
87
+
88
+ // If SSE, parse data lines from the stream
89
+ if (contentType.includes("text/event-stream")) {
90
+ const reader = resp.body!.getReader();
91
+ const decoder = new TextDecoder();
92
+ let buffer = "";
93
+ let result: any = null;
94
+
95
+ try {
96
+ while (true) {
97
+ const { done, value } = await reader.read();
98
+ if (done) break;
99
+ buffer += decoder.decode(value, { stream: true });
100
+
101
+ const lines = buffer.split("\n");
102
+ buffer = lines.pop() || "";
103
+
104
+ for (const line of lines) {
105
+ if (line.startsWith("data: ")) {
106
+ const data = line.slice(6).trim();
107
+ if (data) {
108
+ try {
109
+ const parsed = JSON.parse(data);
110
+ // Take the first response that matches our request id
111
+ if (parsed.id === id || !result) {
112
+ result = parsed;
113
+ }
114
+ } catch {
115
+ /* continuation frame */
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ // Once we have a result, stop reading
122
+ if (result) {
123
+ reader.cancel().catch(() => {});
124
+ break;
125
+ }
126
+ }
127
+ } catch {
128
+ // Stream ended or was cancelled
129
+ }
130
+
131
+ if (!result) {
132
+ throw new Error(`No SSE data received for method=${method}`);
133
+ }
134
+ return { body: result, status: resp.status, sessionId: newSessionId };
135
+ }
136
+
137
+ // Fallback: try to read as text and parse as JSON
138
+ const text = await resp.text();
139
+ try {
140
+ return {
141
+ body: JSON.parse(text),
142
+ status: resp.status,
143
+ sessionId: newSessionId,
144
+ };
145
+ } catch {
146
+ throw new Error(
147
+ `Failed to parse MCP response for method=${method}: ${text.slice(
148
+ 0,
149
+ 200,
150
+ )}`,
151
+ );
152
+ }
153
+ }
154
+
47
155
  test.describe("Dashboard:E2E:API (Deep Verification)", () => {
48
156
  const TEST_CLIENT = `e2e-api-journey-${Date.now()}`;
49
157
 
@@ -72,7 +180,11 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
72
180
  expect(body.services.length).toBeGreaterThan(0);
73
181
 
74
182
  await testInfo.attach("šŸ“Š Health Check Result", {
75
- body: `Status: ${response.status()}\nOverall: ${body.overall}\nServices: ${body.services.map((s: any) => `${s.name}: ${s.status}`).join(", ")}`,
183
+ body: `Status: ${response.status()}\nOverall: ${
184
+ body.overall
185
+ }\nServices: ${body.services
186
+ .map((s: any) => `${s.name}: ${s.status}`)
187
+ .join(", ")}`,
76
188
  contentType: "text/plain",
77
189
  });
78
190
  });
@@ -87,51 +199,106 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
87
199
  console.log(` Test Client: ${TEST_CLIENT}`);
88
200
  console.log(` Content: ${uniqueContent}`);
89
201
 
90
- // Step 1: Trigger MCP Write
91
- await test.step("šŸ“¤ CRUD — POST /add — Create new memory", async () => {
92
- console.log("šŸ“¤ POST /add (MCP API)");
93
- console.log(
94
- ` Payload: { content: "${uniqueContent}", tags: ["journey"] }`,
95
- );
96
-
97
- const resp = await request.post(`${MCP_API_URL}/add`, {
98
- data: { content: uniqueContent, tags: ["journey"] },
99
- headers: getHeaders(TEST_CLIENT),
100
- });
202
+ // Step 1: Initialize MCP Session
203
+ let sessionId: string | undefined;
101
204
 
102
- console.log(` Status: ${resp.status()}`);
103
- const body = await resp.json();
104
- console.log(` Response: ${JSON.stringify(body, null, 2)}`);
205
+ await test.step("šŸ¤ MCP — Initialize Session", async () => {
206
+ console.log("šŸ¤ JSON-RPC: initialize");
207
+ const initRes = await mcpRpc(
208
+ "initialize",
209
+ {
210
+ protocolVersion: "2024-11-05",
211
+ capabilities: {},
212
+ clientInfo: { name: TEST_CLIENT, version: "1.0.0" },
213
+ },
214
+ 1,
215
+ undefined,
216
+ TEST_CLIENT,
217
+ );
218
+ expect(initRes.status).toBe(200);
219
+ sessionId = initRes.sessionId;
220
+ console.log(` Session ID: ${sessionId}`);
221
+
222
+ // Send initialized notification
223
+ await mcpRpc(
224
+ "notifications/initialized",
225
+ {},
226
+ null,
227
+ sessionId,
228
+ TEST_CLIENT,
229
+ );
230
+ });
105
231
 
106
- expect(resp.status()).toBe(200);
232
+ // Step 2: Trigger MCP Write via JSON-RPC
233
+ await test.step("šŸ“¤ CRUD — POST /mcp — Create new memory", async () => {
234
+ console.log("šŸ“¤ POST /mcp (JSON-RPC: tools/call add_memory)");
235
+
236
+ let rpcRes: any;
237
+ for (let i = 0; i < 3; i++) {
238
+ rpcRes = await mcpRpc(
239
+ "tools/call",
240
+ {
241
+ name: "add_memory",
242
+ arguments: {
243
+ content: uniqueContent,
244
+ tags: ["journey"],
245
+ },
246
+ },
247
+ 2,
248
+ sessionId,
249
+ TEST_CLIENT,
250
+ );
251
+ if (rpcRes.status === 200 && !rpcRes.body.result?.isError) break;
252
+ console.log(` āš ļø Attempt ${i + 1} failed, retrying...`);
253
+ await new Promise((resolve) => setTimeout(resolve, 1000));
254
+ }
107
255
 
108
- await testInfo.attach("šŸ“ CRUD — CREATE", {
109
- body: `Endpoint: POST ${MCP_API_URL}/add\n\nRequest:\n{\n "content": "${uniqueContent}",\n "tags": ["journey"]\n}\n\nResponse:\n${JSON.stringify(body, null, 2)}`,
256
+ console.log(` Status: ${rpcRes.status}`);
257
+ console.log(` Response: ${JSON.stringify(rpcRes.body, null, 2)}`);
258
+
259
+ expect(rpcRes.status).toBe(200);
260
+ expect(
261
+ rpcRes.body.result,
262
+ `RPC response result missing. Body: ${JSON.stringify(rpcRes.body)}`,
263
+ ).toBeDefined();
264
+ expect(rpcRes.body.result.isError).not.toBe(true);
265
+
266
+ await testInfo.attach("šŸ“ CRUD — CREATE (RPC)", {
267
+ body: `Endpoint: POST ${RAW_MCP_URL}/mcp\nSession: ${sessionId}\nResponse:\n${JSON.stringify(
268
+ rpcRes.body,
269
+ null,
270
+ 2,
271
+ )}`,
110
272
  contentType: "text/plain",
111
273
  });
112
274
  });
113
275
 
114
- // Step 2: Verify Metrics
276
+ // Step 3: Verify Metrics in Dashboard
115
277
  await test.step("šŸ“Š Discovery — Metrics Reflect Write Activity", async () => {
116
278
  console.log("šŸ“Š GET /api/metrics");
117
279
 
118
- const resp = await request.get(`${DASHBOARD_URL}/api/metrics`, {
119
- headers: getHeaders("antigravity-client"),
280
+ // Use unique URL to avoid caching issues in playwright if any
281
+ const metricsUrl = `${DASHBOARD_URL}/api/metrics?t=${Date.now()}`;
282
+ const metricsResp = await request.get(metricsUrl, {
283
+ headers: { "X-Client-Name": "e2e-api-metrics-check" },
120
284
  });
285
+ const data = await metricsResp.json();
121
286
 
122
- const data = await resp.json();
123
- console.log(` Last Writer: ${data.stats.lastWriter.name}`);
287
+ console.log(` Last Writer: ${data.stats.lastWriter?.name || "N/A"}`);
124
288
  console.log(` Total Requests: ${data.stats.totalRequests}`);
125
289
  console.log(
126
- ` Creates Time Series Length: ${data.timeSeries.creates.length}`,
290
+ ` Creates Time Series Length: ${
291
+ data.timeSeries?.creates?.length || 0
292
+ }`,
127
293
  );
128
294
 
295
+ // Verify that the dashboard reports the unique E2E test client
129
296
  expect(data.stats.lastWriter.name).toContain(TEST_CLIENT);
130
297
  expect(data.stats.totalRequests).toBeGreaterThan(0);
131
298
 
132
299
  await testInfo.attach("šŸ“Š Metrics Snapshot", {
133
- body: `Last Writer: ${data.stats.lastWriter.name}\nTotal Requests: ${data.stats.totalRequests}\nCreates Time Series: ${data.timeSeries.creates.length} entries`,
134
- contentType: "text/plain",
300
+ body: JSON.stringify(data, null, 2),
301
+ contentType: "application/json",
135
302
  });
136
303
  });
137
304
 
@@ -153,10 +320,12 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
153
320
  }
154
321
 
155
322
  expect(latestLog).toBeDefined();
156
- expect(latestLog.operation).toBe("Write");
323
+ expect(latestLog.tool).toBe("Write");
157
324
 
158
325
  await testInfo.attach("šŸ“‹ Audit Log Entry", {
159
- body: `Found: ${latestLog ? "YES" : "NO"}\nClient: ${latestLog?.client}\nOperation: ${latestLog?.operation}\nStatus: ${latestLog?.status}`,
326
+ body: `Found: ${latestLog ? "YES" : "NO"}\nClient: ${
327
+ latestLog?.client
328
+ }\nTool: ${latestLog?.tool}\nStatus: ${latestLog?.status}`,
160
329
  contentType: "text/plain",
161
330
  });
162
331
  });
@@ -164,7 +333,7 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
164
333
  console.log("āœ… FULL JOURNEY TEST COMPLETE");
165
334
 
166
335
  await testInfo.attach("āœ… Journey Complete", {
167
- body: `Test Client: ${TEST_CLIENT}\nContent: ${uniqueContent}\n\nVerified:\nāœ… MCP Write (POST /add)\nāœ… Metrics (GET /api/metrics) — Last Writer confirmed\nāœ… Audit Logs (GET /api/audit-logs) — Entry found`,
336
+ body: `Test Client: ${TEST_CLIENT}\nContent: ${uniqueContent}\n\nVerified:\nāœ… MCP Write (POST /mcp)\nāœ… Metrics (GET /api/metrics) — Last Writer confirmed\nāœ… Audit Logs (GET /api/audit-logs) — Entry found`,
168
337
  contentType: "text/plain",
169
338
  });
170
339
  });
@@ -189,7 +358,9 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
189
358
  expect(config.config.mcpServers).toBeDefined();
190
359
 
191
360
  await testInfo.attach("āš™ļø MCP Config", {
192
- body: `Config Type: ${config.configType}\nmcpServers: ${Object.keys(config.config.mcpServers).join(", ")}`,
361
+ body: `Config Type: ${config.configType}\nmcpServers: ${Object.keys(
362
+ config.config.mcpServers,
363
+ ).join(", ")}`,
193
364
  contentType: "text/plain",
194
365
  });
195
366
  });
@@ -211,7 +382,11 @@ test.describe("Dashboard:E2E:API (Deep Verification)", () => {
211
382
  expect(settings).toHaveProperty("endpoint");
212
383
 
213
384
  await testInfo.attach("āš™ļø Settings", {
214
- body: `Instance Type: ${settings.instanceType}\nEndpoint: ${settings.endpoint}\nAPI Key: ${settings.apiKey ? "***" + settings.apiKey.slice(-4) : "N/A"}`,
385
+ body: `Instance Type: ${settings.instanceType}\nEndpoint: ${
386
+ settings.endpoint
387
+ }\nAPI Key: ${
388
+ settings.apiKey ? "***" + settings.apiKey.slice(-4) : "N/A"
389
+ }`,
215
390
  contentType: "text/plain",
216
391
  });
217
392
  });
package/e2e/ui.spec.ts CHANGED
@@ -48,7 +48,9 @@ async function setupNetworkLogging(
48
48
  .map((l) =>
49
49
  l.type === "REQUEST"
50
50
  ? `šŸ“¤ ${l.method} ${l.url}`
51
- : `šŸ“„ ${l.status} ${l.url}${l.body ? `\n ${l.body.substring(0, 200)}...` : ""}`,
51
+ : `šŸ“„ ${l.status} ${l.url}${
52
+ l.body ? `\n ${l.body.substring(0, 200)}...` : ""
53
+ }`,
52
54
  )
53
55
  .join("\n"),
54
56
  contentType: "text/plain",
@@ -176,11 +178,11 @@ test.describe("Dashboard:E2E:UI (High-Fidelity Mocks)", () => {
176
178
  id: "log-1",
177
179
  timestamp: Date.now(), // Use numeric timestamp for proper formatting
178
180
  client: MOCK_IDENTITY_WRITER,
179
- operation: "Write",
181
+ tool: "Write",
180
182
  method: "POST",
181
- endpoint: "/add",
183
+ endpoint: "/mcp",
182
184
  status: "Success",
183
- description: "/add",
185
+ description: "/mcp",
184
186
  },
185
187
  ],
186
188
  pagination: { currentPage: 1, totalPages: 1, totalItems: 1 },
@@ -239,7 +241,9 @@ test.describe("Dashboard:E2E:UI (High-Fidelity Mocks)", () => {
239
241
 
240
242
  // Attach applied mocks summary to trace
241
243
  await testInfo.attach("šŸ“‹ Applied Mocks", {
242
- body: `Mocks configured for this test:\n\n${appliedMocks.map((m) => `āœ… ${m.endpoint}\n ${m.description}`).join("\n\n")}`,
244
+ body: `Mocks configured for this test:\n\n${appliedMocks
245
+ .map((m) => `āœ… ${m.endpoint}\n ${m.description}`)
246
+ .join("\n\n")}`,
243
247
  contentType: "text/plain",
244
248
  });
245
249
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/dashboard",
3
- "version": "0.14.15",
3
+ "version": "0.16.0",
4
4
  "description": "CyberMem Monitoring Dashboard",
5
5
  "homepage": "https://cybermem.dev",
6
6
  "repository": {