@atomicmail/mcp 0.1.0 → 0.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.
Files changed (49) hide show
  1. package/README.md +45 -145
  2. package/esm/lib/src/agent-auth-http.d.ts +26 -0
  3. package/esm/lib/src/agent-auth-http.d.ts.map +1 -0
  4. package/esm/lib/src/agent-auth-http.js +76 -0
  5. package/esm/{mcp/src/credentials.d.ts → lib/src/agent-credentials-store.d.ts} +3 -2
  6. package/esm/lib/src/agent-credentials-store.d.ts.map +1 -0
  7. package/esm/{mcp/src/credentials.js → lib/src/agent-credentials-store.js} +19 -16
  8. package/esm/lib/src/agent-help-content.d.ts +4 -0
  9. package/esm/lib/src/agent-help-content.d.ts.map +1 -0
  10. package/esm/lib/src/agent-help-content.js +236 -0
  11. package/esm/lib/src/agent-jmap.d.ts +49 -0
  12. package/esm/lib/src/agent-jmap.d.ts.map +1 -0
  13. package/esm/lib/src/agent-jmap.js +130 -0
  14. package/esm/lib/src/agent-jwt.d.ts +14 -0
  15. package/esm/lib/src/agent-jwt.d.ts.map +1 -0
  16. package/esm/lib/src/agent-jwt.js +29 -0
  17. package/esm/lib/src/agent-pow.d.ts +5 -0
  18. package/esm/lib/src/agent-pow.d.ts.map +1 -0
  19. package/esm/lib/src/agent-pow.js +49 -0
  20. package/esm/lib/src/agent-resolve-config.d.ts +24 -0
  21. package/esm/lib/src/agent-resolve-config.d.ts.map +1 -0
  22. package/esm/lib/src/agent-resolve-config.js +70 -0
  23. package/esm/lib/src/agent-session.d.ts +62 -0
  24. package/esm/lib/src/agent-session.d.ts.map +1 -0
  25. package/esm/lib/src/agent-session.js +206 -0
  26. package/esm/lib/src/agent-vars.d.ts +23 -0
  27. package/esm/lib/src/agent-vars.d.ts.map +1 -0
  28. package/esm/lib/src/agent-vars.js +65 -0
  29. package/esm/mcp/src/main.js +31 -61
  30. package/esm/mcp/src/tools/help.d.ts +3 -0
  31. package/esm/mcp/src/tools/help.d.ts.map +1 -0
  32. package/esm/mcp/src/tools/help.js +22 -0
  33. package/esm/mcp/src/tools/jmap.d.ts +2 -2
  34. package/esm/mcp/src/tools/jmap.d.ts.map +1 -1
  35. package/esm/mcp/src/tools/jmap.js +53 -140
  36. package/esm/mcp/src/tools/register.d.ts +2 -2
  37. package/esm/mcp/src/tools/register.d.ts.map +1 -1
  38. package/esm/mcp/src/tools/register.js +9 -45
  39. package/package.json +1 -1
  40. package/esm/mcp/src/auth-session.d.ts +0 -88
  41. package/esm/mcp/src/auth-session.d.ts.map +0 -1
  42. package/esm/mcp/src/auth-session.js +0 -378
  43. package/esm/mcp/src/credentials.d.ts.map +0 -1
  44. package/esm/mcp/src/docs-content.d.ts +0 -4
  45. package/esm/mcp/src/docs-content.d.ts.map +0 -1
  46. package/esm/mcp/src/docs-content.js +0 -405
  47. package/esm/mcp/src/tools/docs.d.ts +0 -3
  48. package/esm/mcp/src/tools/docs.d.ts.map +0 -1
  49. package/esm/mcp/src/tools/docs.js +0 -22
@@ -1,145 +1,103 @@
1
- import { readFile } from "node:fs/promises";
2
- import { isAbsolute, resolve as resolvePath } from "node:path";
3
1
  import { z } from "zod";
