@graphty/remote-logger 0.0.1 → 1.2.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/README.md +944 -28
- package/bin/remote-log-server.js +3 -0
- package/dist/client/RemoteLogClient.d.ts +116 -0
- package/dist/client/RemoteLogClient.d.ts.map +1 -0
- package/dist/client/RemoteLogClient.js +269 -0
- package/dist/client/RemoteLogClient.js.map +1 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +6 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +60 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +6 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +32 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +270 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +14 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +14 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/logs-clear.d.ts +76 -0
- package/dist/mcp/tools/logs-clear.d.ts.map +1 -0
- package/dist/mcp/tools/logs-clear.js +58 -0
- package/dist/mcp/tools/logs-clear.js.map +1 -0
- package/dist/mcp/tools/logs-get-all.d.ts +60 -0
- package/dist/mcp/tools/logs-get-all.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-all.js +50 -0
- package/dist/mcp/tools/logs-get-all.js.map +1 -0
- package/dist/mcp/tools/logs-get-errors.d.ts +65 -0
- package/dist/mcp/tools/logs-get-errors.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-errors.js +46 -0
- package/dist/mcp/tools/logs-get-errors.js.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts +75 -0
- package/dist/mcp/tools/logs-get-file-path.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-file-path.js +90 -0
- package/dist/mcp/tools/logs-get-file-path.js.map +1 -0
- package/dist/mcp/tools/logs-get-recent.d.ts +89 -0
- package/dist/mcp/tools/logs-get-recent.d.ts.map +1 -0
- package/dist/mcp/tools/logs-get-recent.js +74 -0
- package/dist/mcp/tools/logs-get-recent.js.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts +64 -0
- package/dist/mcp/tools/logs-list-sessions.d.ts.map +1 -0
- package/dist/mcp/tools/logs-list-sessions.js +48 -0
- package/dist/mcp/tools/logs-list-sessions.js.map +1 -0
- package/dist/mcp/tools/logs-receive.d.ts +150 -0
- package/dist/mcp/tools/logs-receive.d.ts.map +1 -0
- package/dist/mcp/tools/logs-receive.js +68 -0
- package/dist/mcp/tools/logs-receive.js.map +1 -0
- package/dist/mcp/tools/logs-search.d.ts +91 -0
- package/dist/mcp/tools/logs-search.d.ts.map +1 -0
- package/dist/mcp/tools/logs-search.js +68 -0
- package/dist/mcp/tools/logs-search.js.map +1 -0
- package/dist/mcp/tools/logs-status.d.ts +45 -0
- package/dist/mcp/tools/logs-status.d.ts.map +1 -0
- package/dist/mcp/tools/logs-status.js +45 -0
- package/dist/mcp/tools/logs-status.js.map +1 -0
- package/dist/server/dual-server.d.ts +76 -0
- package/dist/server/dual-server.d.ts.map +1 -0
- package/dist/server/dual-server.js +214 -0
- package/dist/server/dual-server.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jsonl-writer.d.ts +93 -0
- package/dist/server/jsonl-writer.d.ts.map +1 -0
- package/dist/server/jsonl-writer.js +205 -0
- package/dist/server/jsonl-writer.js.map +1 -0
- package/dist/server/log-server.d.ts +126 -0
- package/dist/server/log-server.d.ts.map +1 -0
- package/dist/server/log-server.js +589 -0
- package/dist/server/log-server.js.map +1 -0
- package/dist/server/log-storage.d.ts +301 -0
- package/dist/server/log-storage.d.ts.map +1 -0
- package/dist/server/log-storage.js +408 -0
- package/dist/server/log-storage.js.map +1 -0
- package/dist/server/marker-utils.d.ts +69 -0
- package/dist/server/marker-utils.d.ts.map +1 -0
- package/dist/server/marker-utils.js +118 -0
- package/dist/server/marker-utils.js.map +1 -0
- package/dist/server/self-signed-cert.d.ts +30 -0
- package/dist/server/self-signed-cert.d.ts.map +1 -0
- package/dist/server/self-signed-cert.js +83 -0
- package/dist/server/self-signed-cert.js.map +1 -0
- package/dist/ui/ConsoleCaptureUI.d.ts +118 -0
- package/dist/ui/ConsoleCaptureUI.d.ts.map +1 -0
- package/dist/ui/ConsoleCaptureUI.js +571 -0
- package/dist/ui/ConsoleCaptureUI.js.map +1 -0
- package/dist/ui/index.d.ts +15 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/vite/index.d.ts +8 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +8 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/plugin.d.ts +42 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +46 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +90 -7
- package/src/client/RemoteLogClient.ts +328 -0
- package/src/client/index.ts +7 -0
- package/src/client/types.ts +62 -0
- package/src/index.ts +28 -0
- package/src/mcp/index.ts +25 -0
- package/src/mcp/mcp-server.ts +364 -0
- package/src/mcp/tools/index.ts +69 -0
- package/src/mcp/tools/logs-clear.ts +86 -0
- package/src/mcp/tools/logs-get-all.ts +78 -0
- package/src/mcp/tools/logs-get-errors.ts +71 -0
- package/src/mcp/tools/logs-get-file-path.ts +121 -0
- package/src/mcp/tools/logs-get-recent.ts +104 -0
- package/src/mcp/tools/logs-list-sessions.ts +71 -0
- package/src/mcp/tools/logs-receive.ts +96 -0
- package/src/mcp/tools/logs-search.ts +95 -0
- package/src/mcp/tools/logs-status.ts +69 -0
- package/src/server/dual-server.ts +308 -0
- package/src/server/index.ts +54 -0
- package/src/server/jsonl-writer.ts +277 -0
- package/src/server/log-server.ts +763 -0
- package/src/server/log-storage.ts +651 -0
- package/src/server/marker-utils.ts +144 -0
- package/src/server/self-signed-cert.ts +93 -0
- package/src/ui/ConsoleCaptureUI.ts +649 -0
- package/src/ui/index.ts +15 -0
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin.ts +59 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-signed certificate generation for the log server.
|
|
3
|
+
* Uses the 'selfsigned' npm package to generate proper X.509 certificates.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import selfsigned from "selfsigned";
|
|
7
|
+
/**
|
|
8
|
+
* Generate a self-signed certificate for HTTPS.
|
|
9
|
+
* The certificate is valid for localhost and common local development hostnames.
|
|
10
|
+
* @param hostname - Optional hostname to include in the certificate (default: localhost)
|
|
11
|
+
* @returns Object containing PEM-encoded certificate and private key
|
|
12
|
+
*/
|
|
13
|
+
export function generateSelfSignedCert(hostname = "localhost") {
|
|
14
|
+
const attrs = [
|
|
15
|
+
{ name: "commonName", value: hostname },
|
|
16
|
+
{ name: "organizationName", value: "Remote Log Server" },
|
|
17
|
+
{ name: "countryName", value: "US" },
|
|
18
|
+
];
|
|
19
|
+
const options = {
|
|
20
|
+
keySize: 2048,
|
|
21
|
+
days: 365,
|
|
22
|
+
algorithm: "sha256",
|
|
23
|
+
extensions: [
|
|
24
|
+
{
|
|
25
|
+
name: "basicConstraints",
|
|
26
|
+
cA: false,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "keyUsage",
|
|
30
|
+
keyCertSign: false,
|
|
31
|
+
digitalSignature: true,
|
|
32
|
+
keyEncipherment: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "extKeyUsage",
|
|
36
|
+
serverAuth: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "subjectAltName",
|
|
40
|
+
altNames: [
|
|
41
|
+
{ type: 2, value: hostname }, // DNS name
|
|
42
|
+
{ type: 2, value: "localhost" },
|
|
43
|
+
{ type: 7, ip: "127.0.0.1" }, // IP address
|
|
44
|
+
{ type: 7, ip: "::1" }, // IPv6 localhost
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
const pems = selfsigned.generate(attrs, options);
|
|
50
|
+
return {
|
|
51
|
+
cert: pems.cert,
|
|
52
|
+
key: pems.private,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if certificate files exist and are readable.
|
|
57
|
+
* @param certPath - Path to the certificate file
|
|
58
|
+
* @param keyPath - Path to the private key file
|
|
59
|
+
* @returns true if both files exist and are readable
|
|
60
|
+
*/
|
|
61
|
+
export function certFilesExist(certPath, keyPath) {
|
|
62
|
+
try {
|
|
63
|
+
fs.accessSync(certPath, fs.constants.R_OK);
|
|
64
|
+
fs.accessSync(keyPath, fs.constants.R_OK);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read certificate and key from files.
|
|
73
|
+
* @param certPath - Path to the certificate file
|
|
74
|
+
* @param keyPath - Path to the private key file
|
|
75
|
+
* @returns Object containing PEM-encoded certificate and private key
|
|
76
|
+
*/
|
|
77
|
+
export function readCertFiles(certPath, keyPath) {
|
|
78
|
+
return {
|
|
79
|
+
cert: fs.readFileSync(certPath, "utf-8"),
|
|
80
|
+
key: fs.readFileSync(keyPath, "utf-8"),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=self-signed-cert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-signed-cert.js","sourceRoot":"","sources":["../../src/server/self-signed-cert.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,UAAU,MAAM,YAAY,CAAC;AAOpC;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAQ,GAAG,WAAW;IACzD,MAAM,KAAK,GAAG;QACV,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE;QACvC,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,mBAAmB,EAAE;QACxD,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE;KACvC,CAAC;IAEF,MAAM,OAAO,GAAG;QACZ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,GAAG;QACT,SAAS,EAAE,QAAiB;QAC5B,UAAU,EAAE;YACR;gBACI,IAAI,EAAE,kBAAkB;gBACxB,EAAE,EAAE,KAAK;aACZ;YACD;gBACI,IAAI,EAAE,UAAU;gBAChB,WAAW,EAAE,KAAK;gBAClB,gBAAgB,EAAE,IAAI;gBACtB,eAAe,EAAE,IAAI;aACxB;YACD;gBACI,IAAI,EAAE,aAAa;gBACnB,UAAU,EAAE,IAAI;aACnB;YACD;gBACI,IAAI,EAAE,gBAAgB;gBACtB,QAAQ,EAAE;oBACN,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,WAAW;oBACzC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE;oBAC/B,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,aAAa;oBAC3C,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,iBAAiB;iBAC5C;aACJ;SACJ;KACJ,CAAC;IAEF,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAEjD,OAAO;QACH,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,OAAO;KACpB,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAe;IAC5D,IAAI,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC3C,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,OAAe;IAC3D,OAAO;QACH,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC;QACxC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC;KACzC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConsoleCaptureUI - Console capture with floating UI widget
|
|
3
|
+
*
|
|
4
|
+
* Intercepts console output (log, error, warn, info, debug) and provides
|
|
5
|
+
* a floating button UI with copy, download, view, and clear functionality.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Floating button in top-right corner
|
|
9
|
+
* - Menu with Copy, Download, Clear, and Show Logs options
|
|
10
|
+
* - Works on mobile devices and in XR environments
|
|
11
|
+
* - Global window.__console__ methods for programmatic access
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* import { initConsoleCaptureUI } from "@graphty/remote-logger/ui";
|
|
15
|
+
* initConsoleCaptureUI();
|
|
16
|
+
*
|
|
17
|
+
* Or programmatically:
|
|
18
|
+
* const ui = new ConsoleCaptureUI();
|
|
19
|
+
* console.log("This is captured");
|
|
20
|
+
* ui.destroy(); // Clean up when done
|
|
21
|
+
*/
|
|
22
|
+
declare global {
|
|
23
|
+
interface Window {
|
|
24
|
+
__console__?: {
|
|
25
|
+
copy: () => Promise<void>;
|
|
26
|
+
download: () => void;
|
|
27
|
+
clear: () => void;
|
|
28
|
+
get: () => string;
|
|
29
|
+
logs: CapturedLogEntry[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** A single captured log entry */
|
|
34
|
+
interface CapturedLogEntry {
|
|
35
|
+
type: string;
|
|
36
|
+
args: unknown[];
|
|
37
|
+
timestamp: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* ConsoleCaptureUI class - captures console output and provides floating UI widget
|
|
41
|
+
*/
|
|
42
|
+
export declare class ConsoleCaptureUI {
|
|
43
|
+
private logs;
|
|
44
|
+
private originalMethods;
|
|
45
|
+
private buttonContainer;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new ConsoleCaptureUI instance that intercepts console methods
|
|
48
|
+
* and creates a floating UI widget
|
|
49
|
+
*/
|
|
50
|
+
constructor();
|
|
51
|
+
/**
|
|
52
|
+
* Intercept all console methods to capture output
|
|
53
|
+
*/
|
|
54
|
+
private interceptConsole;
|
|
55
|
+
/**
|
|
56
|
+
* Create the floating UI button and menu
|
|
57
|
+
*/
|
|
58
|
+
private createUI;
|
|
59
|
+
/**
|
|
60
|
+
* Update the log count badge in the menu
|
|
61
|
+
*/
|
|
62
|
+
private updateButtonBadge;
|
|
63
|
+
/**
|
|
64
|
+
* Format a single log entry as a string
|
|
65
|
+
* @param log - The log entry to format
|
|
66
|
+
* @returns Formatted log string
|
|
67
|
+
*/
|
|
68
|
+
private formatLog;
|
|
69
|
+
/**
|
|
70
|
+
* Setup global window.__console__ methods for easy access
|
|
71
|
+
*/
|
|
72
|
+
private setupGlobalMethods;
|
|
73
|
+
/**
|
|
74
|
+
* Show success indicator on the button
|
|
75
|
+
*/
|
|
76
|
+
private showCopySuccess;
|
|
77
|
+
/**
|
|
78
|
+
* Show error indicator on the button and fallback message
|
|
79
|
+
*/
|
|
80
|
+
private showCopyError;
|
|
81
|
+
/**
|
|
82
|
+
* Fallback copy method using textarea (for mobile devices)
|
|
83
|
+
* @param text - The text to copy
|
|
84
|
+
*/
|
|
85
|
+
private copyUsingTextarea;
|
|
86
|
+
/**
|
|
87
|
+
* Show a modal dialog with all captured logs
|
|
88
|
+
*/
|
|
89
|
+
private showLogsModal;
|
|
90
|
+
/**
|
|
91
|
+
* Get all captured logs as a formatted string
|
|
92
|
+
* @returns Formatted log string with timestamps and levels
|
|
93
|
+
*/
|
|
94
|
+
getLogs(): string;
|
|
95
|
+
/**
|
|
96
|
+
* Clear all captured logs
|
|
97
|
+
*/
|
|
98
|
+
clearLogs(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Copy logs to clipboard
|
|
101
|
+
*/
|
|
102
|
+
copyLogs(): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Download logs as a text file
|
|
105
|
+
*/
|
|
106
|
+
downloadLogs(): void;
|
|
107
|
+
/**
|
|
108
|
+
* Restore original console methods and clean up UI
|
|
109
|
+
*/
|
|
110
|
+
destroy(): void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Convenience function to initialize ConsoleCaptureUI
|
|
114
|
+
* @returns A new ConsoleCaptureUI instance
|
|
115
|
+
*/
|
|
116
|
+
export declare function initConsoleCaptureUI(): ConsoleCaptureUI;
|
|
117
|
+
export {};
|
|
118
|
+
//# sourceMappingURL=ConsoleCaptureUI.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConsoleCaptureUI.d.ts","sourceRoot":"","sources":["../../src/ui/ConsoleCaptureUI.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,WAAW,CAAC,EAAE;YACV,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,QAAQ,EAAE,MAAM,IAAI,CAAC;YACrB,KAAK,EAAE,MAAM,IAAI,CAAC;YAClB,GAAG,EAAE,MAAM,MAAM,CAAC;YAClB,IAAI,EAAE,gBAAgB,EAAE,CAAC;SAC5B,CAAC;KACL;CACJ;AAED,kCAAkC;AAClC,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACrB;AAQD;;GAEG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,eAAe,CAAkD;IACzE,OAAO,CAAC,eAAe,CAA4B;IAEnD;;;OAGG;;IAgBH;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA0BxB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAqKhB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAoBjB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgB1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAmCzB;;OAEG;IACH,OAAO,CAAC,aAAa;IA+HrB;;;OAGG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,SAAS,IAAI,IAAI;IAMjB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB/B;;OAEG;IACH,YAAY,IAAI,IAAI;IAsBpB;;OAEG;IACH,OAAO,IAAI,IAAI;CA8BlB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,CAEvD"}
|