@atomicmail/mcp 0.1.0 → 0.2.1
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 +77 -187
- package/esm/_dnt.polyfills.d.ts +101 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +127 -0
- package/esm/lib/agent/auth/agent-auth-http.d.ts +26 -0
- package/esm/lib/agent/auth/agent-auth-http.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-auth-http.js +76 -0
- package/esm/lib/agent/auth/agent-jwt.d.ts +14 -0
- package/esm/lib/agent/auth/agent-jwt.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-jwt.js +29 -0
- package/esm/lib/agent/auth/agent-pow.d.ts +5 -0
- package/esm/lib/agent/auth/agent-pow.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-pow.js +49 -0
- package/esm/lib/agent/jmap/agent-help-content.d.ts +4 -0
- package/esm/lib/agent/jmap/agent-help-content.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-help-content.js +244 -0
- package/esm/lib/agent/jmap/agent-jmap.d.ts +49 -0
- package/esm/lib/agent/jmap/agent-jmap.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-jmap.js +174 -0
- package/esm/lib/agent/jmap/agent-vars.d.ts +23 -0
- package/esm/lib/agent/jmap/agent-vars.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-vars.js +65 -0
- package/esm/{mcp/src/credentials.d.ts → lib/agent/session/agent-credentials-store.d.ts} +3 -2
- package/esm/lib/agent/session/agent-credentials-store.d.ts.map +1 -0
- package/esm/{mcp/src/credentials.js → lib/agent/session/agent-credentials-store.js} +19 -16
- package/esm/lib/agent/session/agent-resolve-config.d.ts +24 -0
- package/esm/lib/agent/session/agent-resolve-config.d.ts.map +1 -0
- package/esm/lib/agent/session/agent-resolve-config.js +70 -0
- package/esm/lib/agent/session/agent-session.d.ts +62 -0
- package/esm/lib/agent/session/agent-session.d.ts.map +1 -0
- package/esm/lib/agent/session/agent-session.js +206 -0
- package/esm/lib/core/consts.d.ts.map +1 -0
- package/esm/lib/core/types.d.ts +2 -0
- package/esm/lib/core/types.d.ts.map +1 -0
- package/esm/lib/core/types.js +1 -0
- package/esm/lib/core/utils.d.ts +10 -0
- package/esm/lib/core/utils.d.ts.map +1 -0
- package/esm/lib/core/utils.js +28 -0
- package/esm/lib/mod.d.ts +14 -0
- package/esm/lib/mod.d.ts.map +1 -0
- package/esm/lib/mod.js +13 -0
- package/esm/lib/network/auth-client.d.ts +57 -0
- package/esm/lib/network/auth-client.d.ts.map +1 -0
- package/esm/lib/network/auth-client.js +188 -0
- package/esm/mcp/main.d.ts +3 -0
- package/esm/mcp/main.d.ts.map +1 -0
- package/esm/mcp/main.js +86 -0
- package/esm/mcp/tools/help.d.ts +3 -0
- package/esm/mcp/tools/help.d.ts.map +1 -0
- package/esm/mcp/tools/help.js +22 -0
- package/esm/mcp/{src/tools → tools}/jmap.d.ts +2 -2
- package/esm/mcp/tools/jmap.d.ts.map +1 -0
- package/esm/mcp/tools/jmap.js +115 -0
- package/esm/mcp/{src/tools → tools}/register.d.ts +2 -2
- package/esm/mcp/tools/register.d.ts.map +1 -0
- package/esm/mcp/tools/register.js +43 -0
- package/package.json +5 -5
- package/presets/list_inbox.json +39 -0
- package/presets/reply.json +75 -0
- package/presets/send_mail.json +42 -0
- package/esm/lib/src/consts.d.ts.map +0 -1
- package/esm/mcp/src/auth-session.d.ts +0 -88
- package/esm/mcp/src/auth-session.d.ts.map +0 -1
- package/esm/mcp/src/auth-session.js +0 -378
- package/esm/mcp/src/credentials.d.ts.map +0 -1
- package/esm/mcp/src/docs-content.d.ts +0 -4
- package/esm/mcp/src/docs-content.d.ts.map +0 -1
- package/esm/mcp/src/docs-content.js +0 -405
- package/esm/mcp/src/main.d.ts +0 -3
- package/esm/mcp/src/main.d.ts.map +0 -1
- package/esm/mcp/src/main.js +0 -116
- package/esm/mcp/src/tools/docs.d.ts +0 -3
- package/esm/mcp/src/tools/docs.d.ts.map +0 -1
- package/esm/mcp/src/tools/docs.js +0 -22
- package/esm/mcp/src/tools/jmap.d.ts.map +0 -1
- package/esm/mcp/src/tools/jmap.js +0 -202
- package/esm/mcp/src/tools/register.d.ts.map +0 -1
- package/esm/mcp/src/tools/register.js +0 -79
- /package/esm/lib/{src → core}/consts.d.ts +0 -0
- /package/esm/lib/{src → core}/consts.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-credentials-store.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/session/agent-credentials-store.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAOjE;AAMD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiCxE;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAOlC;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B;AAED,0EAA0E;AAC1E,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
// Credential file I/O
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// capability.jwt — short-lived (~2min): rotates via /capability.
|
|
5
|
-
//
|
|
6
|
-
// Files are written with mode 0600 so other local users cannot read them.
|
|
7
|
-
//
|
|
8
|
-
// This file mirrors skill/scripts/lib/credentials.ts so that the MCP server
|
|
9
|
-
// reads and writes the same on-disk layout as the atomic-mail-signup /
|
|
10
|
-
// atomic-mail-jmap CLIs from the @atomic-mail/agent-skill skill. The two
|
|
11
|
-
// are interchangeable: you can run the skill CLI to bootstrap credentials
|
|
12
|
-
// and the MCP server will pick them up automatically (see resolveConfig
|
|
13
|
-
// in auth-session.ts).
|
|
14
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
// Credential file I/O shared by MCP and AgentSkill.
|
|
2
|
+
// Three files: credentials.json, session.jwt, capability.jwt (mode 0600).
|
|
3
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
15
4
|
import { dirname, join, resolve } from "node:path";
|
|
16
5
|
export function defaultFilesFromOutDir(outDir) {
|
|
17
6
|
const base = resolve(outDir);
|
|
@@ -35,7 +24,7 @@ export async function readCredentials(path) {
|
|
|
35
24
|
}
|
|
36
25
|
catch (err) {
|
|
37
26
|
throw new Error(`Could not read credentials file '${path}': ${err.message}. ` +
|
|
38
|
-
"Did you run
|
|
27
|
+
"Did you run register first?");
|
|
39
28
|
}
|
|
40
29
|
let obj;
|
|
41
30
|
try {
|
|
@@ -58,7 +47,6 @@ export async function readCredentials(path) {
|
|
|
58
47
|
}
|
|
59
48
|
return obj;
|
|
60
49
|
}
|
|
61
|
-
/** Like readCredentials, but returns undefined when the file does not exist. */
|
|
62
50
|
export async function tryReadCredentials(path) {
|
|
63
51
|
try {
|
|
64
52
|
await readFile(path, "utf-8");
|
|
@@ -81,3 +69,18 @@ export async function tryReadJwtFile(path) {
|
|
|
81
69
|
return undefined;
|
|
82
70
|
}
|
|
83
71
|
}
|
|
72
|
+
/** Best-effort removal of credential artifacts (ignore missing files). */
|
|
73
|
+
export async function unlinkCredentialArtifacts(files) {
|
|
74
|
+
for (const p of [
|
|
75
|
+
files.credentialsFile,
|
|
76
|
+
files.sessionFile,
|
|
77
|
+
files.capabilityFile,
|
|
78
|
+
]) {
|
|
79
|
+
try {
|
|
80
|
+
await unlink(p);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type SkillFiles } from "./agent-credentials-store.js";
|
|
2
|
+
export type ConfigSource = "credentials-file" | "env" | "mixed" | "incomplete";
|
|
3
|
+
export interface ResolvedAgentConfig {
|
|
4
|
+
authUrl: string;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
scryptSalt: string;
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
inboxId?: string;
|
|
9
|
+
credentialDir: string;
|
|
10
|
+
files: SkillFiles;
|
|
11
|
+
source: ConfigSource;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Default credential directory:
|
|
15
|
+
* 1. ATOMIC_MAIL_CREDENTIALS_DIR
|
|
16
|
+
* 2. ~/.atomicmail/ or %USERPROFILE%/.atomicmail
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveCredentialDir(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Merge credentials.json with ATOMIC_MAIL_* env (env wins per field).
|
|
21
|
+
* authUrl and apiUrl must resolve from at least one source.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveAgentConfigFromEnv(): Promise<ResolvedAgentConfig>;
|
|
24
|
+
//# sourceMappingURL=agent-resolve-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-resolve-config.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/session/agent-resolve-config.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,KAAK,GACL,OAAO,GACP,YAAY,CAAC;AAEjB,MAAM,WAAW,mBAAmB;IAClC,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;;;GAGG;AACH,wBAAsB,yBAAyB,IAAI,OAAO,CACxD,mBAAmB,CACpB,CAoDA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Resolve MCP / process credential dir + URLs from env + credentials.json.
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { DEFAULT_POW_SCRYPT_SALT_HEX } from "../../core/consts.js";
|
|
4
|
+
import { defaultFilesFromOutDir, tryReadCredentials, } from "./agent-credentials-store.js";
|
|
5
|
+
/**
|
|
6
|
+
* Default credential directory:
|
|
7
|
+
* 1. ATOMIC_MAIL_CREDENTIALS_DIR
|
|
8
|
+
* 2. ~/.atomicmail/ or %USERPROFILE%/.atomicmail
|
|
9
|
+
*/
|
|
10
|
+
export function resolveCredentialDir() {
|
|
11
|
+
const fromEnv = process.env.ATOMIC_MAIL_CREDENTIALS_DIR;
|
|
12
|
+
if (fromEnv && fromEnv.length > 0)
|
|
13
|
+
return fromEnv;
|
|
14
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
15
|
+
if (!home) {
|
|
16
|
+
throw new Error("Cannot determine default credential directory: HOME and USERPROFILE " +
|
|
17
|
+
"are both unset. Set ATOMIC_MAIL_CREDENTIALS_DIR explicitly.");
|
|
18
|
+
}
|
|
19
|
+
return `${home.replace(/[\\/]+$/, "")}/.atomicmail`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Merge credentials.json with ATOMIC_MAIL_* env (env wins per field).
|
|
23
|
+
* authUrl and apiUrl must resolve from at least one source.
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveAgentConfigFromEnv() {
|
|
26
|
+
const credentialDir = resolveCredentialDir();
|
|
27
|
+
const files = defaultFilesFromOutDir(credentialDir);
|
|
28
|
+
const fileCreds = await tryReadCredentials(files.credentialsFile);
|
|
29
|
+
const env = process.env;
|
|
30
|
+
const envAuthUrl = env.ATOMIC_MAIL_AUTH_URL;
|
|
31
|
+
const envApiUrl = env.ATOMIC_MAIL_API_URL;
|
|
32
|
+
const envSalt = env.ATOMIC_MAIL_SCRYPT_SALT;
|
|
33
|
+
const envApiKey = env.ATOMIC_MAIL_API_KEY;
|
|
34
|
+
const authUrl = envAuthUrl ?? fileCreds?.authUrl;
|
|
35
|
+
const apiUrl = envApiUrl ?? fileCreds?.apiUrl;
|
|
36
|
+
const scryptSalt = envSalt ?? fileCreds?.scryptSalt ??
|
|
37
|
+
DEFAULT_POW_SCRYPT_SALT_HEX;
|
|
38
|
+
const apiKey = envApiKey ?? fileCreds?.apiKey;
|
|
39
|
+
const inboxId = fileCreds?.inboxId;
|
|
40
|
+
const missing = [];
|
|
41
|
+
if (!authUrl)
|
|
42
|
+
missing.push("ATOMIC_MAIL_AUTH_URL");
|
|
43
|
+
if (!apiUrl)
|
|
44
|
+
missing.push("ATOMIC_MAIL_API_URL");
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
throw new Error(`Missing required configuration: ${missing.join(", ")}. ` +
|
|
47
|
+
`Provide these via environment variables, or place a populated ` +
|
|
48
|
+
`credentials.json in '${credentialDir}' (run register first, or set ` +
|
|
49
|
+
`ATOMIC_MAIL_CREDENTIALS_DIR).`);
|
|
50
|
+
}
|
|
51
|
+
const usingFile = fileCreds !== undefined;
|
|
52
|
+
const usingEnv = !!(envAuthUrl || envApiUrl || envSalt || envApiKey);
|
|
53
|
+
const source = usingFile && usingEnv
|
|
54
|
+
? "mixed"
|
|
55
|
+
: usingFile
|
|
56
|
+
? "credentials-file"
|
|
57
|
+
: usingEnv
|
|
58
|
+
? "env"
|
|
59
|
+
: "incomplete";
|
|
60
|
+
return {
|
|
61
|
+
authUrl: authUrl.replace(/\/+$/, ""),
|
|
62
|
+
apiUrl: apiUrl.replace(/\/+$/, ""),
|
|
63
|
+
scryptSalt: scryptSalt,
|
|
64
|
+
apiKey,
|
|
65
|
+
inboxId,
|
|
66
|
+
credentialDir,
|
|
67
|
+
files,
|
|
68
|
+
source,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type SkillFiles } from "./agent-credentials-store.js";
|
|
2
|
+
export interface AgentSessionConfig {
|
|
3
|
+
authUrl: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
scryptSalt: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
inboxId?: string;
|
|
8
|
+
credentialDir: string;
|
|
9
|
+
files: SkillFiles;
|
|
10
|
+
}
|
|
11
|
+
export interface RegisterResult {
|
|
12
|
+
inbox: string;
|
|
13
|
+
accountId: string;
|
|
14
|
+
/** Present only on first-time signup (not idempotent replay). */
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
idempotent?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Local-part of an inbox email, or the whole string if no @. */
|
|
19
|
+
export declare function inboxLocalPart(inboxId: string): string;
|
|
20
|
+
export declare class AgentSession {
|
|
21
|
+
private readonly authUrl;
|
|
22
|
+
readonly apiUrl: string;
|
|
23
|
+
private readonly scryptSalt;
|
|
24
|
+
private apiKey;
|
|
25
|
+
private inboxId;
|
|
26
|
+
readonly credentialDir: string;
|
|
27
|
+
readonly files: SkillFiles;
|
|
28
|
+
private sessionJWT;
|
|
29
|
+
private capabilityJWT;
|
|
30
|
+
private cachedMailAccountId;
|
|
31
|
+
constructor(cfg: AgentSessionConfig);
|
|
32
|
+
static create(cfg: AgentSessionConfig): Promise<AgentSession>;
|
|
33
|
+
get hasApiKey(): boolean;
|
|
34
|
+
get currentInboxId(): string | undefined;
|
|
35
|
+
private loadFromDisk;
|
|
36
|
+
/**
|
|
37
|
+
* Primary JMAP mail accountId from GET /.well-known/jmap (cached).
|
|
38
|
+
*/
|
|
39
|
+
getPrimaryMailAccountId(): Promise<string>;
|
|
40
|
+
invalidateJmapSessionCache(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Register or return existing inbox when username matches (idempotent).
|
|
43
|
+
* Different username replaces on-disk credentials and creates a new inbox.
|
|
44
|
+
*/
|
|
45
|
+
register(username: string): Promise<RegisterResult>;
|
|
46
|
+
getCapabilityToken(): Promise<string>;
|
|
47
|
+
private ensureSession;
|
|
48
|
+
destroy(): void;
|
|
49
|
+
}
|
|
50
|
+
export interface PersistLoginWithApiKeyInput {
|
|
51
|
+
authUrl: string;
|
|
52
|
+
apiUrl: string;
|
|
53
|
+
scryptSalt: string;
|
|
54
|
+
apiKey: string;
|
|
55
|
+
files: SkillFiles;
|
|
56
|
+
onPowProgress?: (nonce: bigint) => void;
|
|
57
|
+
}
|
|
58
|
+
/** PoW login with an existing API key; writes credentials + JWT files. */
|
|
59
|
+
export declare function persistLoginWithApiKey(input: PersistLoginWithApiKeyInput): Promise<{
|
|
60
|
+
inboxId: string;
|
|
61
|
+
}>;
|
|
62
|
+
//# sourceMappingURL=agent-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/session/agent-session.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,UAAU,EAMhB,MAAM,8BAA8B,CAAC;AAatC,MAAM,WAAW,kBAAkB;IACjC,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,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAMD,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,qBAAa,YAAY;IACvB,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;IAC1C,OAAO,CAAC,mBAAmB,CAAqB;gBAEpC,GAAG,EAAE,kBAAkB;WAUtB,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAMnE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;YAEa,YAAY;IAU1B;;OAEG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAShD,0BAA0B,IAAI,IAAI;IAIlC;;;OAGG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAuEnD,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;YA4B7B,aAAa;IAyB3B,OAAO,IAAI,IAAI;CAGhB;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,UAAU,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,0EAA0E;AAC1E,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyB9B"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Stateful PoW + capability JWT + optional cached JMAP session (accountId).
|
|
2
|
+
import { tryReadCredentials, tryReadJwtFile, unlinkCredentialArtifacts, writeCredentials, writeJwtFile, } from "./agent-credentials-store.js";
|
|
3
|
+
import { CAPABILITY_SAFETY_MARGIN_MS, decodeJwtPayload, isJwtExpired, SESSION_SAFETY_MARGIN_MS, } from "../auth/agent-jwt.js";
|
|
4
|
+
import { extractPrimaryMailAccountId, fetchJmapWellKnown, } from "../jmap/agent-jmap.js";
|
|
5
|
+
import { fetchCapability, performPoWAndSession } from "../auth/agent-auth-http.js";
|
|
6
|
+
function normalizeUsername(u) {
|
|
7
|
+
return u.trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
/** Local-part of an inbox email, or the whole string if no @. */
|
|
10
|
+
export function inboxLocalPart(inboxId) {
|
|
11
|
+
const i = inboxId.indexOf("@");
|
|
12
|
+
return i === -1
|
|
13
|
+
? normalizeUsername(inboxId)
|
|
14
|
+
: normalizeUsername(inboxId.slice(0, i));
|
|
15
|
+
}
|
|
16
|
+
export class AgentSession {
|
|
17
|
+
authUrl;
|
|
18
|
+
apiUrl;
|
|
19
|
+
scryptSalt;
|
|
20
|
+
apiKey;
|
|
21
|
+
inboxId;
|
|
22
|
+
credentialDir;
|
|
23
|
+
files;
|
|
24
|
+
sessionJWT;
|
|
25
|
+
capabilityJWT;
|
|
26
|
+
cachedMailAccountId;
|
|
27
|
+
constructor(cfg) {
|
|
28
|
+
this.authUrl = cfg.authUrl.replace(/\/+$/, "");
|
|
29
|
+
this.apiUrl = cfg.apiUrl.replace(/\/+$/, "");
|
|
30
|
+
this.scryptSalt = cfg.scryptSalt;
|
|
31
|
+
this.apiKey = cfg.apiKey;
|
|
32
|
+
this.inboxId = cfg.inboxId;
|
|
33
|
+
this.credentialDir = cfg.credentialDir;
|
|
34
|
+
this.files = cfg.files;
|
|
35
|
+
}
|
|
36
|
+
static async create(cfg) {
|
|
37
|
+
const session = new AgentSession(cfg);
|
|
38
|
+
await session.loadFromDisk();
|
|
39
|
+
return session;
|
|
40
|
+
}
|
|
41
|
+
get hasApiKey() {
|
|
42
|
+
return this.apiKey !== undefined && this.apiKey.length > 0;
|
|
43
|
+
}
|
|
44
|
+
get currentInboxId() {
|
|
45
|
+
return this.inboxId;
|
|
46
|
+
}
|
|
47
|
+
async loadFromDisk() {
|
|
48
|
+
this.sessionJWT = await tryReadJwtFile(this.files.sessionFile);
|
|
49
|
+
this.capabilityJWT = await tryReadJwtFile(this.files.capabilityFile);
|
|
50
|
+
const disk = await tryReadCredentials(this.files.credentialsFile);
|
|
51
|
+
if (disk) {
|
|
52
|
+
this.apiKey = this.apiKey ?? disk.apiKey;
|
|
53
|
+
this.inboxId = this.inboxId ?? disk.inboxId;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Primary JMAP mail accountId from GET /.well-known/jmap (cached).
|
|
58
|
+
*/
|
|
59
|
+
async getPrimaryMailAccountId() {
|
|
60
|
+
if (this.cachedMailAccountId)
|
|
61
|
+
return this.cachedMailAccountId;
|
|
62
|
+
const cap = await this.getCapabilityToken();
|
|
63
|
+
const session = await fetchJmapWellKnown(this.apiUrl, cap);
|
|
64
|
+
const id = extractPrimaryMailAccountId(session);
|
|
65
|
+
this.cachedMailAccountId = id;
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
invalidateJmapSessionCache() {
|
|
69
|
+
this.cachedMailAccountId = undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Register or return existing inbox when username matches (idempotent).
|
|
73
|
+
* Different username replaces on-disk credentials and creates a new inbox.
|
|
74
|
+
*/
|
|
75
|
+
async register(username) {
|
|
76
|
+
const want = normalizeUsername(username);
|
|
77
|
+
if (this.hasApiKey && !this.inboxId) {
|
|
78
|
+
throw new Error("Cannot register: an API key is configured but inboxId is unknown. " +
|
|
79
|
+
"Fix credentials.json or unset ATOMIC_MAIL_API_KEY before registering.");
|
|
80
|
+
}
|
|
81
|
+
if (this.hasApiKey && this.inboxId) {
|
|
82
|
+
const have = inboxLocalPart(this.inboxId);
|
|
83
|
+
if (have === want) {
|
|
84
|
+
const accountId = await this.getPrimaryMailAccountId();
|
|
85
|
+
return {
|
|
86
|
+
inbox: this.inboxId,
|
|
87
|
+
accountId,
|
|
88
|
+
idempotent: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
await unlinkCredentialArtifacts(this.files);
|
|
92
|
+
this.apiKey = undefined;
|
|
93
|
+
this.inboxId = undefined;
|
|
94
|
+
this.sessionJWT = undefined;
|
|
95
|
+
this.capabilityJWT = undefined;
|
|
96
|
+
this.cachedMailAccountId = undefined;
|
|
97
|
+
}
|
|
98
|
+
const result = await performPoWAndSession({
|
|
99
|
+
authUrl: this.authUrl,
|
|
100
|
+
scryptSalt: this.scryptSalt,
|
|
101
|
+
username,
|
|
102
|
+
});
|
|
103
|
+
if (!result.apiKey) {
|
|
104
|
+
throw new Error("Signup did not return an apiKey — this indicates a server bug.");
|
|
105
|
+
}
|
|
106
|
+
this.apiKey = result.apiKey;
|
|
107
|
+
this.sessionJWT = result.sessionJWT;
|
|
108
|
+
await writeJwtFile(this.files.sessionFile, this.sessionJWT);
|
|
109
|
+
const capability = await fetchCapability(this.authUrl, this.sessionJWT);
|
|
110
|
+
this.capabilityJWT = capability;
|
|
111
|
+
await writeJwtFile(this.files.capabilityFile, capability);
|
|
112
|
+
const claims = decodeJwtPayload(capability);
|
|
113
|
+
if (typeof claims.inboxId !== "string" || claims.inboxId.length === 0) {
|
|
114
|
+
throw new Error("Capability JWT missing inboxId claim after signup.");
|
|
115
|
+
}
|
|
116
|
+
this.inboxId = claims.inboxId;
|
|
117
|
+
this.cachedMailAccountId = undefined;
|
|
118
|
+
const creds = {
|
|
119
|
+
apiKey: this.apiKey,
|
|
120
|
+
inboxId: this.inboxId,
|
|
121
|
+
authUrl: this.authUrl,
|
|
122
|
+
apiUrl: this.apiUrl,
|
|
123
|
+
scryptSalt: this.scryptSalt,
|
|
124
|
+
};
|
|
125
|
+
await writeCredentials(this.files.credentialsFile, creds);
|
|
126
|
+
const accountId = await this.getPrimaryMailAccountId();
|
|
127
|
+
return {
|
|
128
|
+
inbox: this.inboxId,
|
|
129
|
+
accountId,
|
|
130
|
+
apiKey: this.apiKey,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async getCapabilityToken() {
|
|
134
|
+
if (this.capabilityJWT &&
|
|
135
|
+
!isJwtExpired(this.capabilityJWT, CAPABILITY_SAFETY_MARGIN_MS)) {
|
|
136
|
+
return this.capabilityJWT;
|
|
137
|
+
}
|
|
138
|
+
await this.ensureSession();
|
|
139
|
+
if (!this.sessionJWT) {
|
|
140
|
+
throw new Error("Internal: ensureSession() left sessionJWT unset.");
|
|
141
|
+
}
|
|
142
|
+
const cap = await fetchCapability(this.authUrl, this.sessionJWT);
|
|
143
|
+
this.capabilityJWT = cap;
|
|
144
|
+
await writeJwtFile(this.files.capabilityFile, cap);
|
|
145
|
+
try {
|
|
146
|
+
const claims = decodeJwtPayload(cap);
|
|
147
|
+
if (typeof claims.inboxId === "string" && claims.inboxId.length > 0) {
|
|
148
|
+
this.inboxId = claims.inboxId;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// non-fatal
|
|
153
|
+
}
|
|
154
|
+
return cap;
|
|
155
|
+
}
|
|
156
|
+
async ensureSession() {
|
|
157
|
+
if (this.sessionJWT &&
|
|
158
|
+
!isJwtExpired(this.sessionJWT, SESSION_SAFETY_MARGIN_MS)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!this.apiKey) {
|
|
162
|
+
throw new Error("No API key configured and no valid session on disk. Run register " +
|
|
163
|
+
"first, set ATOMIC_MAIL_API_KEY, or place credentials.json in the " +
|
|
164
|
+
"credential directory.");
|
|
165
|
+
}
|
|
166
|
+
const result = await performPoWAndSession({
|
|
167
|
+
authUrl: this.authUrl,
|
|
168
|
+
scryptSalt: this.scryptSalt,
|
|
169
|
+
apiKey: this.apiKey,
|
|
170
|
+
});
|
|
171
|
+
this.sessionJWT = result.sessionJWT;
|
|
172
|
+
this.capabilityJWT = undefined;
|
|
173
|
+
this.cachedMailAccountId = undefined;
|
|
174
|
+
await writeJwtFile(this.files.sessionFile, this.sessionJWT);
|
|
175
|
+
}
|
|
176
|
+
destroy() {
|
|
177
|
+
// reserved
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** PoW login with an existing API key; writes credentials + JWT files. */
|
|
181
|
+
export async function persistLoginWithApiKey(input) {
|
|
182
|
+
const authUrl = input.authUrl.replace(/\/+$/, "");
|
|
183
|
+
const apiUrl = input.apiUrl.replace(/\/+$/, "");
|
|
184
|
+
const session = await performPoWAndSession({
|
|
185
|
+
authUrl,
|
|
186
|
+
scryptSalt: input.scryptSalt,
|
|
187
|
+
apiKey: input.apiKey,
|
|
188
|
+
onPowProgress: input.onPowProgress,
|
|
189
|
+
});
|
|
190
|
+
const capabilityJWT = await fetchCapability(authUrl, session.sessionJWT);
|
|
191
|
+
const claims = decodeJwtPayload(capabilityJWT);
|
|
192
|
+
const inboxId = claims.inboxId;
|
|
193
|
+
if (typeof inboxId !== "string" || inboxId.length === 0) {
|
|
194
|
+
throw new Error("Capability JWT did not contain an inboxId claim.");
|
|
195
|
+
}
|
|
196
|
+
await writeCredentials(input.files.credentialsFile, {
|
|
197
|
+
apiKey: input.apiKey,
|
|
198
|
+
inboxId,
|
|
199
|
+
authUrl,
|
|
200
|
+
apiUrl,
|
|
201
|
+
scryptSalt: input.scryptSalt,
|
|
202
|
+
});
|
|
203
|
+
await writeJwtFile(input.files.sessionFile, session.sessionJWT);
|
|
204
|
+
await writeJwtFile(input.files.capabilityFile, capabilityJWT);
|
|
205
|
+
return { inboxId };
|
|
206
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../../../src/lib/core/consts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,qEAC4B,CAAC;AAErE,eAAO,MAAM,UAAU,OAAO,CAAC;AAC/B,eAAO,MAAM,UAAU,QAAkB,CAAC;AAC1C,eAAO,MAAM,WAAW,QAAkB,CAAC;AAC3C,eAAO,MAAM,UAAU,QAAmB,CAAC;AAC3C,eAAO,MAAM,YAAY,QAAkB,CAAC;AAC5C,eAAO,MAAM,WAAW,QAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MaybePromise } from "./types.js";
|
|
2
|
+
export declare function delay(ms: number): Promise<void>;
|
|
3
|
+
export type RetryCfg = {
|
|
4
|
+
maxTimeoutMs?: number;
|
|
5
|
+
startTimeoutMs?: number;
|
|
6
|
+
backoffMul?: number;
|
|
7
|
+
onBeforeRetry?: (e: unknown) => MaybePromise<void>;
|
|
8
|
+
};
|
|
9
|
+
export declare function retry<R>(fn: () => MaybePromise<R>, config: RetryCfg): Promise<R>;
|
|
10
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/core/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;CACpD,CAAC;AASF,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,CAAC,CAAC,CAgBZ"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ONE_SEC_MS } from "./consts.js";
|
|
2
|
+
export function delay(ms) {
|
|
3
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
const defaultCfg = {
|
|
6
|
+
maxTimeoutMs: ONE_SEC_MS * 32,
|
|
7
|
+
startTimeoutMs: ONE_SEC_MS,
|
|
8
|
+
backoffMul: 2,
|
|
9
|
+
};
|
|
10
|
+
// retry with exponential backoff, retries the fn on throw, re-throws on max backoff
|
|
11
|
+
export async function retry(fn, config) {
|
|
12
|
+
const cfg = { ...defaultCfg, ...config };
|
|
13
|
+
let curTimeoutMs = cfg.startTimeoutMs;
|
|
14
|
+
while (true) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fn();
|
|
17
|
+
return res;
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
if (cfg.onBeforeRetry)
|
|
21
|
+
await cfg.onBeforeRetry(e);
|
|
22
|
+
if (curTimeoutMs > cfg.maxTimeoutMs)
|
|
23
|
+
throw e;
|
|
24
|
+
await delay(curTimeoutMs);
|
|
25
|
+
curTimeoutMs = Math.floor(curTimeoutMs * cfg.backoffMul);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/esm/lib/mod.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./network/auth-client.js";
|
|
2
|
+
export * from "./core/utils.js";
|
|
3
|
+
export * from "./core/consts.js";
|
|
4
|
+
export * from "./core/types.js";
|
|
5
|
+
export * from "./agent/session/agent-credentials-store.js";
|
|
6
|
+
export * from "./agent/auth/agent-jwt.js";
|
|
7
|
+
export * from "./agent/auth/agent-pow.js";
|
|
8
|
+
export * from "./agent/auth/agent-auth-http.js";
|
|
9
|
+
export * from "./agent/jmap/agent-jmap.js";
|
|
10
|
+
export * from "./agent/session/agent-session.js";
|
|
11
|
+
export * from "./agent/session/agent-resolve-config.js";
|
|
12
|
+
export * from "./agent/jmap/agent-help-content.js";
|
|
13
|
+
export * from "./agent/jmap/agent-vars.js";
|
|
14
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/lib/mod.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,4CAA4C,CAAC;AAC3D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC"}
|
package/esm/lib/mod.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./network/auth-client.js";
|
|
2
|
+
export * from "./core/utils.js";
|
|
3
|
+
export * from "./core/consts.js";
|
|
4
|
+
export * from "./core/types.js";
|
|
5
|
+
export * from "./agent/session/agent-credentials-store.js";
|
|
6
|
+
export * from "./agent/auth/agent-jwt.js";
|
|
7
|
+
export * from "./agent/auth/agent-pow.js";
|
|
8
|
+
export * from "./agent/auth/agent-auth-http.js";
|
|
9
|
+
export * from "./agent/jmap/agent-jmap.js";
|
|
10
|
+
export * from "./agent/session/agent-session.js";
|
|
11
|
+
export * from "./agent/session/agent-resolve-config.js";
|
|
12
|
+
export * from "./agent/jmap/agent-help-content.js";
|
|
13
|
+
export * from "./agent/jmap/agent-vars.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface AuthClientOptions {
|
|
2
|
+
/** Base URL of auth-service, e.g. "http://localhost:8000". Trailing slashes are stripped. */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/**
|
|
5
|
+
* PoW scrypt salt (hex string). When omitted, {@link DEFAULT_POW_SCRYPT_SALT_HEX}
|
|
6
|
+
* is used so clients match the bundled auth-service.
|
|
7
|
+
*/
|
|
8
|
+
scryptSaltHex?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SignupResult {
|
|
11
|
+
/** Freshly minted API key. The server only returns it once — persist it. */
|
|
12
|
+
apiKey: string;
|
|
13
|
+
sessionJWT: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LoginResult {
|
|
16
|
+
sessionJWT: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RenewResult {
|
|
19
|
+
capabilityJWT: string;
|
|
20
|
+
}
|
|
21
|
+
/** Thrown for any non-2xx HTTP response or malformed payload. */
|
|
22
|
+
export declare class AuthClientError extends Error {
|
|
23
|
+
status: number;
|
|
24
|
+
bodyText: string;
|
|
25
|
+
constructor(status: number, bodyText: string, message: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class AuthClient {
|
|
28
|
+
private readonly baseUrl;
|
|
29
|
+
private readonly scryptSaltHex;
|
|
30
|
+
constructor(options: AuthClientOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Register a new inbox under `username`. Returns the freshly minted API key
|
|
33
|
+
* (the server only ever returns it once — the caller MUST persist it) and
|
|
34
|
+
* a session JWT.
|
|
35
|
+
*/
|
|
36
|
+
signup(username: string): Promise<SignupResult>;
|
|
37
|
+
/** Exchange an existing API key for a fresh session JWT. */
|
|
38
|
+
login(apiKey: string): Promise<LoginResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Exchange a session JWT for a short-lived capability JWT (audience:
|
|
41
|
+
* api-service).
|
|
42
|
+
*/
|
|
43
|
+
renew(sessionJWT: string): Promise<RenewResult>;
|
|
44
|
+
private fetchChallenge;
|
|
45
|
+
private postSession;
|
|
46
|
+
private parseJsonOrThrow;
|
|
47
|
+
/**
|
|
48
|
+
* Brute-force a PoW nonce. Mirrors `generatePow` in
|
|
49
|
+
* services/auth-service/src/crypto.ts: scrypt(`${challenge}:${nonce}`, salt,
|
|
50
|
+
* 64) until `difficulty` leading bits of the digest are zero.
|
|
51
|
+
*
|
|
52
|
+
* Expected work at the server's POW_DIFFICULTY=6 is ~2^6 = 64 attempts; well
|
|
53
|
+
* within the challenge JWT's 3-minute TTL.
|
|
54
|
+
*/
|
|
55
|
+
private solvePoW;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=auth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-client.d.ts","sourceRoot":"","sources":["../../../src/lib/network/auth-client.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAChC,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,iEAAiE;AACjE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;gBAEL,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAM9D;AAOD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,OAAO,EAAE,iBAAiB;IAKtC;;;;OAIG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAwBrD,4DAA4D;IACtD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBjD;;;OAGG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAgBvC,cAAc;YAkCd,WAAW;YAeX,gBAAgB;IAuB9B;;;;;;;OAOG;YACW,QAAQ;CAgBvB"}
|