4
- const DEFAULT_USING = [
5
- "urn:ietf:params:jmap:core",
6
- "urn:ietf:params:jmap:mail",
7
- ];
8
- function parseEnvelope(raw, defaultUsing, source) {
9
- let value;
10
- try {
11
- value = JSON.parse(raw);
12
- }
13
- catch (err) {
14
- throw new Error(`${source} is not valid JSON: ${err.message}`);
15
- }
16
- if (Array.isArray(value)) {
17
- return { using: defaultUsing, methodCalls: value };
18
- }
19
- if (value !== null &&
20
- typeof value === "object" &&
21
- Array.isArray(value.methodCalls)) {
22
- const obj = value;
23
- const using = Array.isArray(obj.using)
24
- ? obj.using.filter((u) => typeof u === "string")
25
- : defaultUsing;
26
- return { using, methodCalls: obj.methodCalls };
27
- }
28
- throw new Error(`${source} must be a methodCalls array, e.g. ` +
29
- '[["Mailbox/get",{...},"m0"]], or an object with a methodCalls array.');
30
- }
2
+ import { DEFAULT_JMAP_USING, readOpsFile, runJmapRequest, } from "../../../lib/src/agent-jmap.js";
31
3
  export function registerJmapTool(server, session) {
32
- const apiUrl = session.apiUrl;
33
4
  server.registerTool("jmap_request", {
34
5
  title: "Send a JMAP request",
35
- description: "Send a JMAP request to your AtomicMail inbox. Auth (session and " +
36
- "capability JWT rotation) is handled automaticallyjust provide " +
37
- "the JMAP method calls.\n\n" +
38
- "Provide exactly one request body:\n" +
39
- " methodCalls inline JMAP batch, an array of " +
40
- "[methodName, args, callId] entries.\n" +
41
- " • opsFile — path to a saved JSON preset. Relative paths " +
42
- "resolve against the credential directory (the same directory " +
43
- "the skill's --ops-file flag uses), so common requests can be " +
44
- "saved once and reused.\n\n" +
45
- "Tip: call jmap_session first to discover your accountId.\n\n" +
46
- "Examples:\n" +
47
- ' methodCalls: [["Mailbox/get", {"accountId": "..."}, "m0"]]\n' +
48
- ' methodCalls: [["Email/query", {"accountId": "...", "filter": {"inMailbox": "..."}}, "q0"]]\n' +
49
- " opsFile: fetch_last_100.json",
6
+ description: "Send a JMAP method-call batch. Auth and JWT rotation are automatic. " +
7
+ "Provide exactly one of: `ops` (JSON string methodCalls array or full " +
8
+ "envelope) or `ops_file` (preset path; relative paths resolve against " +
9
+ "the credential directory). Tokens `$VAR_NAME` (uppercase `$FOO_BAR`) in " +
10
+ "either input are replaced: `$ACCOUNT_ID` and `$INBOX` come from the JMAP " +
11
+ "session; pass other names via `vars`.",
50
12
  inputSchema: z.object({
51
13
  using: z
52
14
  .array(z.string())
53
- .default(DEFAULT_USING)
54
- .describe("JMAP capability URNs. Defaults to core + mail. Add " +
55
- '"urn:ietf:params:jmap:submission" when sending email. ' +
56
- "Ignored if the file/inline body already sets a 'using' field."),
57
- methodCalls: z
58
- .array(z.json())
15
+ .default([...DEFAULT_JMAP_USING])
16
+ .describe("JMAP capability URNs when `ops` omits `using`. Ignored if the " +
17
+ "JSON body already sets `using`."),
18
+ ops: z
19
+ .string()
59
20
  .optional()
60
- .describe("Inline JMAP method calls array. Each entry is " +
61
- '[methodName, args, callId], e.g. ["Mailbox/get", {"accountId": "abc"}, "m0"]. ' +
62
- "Mutually exclusive with opsFile."),
63
- opsFile: z
21
+ .describe("Inline JSON: methodCalls array or { using, methodCalls }. " +
22
+ "Mutually exclusive with ops_file."),
23
+ ops_file: z
64
24
  .string()
65
25
  .optional()
66
- .describe("Path to a saved JMAP preset file. Relative paths resolve " +
67
- "against the credential directory. The file may contain " +
68
- "either a methodCalls array or a full {using, methodCalls} " +
69
- "envelope. Mutually exclusive with methodCalls."),
26
+ .describe("Path to a preset JSON file. Mutually exclusive with ops."),
27
+ vars: z
28
+ .record(z.string().regex(/^[A-Z][A-Z0-9_]*$/), z.string())
29
+ .optional()
30
+ .describe("Map of placeholder names (no `$`) to string values, e.g. " +
31
+ '{ "TO": "a@b.com", "SUBJECT": "Hi" } for `$TO` and `$SUBJECT` in ' +
32
+ "ops or ops_file. Overrides session values for `ACCOUNT_ID` / `INBOX` if set."),
70
33
  }),
71
- }, async ({ using, methodCalls, opsFile }) => {
34
+ }, async ({ using, ops, ops_file, vars }) => {
72
35
  try {
73
- if (methodCalls && opsFile) {
36
+ if (ops && ops_file) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: "ops and ops_file are mutually exclusive — provide one.",
42
+ },
43
+ ],
44
+ isError: true,
45
+ };
46
+ }
47
+ if (!ops && !ops_file) {
74
48
  return {
75
49
  content: [
76
50
  {
77
51
  type: "text",
78
- text: "methodCalls and opsFile are mutually exclusive — provide one.",
52
+ text: "Provide either ops or ops_file.",
79
53
  },
80
54
  ],
81
55
  isError: true,
82
56
  };
83
57
  }
84
- let envelope;
85
- if (opsFile) {
86
- const filePath = isAbsolute(opsFile)
87
- ? opsFile
88
- : resolvePath(session.credentialDir, opsFile);
89
- let raw;
58
+ let raw;
59
+ let sourceLabel;
60
+ if (ops_file) {
90
61
  try {
91
- raw = await readFile(filePath, "utf-8");
62
+ raw = await readOpsFile(session.credentialDir, ops_file);
92
63
  }
93
64
  catch (err) {
94
65
  return {
95
66
  content: [
96
67
  {
97
68
  type: "text",
98
- text: `Could not read opsFile '${filePath}': ${err.message}`,
69
+ text: `Could not read ops_file: ${err.message}`,
99
70
  },
100
71
  ],
101
72
  isError: true,
102
73
  };
103
74
  }
104
- envelope = parseEnvelope(raw, using, `opsFile '${filePath}'`);
105
- }
106
- else if (methodCalls) {
107
- envelope = { using, methodCalls };
75
+ sourceLabel = `ops_file '${ops_file}'`;
108
76
  }
109
77
  else {
110
- return {
111
- content: [
112
- {
113
- type: "text",
114
- text: "Provide either methodCalls or opsFile.",
115
- },
116
- ],
117
- isError: true,
118
- };
78
+ raw = ops;
79
+ sourceLabel = "ops";
119
80
  }
120
- const capabilityJWT = await session.getCapabilityToken();
121
- const res = await fetch(`${apiUrl}/jmap/`, {
122
- method: "POST",
123
- headers: {
124
- "Content-Type": "application/json",
125
- Authorization: `Bearer ${capabilityJWT}`,
126
- },
127
- body: JSON.stringify(envelope),
81
+ const { ok, status, bodyText } = await runJmapRequest({
82
+ session,
83
+ opsJson: raw,
84
+ defaultUsing: using,
85
+ sourceLabel,
86
+ vars,
128
87
  });
129
- const text = await res.text();
130
- if (!res.ok) {
88
+ if (!ok) {
131
89
  return {
132
90
  content: [
133
91
  {
134
92
  type: "text",
135
- text: `JMAP request failed (HTTP ${res.status}): ${text}`,
93
+ text: `JMAP request failed (HTTP ${status}): ${bodyText}`,
136
94
  },
137
95
  ],
138
96
  isError: true,
139
97
  };
140
98
  }
141
99
  return {
142
- content: [{ type: "text", text }],
100
+ content: [{ type: "text", text: bodyText }],
143
101
  };
144
102
  }
145
103
  catch (error) {
@@ -154,49 +112,4 @@ export function registerJmapTool(server, session) {
154
112
  };
155
113
  }
156
114
  });
157
- server.registerTool("jmap_session", {
158
- title: "Get JMAP session",
159
- description: "Fetch the JMAP session object via GET /.well-known/jmap. Returns " +
160
- "your accountId, capabilities, and mailbox discovery URLs. Call " +
161
- "this first to learn your accountId before issuing jmap_request " +
162
- "method calls.",
163
- inputSchema: {},
164
- annotations: {
165
- readOnlyHint: true,
166
- idempotentHint: true,
167
- },
168
- }, async () => {
169
- try {
170
- const capabilityJWT = await session.getCapabilityToken();
171
- const res = await fetch(`${apiUrl}/.well-known/jmap`, {
172
- headers: { Authorization: `Bearer ${capabilityJWT}` },
173
- });
174
- const text = await res.text();
175
- if (!res.ok) {
176
- return {
177
- content: [
178
- {
179
- type: "text",
180
- text: `Session fetch failed (HTTP ${res.status}): ${text}`,
181
- },
182
- ],
183
- isError: true,
184
- };
185
- }
186
- return {
187
- content: [{ type: "text", text }],
188
- };
189
- }
190
- catch (error) {
191
- return {
192
- content: [
193
- {
194
- type: "text",
195
- text: `Session fetch error: ${error instanceof Error ? error.message : String(error)}`,
196
- },
197
- ],
198
- isError: true,
199
- };
200
- }
201
- });
202
115
  }
@@ -1,4 +1,4 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp";
2
- import type { AuthSession } from "../auth-session.js";
3
- export declare function registerRegisterTool(server: McpServer, session: AuthSession): void;
2
+ import type { AgentSession } from "../../../lib/src/agent-session.js";
3
+ export declare function registerRegisterTool(server: McpServer, session: AgentSession): void;
4
4
  //# sourceMappingURL=register.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/mcp/src/tools/register.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,IAAI,CAuFN"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../src/mcp/src/tools/register.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAEtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEtE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,YAAY,GACpB,IAAI,CAiDN"}
@@ -1,65 +1,29 @@
1
1
  import { z } from "zod";
2
2
  export function registerRegisterTool(server, session) {
3
3
  server.registerTool("register", {
4
- title: "Register a new AtomicMail inbox",
5
- description: "Create a new AtomicMail account. Performs a Proof-of-Work signup, " +
6
- "fetches the first session and capability JWTs, and writes " +
7
- "credentials.json + session.jwt + capability.jwt into the " +
8
- "configured credential directory. After this returns, the agent " +
9
- "can immediately call jmap_session and jmap_request without any " +
10
- "further setup. Idempotent guard: if an API key is already " +
11
- "configured, this tool will refuse to register a second account.",
4
+ title: "Register an Atomic Mail inbox",
5
+ description: "Proof-of-work signup; persists credentials. Idempotent when the same " +
6
+ "username matches the inbox already stored. A different username " +
7
+ "replaces credentials and creates a new inbox. Returns JSON with " +
8
+ "inbox, accountId, and apiKey on first signup only.",
12
9
  inputSchema: z.object({
13
10
  username: z
14
11
  .string()
15
12
  .min(1)
16
- .describe("Desired username for the new inbox. Becomes the local-part of your email address."),
13
+ .describe("Desired username (local-part of your @atomicmail.ai address)."),
17
14
  }),
18
15
  annotations: {
19
- idempotentHint: false,
16
+ idempotentHint: true,
20
17
  destructiveHint: false,
21
18
  },
22
19
  }, async ({ username }) => {
23
20
  try {
24
- if (session.hasApiKey) {
25
- return {
26
- content: [
27
- {
28
- type: "text",
29
- text: "An API key is already configured for this MCP server. " +
30
- "Refusing to register a second account. Use jmap_request " +
31
- "to interact with your existing inbox, or remove " +
32
- "credentials.json (and unset ATOMIC_MAIL_API_KEY) before " +
33
- "registering a new one.",
34
- },
35
- ],
36
- };
37
- }
38
- const { apiKey, inboxId } = await session.signup(username);
21
+ const result = await session.register(username);
39
22
  return {
40
23
  content: [
41
24
  {
42
25
  type: "text",
43
- text: [
44
- `Account created successfully for "${username}".`,
45
- "",
46
- `Inbox ID: ${inboxId}`,
47
- `API Key: ${apiKey}`,
48
- "",
49
- "Credentials persisted to:",
50
- ` ${session.files.credentialsFile}`,
51
- ` ${session.files.sessionFile}`,
52
- ` ${session.files.capabilityFile}`,
53
- "",
54
- "IMPORTANT: The API key is stored in credentials.json above. " +
55
- "Treat that file as a secret (mode 0600). To re-use this " +
56
- "account from another machine or a fresh MCP host, copy " +
57
- "credentials.json over, or set ATOMIC_MAIL_API_KEY in the " +
58
- "new environment.",
59
- "",
60
- "You are now logged in. Call jmap_session next to discover " +
61
- "your accountId, then use jmap_request for all email operations.",
62
- ].join("\n"),
26
+ text: JSON.stringify(result, null, 2),
63
27
  },
64
28
  ],
65
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomicmail/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "AtomicMail MCP server — local stdio proxy with PoW auth and JMAP, for AI agents.",
5
5
  "keywords": [
6
6
  "atomic-mail",
@@ -1,88 +0,0 @@
1
- import { type SkillFiles } from "./credentials.js";
2
- export declare const SESSION_TTL_MS: number;
3
- export declare const CAPABILITY_TTL_MS: number;
4
- export declare const SESSION_SAFETY_MARGIN_MS = 60000;
5
- export declare const CAPABILITY_SAFETY_MARGIN_MS = 20000;
6
- export interface JwtPayload {
7
- exp?: number;
8
- iat?: number;
9
- jti?: string;
10
- inboxId?: string;
11
- [key: string]: unknown;
12
- }
13
- export declare function decodeJwtPayload<T = JwtPayload>(jwt: string): T;
14
- export declare function isJwtExpired(jwt: string, marginMs: number): boolean;
15
- export type ConfigSource = "credentials-file" | "env" | "mixed" | "incomplete";
16
- export interface ResolvedConfig {
17
- authUrl: string;
18
- apiUrl: string;
19
- scryptSalt: string;
20
- apiKey?: string;
21
- inboxId?: string;
22
- credentialDir: string;
23
- files: SkillFiles;
24
- source: ConfigSource;
25
- }
26
- /**
27
- * Resolve credential directory from (in priority order):
28
- * 1. ATOMIC_MAIL_CREDENTIALS_DIR env var
29
- * 2. ~/.atomicmail/ (POSIX) or %USERPROFILE%/.atomicmail (Windows)
30
- */
31
- export declare function resolveCredentialDir(): string;
32
- /**
33
- * Resolve server configuration from:
34
- * 1. credentials.json in the credential directory (if present and valid).
35
- * 2. ATOMIC_MAIL_* environment variables (overlay on top of (1) so env
36
- * always wins on a per-field basis).
37
- *
38
- * The auth and api base URLs MUST be resolvable from at least one source.
39
- * PoW scrypt salt defaults to the deployment constant when not set in env or
40
- * credentials.json.
41
- * The api key is optional; without it, the agent must call the register tool
42
- * before any JMAP request.
43
- */
44
- export declare function resolveConfig(): Promise<ResolvedConfig>;
45
- export interface AuthSessionConfig {
46
- authUrl: string;
47
- apiUrl: string;
48
- scryptSalt: string;
49
- apiKey?: string;
50
- inboxId?: string;
51
- credentialDir: string;
52
- files: SkillFiles;
53
- }
54
- export declare class AuthSession {
55
- private readonly authUrl;
56
- readonly apiUrl: string;
57
- private readonly scryptSalt;
58
- private apiKey;
59
- private inboxId;
60
- readonly credentialDir: string;
61
- readonly files: SkillFiles;
62
- private sessionJWT;
63
- private capabilityJWT;
64
- constructor(cfg: AuthSessionConfig);
65
- /** Construct a session and load any previously persisted JWTs from disk. */
66
- static create(cfg: AuthSessionConfig): Promise<AuthSession>;
67
- get hasApiKey(): boolean;
68
- get currentInboxId(): string | undefined;
69
- private loadFromDisk;
70
- /**
71
- * Full PoW signup. Persists credentials.json + session.jwt + capability.jwt
72
- * to disk and updates in-memory state. Throws if an API key is already
73
- * configured (refuse to clobber an existing account).
74
- */
75
- signup(username: string): Promise<{
76
- apiKey: string;
77
- inboxId: string;
78
- }>;
79
- /**
80
- * Get a valid capability JWT, rotating session/capability tokens as needed.
81
- * This is the primary method tool handlers call before every JMAP request.
82
- */
83
- getCapabilityToken(): Promise<string>;
84
- private ensureSession;
85
- /** Clean shutdown. No-op today; reserved for future cleanup. */
86
- destroy(): void;
87
- }
88
- //# sourceMappingURL=auth-session.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth-session.d.ts","sourceRoot":"","sources":["../../../src/mcp/src/auth-session.ts"],"names":[],"mappings":"AAsBA,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,kBAAkB,CAAC;AAO1B,eAAO,MAAM,cAAc,QAAqB,CAAC;AACjD,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAI/C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAc/D;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQnE;AAgLD,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,KAAK,GACL,OAAO,GACP,YAAY,CAAC;AAEjB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAW7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC,CAqD7D;AAID,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAqB;IACpC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAE3B,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,aAAa,CAAqB;gBAE9B,GAAG,EAAE,iBAAiB;IAUlC,4EAA4E;WAC/D,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC;IAMjE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;YAEa,YAAY;IAK1B;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IA6CF;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;YA8B7B,aAAa;IA0B3B,gEAAgE;IAChE,OAAO,IAAI,IAAI;CAGhB"}