@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.
Files changed (80) hide show
  1. package/README.md +77 -187
  2. package/esm/_dnt.polyfills.d.ts +101 -0
  3. package/esm/_dnt.polyfills.d.ts.map +1 -0
  4. package/esm/_dnt.polyfills.js +127 -0
  5. package/esm/lib/agent/auth/agent-auth-http.d.ts +26 -0
  6. package/esm/lib/agent/auth/agent-auth-http.d.ts.map +1 -0
  7. package/esm/lib/agent/auth/agent-auth-http.js +76 -0
  8. package/esm/lib/agent/auth/agent-jwt.d.ts +14 -0
  9. package/esm/lib/agent/auth/agent-jwt.d.ts.map +1 -0
  10. package/esm/lib/agent/auth/agent-jwt.js +29 -0
  11. package/esm/lib/agent/auth/agent-pow.d.ts +5 -0
  12. package/esm/lib/agent/auth/agent-pow.d.ts.map +1 -0
  13. package/esm/lib/agent/auth/agent-pow.js +49 -0
  14. package/esm/lib/agent/jmap/agent-help-content.d.ts +4 -0
  15. package/esm/lib/agent/jmap/agent-help-content.d.ts.map +1 -0
  16. package/esm/lib/agent/jmap/agent-help-content.js +244 -0
  17. package/esm/lib/agent/jmap/agent-jmap.d.ts +49 -0
  18. package/esm/lib/agent/jmap/agent-jmap.d.ts.map +1 -0
  19. package/esm/lib/agent/jmap/agent-jmap.js +174 -0
  20. package/esm/lib/agent/jmap/agent-vars.d.ts +23 -0
  21. package/esm/lib/agent/jmap/agent-vars.d.ts.map +1 -0
  22. package/esm/lib/agent/jmap/agent-vars.js +65 -0
  23. package/esm/{mcp/src/credentials.d.ts → lib/agent/session/agent-credentials-store.d.ts} +3 -2
  24. package/esm/lib/agent/session/agent-credentials-store.d.ts.map +1 -0
  25. package/esm/{mcp/src/credentials.js → lib/agent/session/agent-credentials-store.js} +19 -16
  26. package/esm/lib/agent/session/agent-resolve-config.d.ts +24 -0
  27. package/esm/lib/agent/session/agent-resolve-config.d.ts.map +1 -0
  28. package/esm/lib/agent/session/agent-resolve-config.js +70 -0
  29. package/esm/lib/agent/session/agent-session.d.ts +62 -0
  30. package/esm/lib/agent/session/agent-session.d.ts.map +1 -0
  31. package/esm/lib/agent/session/agent-session.js +206 -0
  32. package/esm/lib/core/consts.d.ts.map +1 -0
  33. package/esm/lib/core/types.d.ts +2 -0
  34. package/esm/lib/core/types.d.ts.map +1 -0
  35. package/esm/lib/core/types.js +1 -0
  36. package/esm/lib/core/utils.d.ts +10 -0
  37. package/esm/lib/core/utils.d.ts.map +1 -0
  38. package/esm/lib/core/utils.js +28 -0
  39. package/esm/lib/mod.d.ts +14 -0
  40. package/esm/lib/mod.d.ts.map +1 -0
  41. package/esm/lib/mod.js +13 -0
  42. package/esm/lib/network/auth-client.d.ts +57 -0
  43. package/esm/lib/network/auth-client.d.ts.map +1 -0
  44. package/esm/lib/network/auth-client.js +188 -0
  45. package/esm/mcp/main.d.ts +3 -0
  46. package/esm/mcp/main.d.ts.map +1 -0
  47. package/esm/mcp/main.js +86 -0
  48. package/esm/mcp/tools/help.d.ts +3 -0
  49. package/esm/mcp/tools/help.d.ts.map +1 -0
  50. package/esm/mcp/tools/help.js +22 -0
  51. package/esm/mcp/{src/tools → tools}/jmap.d.ts +2 -2
  52. package/esm/mcp/tools/jmap.d.ts.map +1 -0
  53. package/esm/mcp/tools/jmap.js +115 -0
  54. package/esm/mcp/{src/tools → tools}/register.d.ts +2 -2
  55. package/esm/mcp/tools/register.d.ts.map +1 -0
  56. package/esm/mcp/tools/register.js +43 -0
  57. package/package.json +5 -5
  58. package/presets/list_inbox.json +39 -0
  59. package/presets/reply.json +75 -0
  60. package/presets/send_mail.json +42 -0
  61. package/esm/lib/src/consts.d.ts.map +0 -1
  62. package/esm/mcp/src/auth-session.d.ts +0 -88
  63. package/esm/mcp/src/auth-session.d.ts.map +0 -1
  64. package/esm/mcp/src/auth-session.js +0 -378
  65. package/esm/mcp/src/credentials.d.ts.map +0 -1
  66. package/esm/mcp/src/docs-content.d.ts +0 -4
  67. package/esm/mcp/src/docs-content.d.ts.map +0 -1
  68. package/esm/mcp/src/docs-content.js +0 -405
  69. package/esm/mcp/src/main.d.ts +0 -3
  70. package/esm/mcp/src/main.d.ts.map +0 -1
  71. package/esm/mcp/src/main.js +0 -116
  72. package/esm/mcp/src/tools/docs.d.ts +0 -3
  73. package/esm/mcp/src/tools/docs.d.ts.map +0 -1
  74. package/esm/mcp/src/tools/docs.js +0 -22
  75. package/esm/mcp/src/tools/jmap.d.ts.map +0 -1
  76. package/esm/mcp/src/tools/jmap.js +0 -202
  77. package/esm/mcp/src/tools/register.d.ts.map +0 -1
  78. package/esm/mcp/src/tools/register.js +0 -79
  79. /package/esm/lib/{src → core}/consts.d.ts +0 -0
  80. /package/esm/lib/{src → core}/consts.js +0 -0
@@ -0,0 +1,29 @@
1
+ // JWT helpers for capability/session expiry checks.
2
+ export const SESSION_TTL_MS = 4 * 60 * 60 * 1000;
3
+ export const CAPABILITY_TTL_MS = 2 * 60 * 1000;
4
+ export const SESSION_SAFETY_MARGIN_MS = 60_000;
5
+ export const CAPABILITY_SAFETY_MARGIN_MS = 20_000;
6
+ export function decodeJwtPayload(jwt) {
7
+ const parts = jwt.split(".");
8
+ if (parts.length < 2) {
9
+ throw new Error("Malformed JWT: expected at least 2 dot-separated segments.");
10
+ }
11
+ const payloadB64Url = parts[1];
12
+ const padLen = (4 - (payloadB64Url.length % 4)) % 4;
13
+ const base64 = payloadB64Url
14
+ .replace(/-/g, "+")
15
+ .replace(/_/g, "/")
16
+ .padEnd(payloadB64Url.length + padLen, "=");
17
+ return JSON.parse(atob(base64));
18
+ }
19
+ export function isJwtExpired(jwt, marginMs) {
20
+ try {
21
+ const { exp } = decodeJwtPayload(jwt);
22
+ if (typeof exp !== "number")
23
+ return true;
24
+ return Date.now() >= exp * 1000 - marginMs;
25
+ }
26
+ catch {
27
+ return true;
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ export declare function solvePow(challenge: string, difficulty: number, salt: string, onProgress?: (nonce: bigint) => void): Promise<{
2
+ powHex: string;
3
+ nonce: string;
4
+ }>;
5
+ //# sourceMappingURL=agent-pow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-pow.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/auth/agent-pow.ts"],"names":[],"mappings":"AAuCA,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAU5C"}
@@ -0,0 +1,49 @@
1
+ // PoW scrypt — mirrors services/auth-service/src/crypto.ts.
2
+ import { scrypt } from "node:crypto";
3
+ const SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
4
+ const POW_HASH_BYTES = 64;
5
+ function bytesToHex(bytes) {
6
+ let hex = "";
7
+ for (let i = 0; i < bytes.length; i++) {
8
+ hex += bytes[i].toString(16).padStart(2, "0");
9
+ }
10
+ return hex;
11
+ }
12
+ function hasLeadingZeroBits(hash, bits) {
13
+ if (bits > hash.length * 8)
14
+ return false;
15
+ const fullBytes = Math.floor(bits / 8);
16
+ const remainingBits = bits % 8;
17
+ for (let i = 0; i < fullBytes; i++) {
18
+ if (hash[i] !== 0)
19
+ return false;
20
+ }
21
+ if (remainingBits > 0) {
22
+ const mask = (0xff << (8 - remainingBits)) & 0xff;
23
+ if ((hash[fullBytes] & mask) !== 0)
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ function scryptHash(data, salt) {
29
+ const bytes = new TextEncoder().encode(data);
30
+ return new Promise((resolve, reject) => {
31
+ scrypt(bytes, salt, POW_HASH_BYTES, SCRYPT_PARAMS, (err, derived) => {
32
+ if (err)
33
+ return reject(err);
34
+ resolve(new Uint8Array(derived));
35
+ });
36
+ });
37
+ }
38
+ export async function solvePow(challenge, difficulty, salt, onProgress) {
39
+ let nonce = 0n;
40
+ while (true) {
41
+ const digest = await scryptHash(`${challenge}:${nonce}`, salt);
42
+ if (hasLeadingZeroBits(digest, difficulty)) {
43
+ return { powHex: bytesToHex(digest), nonce: nonce.toString() };
44
+ }
45
+ nonce++;
46
+ if (onProgress && nonce % 64n === 0n)
47
+ onProgress(nonce);
48
+ }
49
+ }
@@ -0,0 +1,4 @@
1
+ export declare const HELP_TOPICS: Record<string, string>;
2
+ export declare const HELP_TOPIC_LIST: string[];
3
+ export declare function getHelp(topic?: string): string;
4
+ //# sourceMappingURL=agent-help-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-help-content.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/jmap/agent-help-content.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA+O9C,CAAC;AAEF,eAAO,MAAM,eAAe,UAA2B,CAAC;AAExD,wBAAgB,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAW9C"}
@@ -0,0 +1,244 @@
1
+ // Long-form help for MCP `help` tool and AgentSkill `help` command.
2
+ export const HELP_TOPICS = {
3
+ overview: `\
4
+ # Atomic Mail — Overview
5
+
6
+ Atomic Mail is an email service provider (ESP) designed for AI agents. You
7
+ manage mail over JMAP (RFC 8620 + RFC 8621).
8
+
9
+ ## Public surface (identical for MCP and AgentSkill)
10
+
11
+ Three operations only:
12
+
13
+ 1. **register** — Proof-of-work signup (or idempotent replay when the same
14
+ username matches the inbox already on disk). Persists credentials and
15
+ returns \`{ inbox, accountId }\` (and \`apiKey\` on first signup).
16
+ 2. **jmap_request** — Send a JMAP method-call batch. Auth and JWT rotation are
17
+ automatic. Pass inline \`ops\` JSON or an \`ops_file\` preset path (same
18
+ substitution applies to both). Uppercase tokens like \`$ACCOUNT_ID\`,
19
+ \`$INBOX\`, \`$TO\`, \`$SUBJECT\` are replaced before the request is sent.
20
+ \`$ACCOUNT_ID\` / \`$INBOX\` come from the JMAP session and credentials; pass
21
+ any other names via MCP \`vars\` or skill \`--vars\`.
22
+ 3. **help** — This documentation (optional \`topic\` / \`--topic\`).
23
+
24
+ ## Typical workflow
25
+
26
+ 1. \`register\` with a username.
27
+ 2. \`jmap_request\` with JMAP method calls (presets may use \`$VAR_NAME\`; pass
28
+ custom values in \`vars\` / \`--vars\`).
29
+ 3. If stuck, read error hints and call \`help\`.
30
+
31
+ Available topics: overview, installation, auth, jmap_cheatsheet, tools,
32
+ presets, troubleshooting.`,
33
+ installation: `\
34
+ # Atomic Mail — Installation
35
+
36
+ ## MCP (stdio)
37
+
38
+ \`\`\`json
39
+ {
40
+ "mcpServers": {
41
+ "atomicmail": {
42
+ "command": "npx",
43
+ "args": ["-y", "@atomicmail/mcp"]
44
+ }
45
+ }
46
+ }
47
+ \`\`\`
48
+
49
+ ## AgentSkill (shell)
50
+
51
+ \`\`\`bash
52
+ npx --package=@atomicmail/agent-skill atomicmail register --username "myagent"
53
+ npx --package=@atomicmail/agent-skill atomicmail jmap_request \\
54
+ --ops-file list_inbox.json \\
55
+ --vars '{"COUNT":"10"}'
56
+ npx --package=@atomicmail/agent-skill atomicmail help
57
+ \`\`\`
58
+
59
+ From the repo: \`deno run -A scripts/cli.ts <command> ...\` inside \`skill/\`.
60
+
61
+ ## Shared credentials
62
+
63
+ MCP and the skill use the same directory layout (default \`~/.atomicmail/\`):
64
+
65
+ - \`credentials.json\`, \`session.jwt\`, \`capability.jwt\`
66
+
67
+ ## Overriding defaults
68
+
69
+ - Endpoints: \`ATOMIC_MAIL_AUTH_URL\`, \`ATOMIC_MAIL_API_URL\`
70
+ - Credentials path: \`ATOMIC_MAIL_CREDENTIALS_DIR\` (MCP), \`--credentials-dir\` (skill)
71
+ - Optional PoW salt: \`ATOMIC_MAIL_SCRYPT_SALT\`
72
+
73
+ ## From source (development)
74
+
75
+ From the repo \`mcp/\` or \`skill/\` directory:
76
+
77
+ \`\`\`bash
78
+ deno task start # MCP
79
+ deno task build:npm
80
+ \`\`\`
81
+
82
+ See each package README for details.`,
83
+ auth: `\
84
+ # Atomic Mail — Auth flow
85
+
86
+ Auth is automatic after \`register\` (or when \`credentials.json\` + API key
87
+ exist).
88
+
89
+ 1. **Challenge** — \`POST /api/v1/challenge\`
90
+ 2. **Proof-of-work** — scrypt until difficulty satisfied
91
+ 3. **Session JWT** — \`POST /api/v1/session\` (4h TTL); signup returns a
92
+ one-time \`apiKey\`
93
+ 4. **Capability JWT** — \`POST /api/v1/capability\` (2 min TTL) used as the
94
+ JMAP bearer
95
+
96
+ JWTs are rotated before expiry and written back to disk.
97
+
98
+ ## Credential files (mode 0600)
99
+
100
+ \`credentials.json\` — \`{ apiKey, inboxId, authUrl, apiUrl, scryptSalt }\`
101
+ \`session.jwt\` — session token
102
+ \`capability.jwt\` — capability token
103
+
104
+ ## Overriding defaults
105
+
106
+ - \`ATOMIC_MAIL_AUTH_URL\` (default: \`https://auth.atomicmail.ai\`)
107
+ - \`ATOMIC_MAIL_API_URL\` (default: \`https://api.atomicmail.ai\`)
108
+ - \`ATOMIC_MAIL_SCRYPT_SALT\` (optional)
109
+ - \`ATOMIC_MAIL_API_KEY\` (optional)
110
+ - \`ATOMIC_MAIL_CREDENTIALS_DIR\` (default: \`~/.atomicmail\`)`,
111
+ jmap_cheatsheet: `\
112
+ # JMAP cheatsheet
113
+
114
+ ## Capabilities (\`using\`)
115
+
116
+ - urn:ietf:params:jmap:core
117
+ - urn:ietf:params:jmap:mail
118
+ - urn:ietf:params:jmap:submission
119
+
120
+ ## Examples
121
+
122
+ Use \`$ACCOUNT_ID\` and \`$INBOX\` for session fields; use \`$TO\`, \`$SUBJECT\`,
123
+ etc., and supply values via MCP \`vars\` or \`--vars\` (JSON object of strings).
124
+
125
+ ### Mailboxes
126
+ \`\`\`json
127
+ ["Mailbox/get", {"accountId": "$ACCOUNT_ID"}, "m0"]
128
+ \`\`\`
129
+
130
+ ### Query + fetch emails
131
+ \`\`\`json
132
+ ["Email/query", {
133
+ "accountId": "$ACCOUNT_ID",
134
+ "filter": {"inMailbox": "$INBOX"},
135
+ "sort": [{"property": "receivedAt", "isAscending": false}],
136
+ "limit": 100
137
+ }, "q0"]
138
+ \`\`\`
139
+
140
+ ### Send (add submission to \`using\`)
141
+
142
+ Include \`urn:ietf:params:jmap:submission\` in the envelope \`using\` array
143
+ when calling \`EmailSubmission/set\`.
144
+
145
+ ## Tips
146
+
147
+ - Back-references (\`#ids\`, \`#draft\`) chain calls in one batch.
148
+ - Save reusable JSON as preset files and pass \`ops_file\`.`,
149
+ tools: `\
150
+ # Tool / CLI reference
151
+
152
+ ## register
153
+
154
+ **MCP input:** \`{ "username": string }\`
155
+ **Skill:** \`register --username NAME\` (or \`--api-key KEY\`).
156
+
157
+ Creates an inbox or returns the same \`{ inbox, accountId }\` when the
158
+ username matches the stored inbox local-part. A **different** username
159
+ replaces credentials in the directory and registers a new inbox.
160
+
161
+ ## jmap_request
162
+
163
+ **MCP input:** \`{ "using"?: string[], "ops"?: string, "ops_file"?: string,
164
+ "vars"?: Record<string, string> }\` — keys in \`vars\` are names without \`$\`
165
+ (e.g. \`TO\` for \`$TO\`). Exactly one of \`ops\` or \`ops_file\`.
166
+
167
+ **Skill:** \`jmap_request --ops '...'\` or \`--ops-file path\` plus
168
+ \`--credentials-dir\` (optional), plus optional \`--vars '<json>'\`, \`--using\`,
169
+ \`--dry-run\`.
170
+
171
+ ## help
172
+
173
+ **MCP:** \`{ "topic"?: string }\`
174
+ **Skill:** \`help [--topic TOPIC]\`
175
+
176
+ Topics: overview, installation, auth, jmap_cheatsheet, tools, presets,
177
+ troubleshooting.`,
178
+ presets: `\
179
+ # JMAP presets
180
+
181
+ Save a method-call array or a full \`{ "using", "methodCalls" }\` envelope
182
+ as JSON, then pass \`ops_file\` (MCP) or \`--ops-file\` (skill).
183
+
184
+ Relative paths first resolve against the credential directory (MCP) or current
185
+ \`--credentials-dir\` (skill). If not found, the runtime falls back to bundled
186
+ presets that ship in both npm packages.
187
+
188
+ ## Bundled presets
189
+
190
+ - \`send_mail.json\` — sends one email using \`$TO\`, \`$SUBJECT\`, \`$BODY\`.
191
+ - \`list_inbox.json\` — returns the latest \`$COUNT\` inbox messages.
192
+ - \`reply.json\` — replies in-thread using \`$MAIL_ID\` and \`$BODY\`.
193
+
194
+ ## Placeholders
195
+
196
+ Syntax: \`$VAR_NAME\` where \`VAR_NAME\` matches \`/^[A-Z][A-Z0-9_]*$/\` (so JMAP
197
+ keywords like \`$draft\` stay untouched).
198
+
199
+ - \`$ACCOUNT_ID\` — primary mail account id (from \`GET /.well-known/jmap\` when
200
+ referenced).
201
+ - \`$INBOX\` — inbox email address from credentials.
202
+ - Any other \`$FOO\` — must appear in MCP \`vars\` or skill \`--vars\` as
203
+ \`"FOO": "..."\` (string values only; JSON escaping in the preset body is your
204
+ responsibility).
205
+
206
+ You may override \`ACCOUNT_ID\` / \`INBOX\` via \`vars\` / \`--vars\` if needed.`,
207
+ troubleshooting: `\
208
+ # Troubleshooting
209
+
210
+ ## Custom endpoint configuration issues
211
+
212
+ Defaults are production endpoints. Set env vars only for custom deployments.
213
+
214
+ ## No API key / register first
215
+
216
+ Run \`register\`, or set \`ATOMIC_MAIL_API_KEY\`, or copy an existing
217
+ \`credentials.json\` into the credential directory.
218
+
219
+ ## auth-service /api/v1/session returned 401
220
+
221
+ Invalid \`apiKey\` or wrong \`ATOMIC_MAIL_SCRYPT_SALT\` for this deployment.
222
+
223
+ ## Capability JWT missing inboxId
224
+
225
+ Server/version mismatch — verify \`ATOMIC_MAIL_AUTH_URL\`.
226
+
227
+ ## Could not read ops file
228
+
229
+ Check the path; use an absolute path if unsure.
230
+
231
+ ## Missing values for variables (\`$TO\`, etc.)
232
+
233
+ Pass every custom placeholder in MCP \`vars\` or \`--vars\` as a JSON object of
234
+ strings. Ensure \`register\` completed so \`$ACCOUNT_ID\` / \`$INBOX\` can resolve.`,
235
+ };
236
+ export const HELP_TOPIC_LIST = Object.keys(HELP_TOPICS);
237
+ export function getHelp(topic) {
238
+ if (!topic) {
239
+ return HELP_TOPICS["overview"];
240
+ }
241
+ const key = topic.toLowerCase().replace(/[\s-]/g, "_");
242
+ return (HELP_TOPICS[key] ??
243
+ `Unknown topic "${topic}". Available topics: ${HELP_TOPIC_LIST.join(", ")}`);
244
+ }
@@ -0,0 +1,49 @@
1
+ export declare const DEFAULT_JMAP_USING: readonly ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
2
+ export declare const JMAP_MAIL_URN: "urn:ietf:params:jmap:mail";
3
+ export interface JmapEnvelope {
4
+ using: string[];
5
+ methodCalls: unknown[];
6
+ }
7
+ export declare function parseJmapEnvelope(raw: string, defaultUsing: string[], source: string): JmapEnvelope;
8
+ export declare function resolveOpsFilePath(credentialDir: string, opsFile: string): string;
9
+ export declare function readOpsFile(credentialDir: string, opsFile: string): Promise<string>;
10
+ export declare function extractPrimaryMailAccountId(session: Record<string, unknown>): string;
11
+ export declare function fetchJmapWellKnown(apiUrl: string, capabilityJwt: string): Promise<Record<string, unknown>>;
12
+ /** Minimal surface for JMAP execution (implemented by AgentSession). */
13
+ export interface JmapSessionPort {
14
+ readonly apiUrl: string;
15
+ readonly files: {
16
+ credentialsFile: string;
17
+ };
18
+ getPrimaryMailAccountId(): Promise<string>;
19
+ getCapabilityToken(): Promise<string>;
20
+ readonly currentInboxId?: string;
21
+ }
22
+ export interface RunJmapRequestInput {
23
+ session: JmapSessionPort;
24
+ /** Raw JSON: methodCalls array or full envelope */
25
+ opsJson: string;
26
+ /** Default `using` when the envelope omits it */
27
+ defaultUsing: string[];
28
+ /** Label for parse errors */
29
+ sourceLabel: string;
30
+ dryRun?: boolean;
31
+ /** Values for `$VAR` tokens (keys without `$`). Overrides session vars when present. */
32
+ vars?: Record<string, string>;
33
+ }
34
+ /**
35
+ * Parse ops JSON, substitute `$VAR_NAME` tokens (session + caller vars), POST to JMAP.
36
+ */
37
+ export declare function runJmapRequest(input: RunJmapRequestInput): Promise<{
38
+ ok: boolean;
39
+ status: number;
40
+ bodyText: string;
41
+ }>;
42
+ export declare function postJmap(apiUrl: string, capabilityJwt: string, envelope: JmapEnvelope): Promise<{
43
+ ok: boolean;
44
+ status: number;
45
+ bodyText: string;
46
+ }>;
47
+ /** Attach _next hints to a successful JMAP JSON object when parseable. */
48
+ export declare function attachJmapNextHints(bodyText: string): string;
49
+ //# sourceMappingURL=agent-jmap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-jmap.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/jmap/agent-jmap.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,kBAAkB,qEAGrB,CAAC;AAEX,eAAO,MAAM,aAAa,EAAG,2BAAoC,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,OAAO,EAAE,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EAAE,EACtB,MAAM,EAAE,MAAM,GACb,YAAY,CAyBd;AAED,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,MAAM,CAER;AAED,wBAAsB,WAAW,CAC/B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAiBjB;AA8CD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAcR;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAclC;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE;QAAE,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,eAAe,CAAC;IACzB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA4C5D;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAY5D;AAQD,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU5D"}
@@ -0,0 +1,174 @@
1
+ // JMAP envelope parsing, preset paths, $VAR substitution, and HTTP helpers.
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { readCredentials } from "../session/agent-credentials-store.js";
6
+ import { substituteVars } from "./agent-vars.js";
7
+ export const DEFAULT_JMAP_USING = [
8
+ "urn:ietf:params:jmap:core",
9
+ "urn:ietf:params:jmap:mail",
10
+ ];
11
+ export const JMAP_MAIL_URN = "urn:ietf:params:jmap:mail";
12
+ export function parseJmapEnvelope(raw, defaultUsing, source) {
13
+ let value;
14
+ try {
15
+ value = JSON.parse(raw);
16
+ }
17
+ catch (err) {
18
+ throw new Error(`${source} is not valid JSON: ${err.message}`);
19
+ }
20
+ if (Array.isArray(value)) {
21
+ return { using: [...defaultUsing], methodCalls: value };
22
+ }
23
+ if (value !== null &&
24
+ typeof value === "object" &&
25
+ Array.isArray(value.methodCalls)) {
26
+ const obj = value;
27
+ const using = Array.isArray(obj.using)
28
+ ? obj.using.filter((u) => typeof u === "string")
29
+ : [...defaultUsing];
30
+ return { using, methodCalls: obj.methodCalls };
31
+ }
32
+ throw new Error(`${source} must be a methodCalls array, e.g. ` +
33
+ '[["Mailbox/get",{...},"m0"]], or an object with a methodCalls array.');
34
+ }
35
+ export function resolveOpsFilePath(credentialDir, opsFile) {
36
+ return isAbsolute(opsFile) ? opsFile : resolvePath(credentialDir, opsFile);
37
+ }
38
+ export async function readOpsFile(credentialDir, opsFile) {
39
+ const filePath = resolveOpsFilePath(credentialDir, opsFile);
40
+ try {
41
+ return await readFile(filePath, "utf-8");
42
+ }
43
+ catch (err) {
44
+ if (!(err instanceof Error) || !isFileNotFound(err) || isAbsolute(opsFile)) {
45
+ throw err;
46
+ }
47
+ }
48
+ const bundledPath = await resolveBundledPresetPath(opsFile);
49
+ if (!bundledPath) {
50
+ return await readFile(filePath, "utf-8");
51
+ }
52
+ return await readFile(bundledPath, "utf-8");
53
+ }
54
+ function isFileNotFound(err) {
55
+ const code = err.code;
56
+ return code === "ENOENT" || code === "ENOTDIR";
57
+ }
58
+ async function resolveBundledPresetPath(opsFile) {
59
+ const moduleDir = dirname(fileURLToPath(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
60
+ let currentDir = moduleDir;
61
+ for (let depth = 0; depth < 8; depth++) {
62
+ const candidates = [
63
+ resolvePath(currentDir, "presets", opsFile),
64
+ resolvePath(currentDir, "agent", "jmap", "presets", opsFile),
65
+ resolvePath(currentDir, "lib", "src", "agent", "jmap", "presets", opsFile),
66
+ ];
67
+ for (const candidate of candidates) {
68
+ try {
69
+ await readFile(candidate, "utf-8");
70
+ return candidate;
71
+ }
72
+ catch (err) {
73
+ if (!(err instanceof Error) || !isFileNotFound(err)) {
74
+ throw err;
75
+ }
76
+ }
77
+ }
78
+ const parent = resolvePath(currentDir, "..");
79
+ if (parent === currentDir)
80
+ break;
81
+ currentDir = parent;
82
+ }
83
+ return undefined;
84
+ }
85
+ export function extractPrimaryMailAccountId(session) {
86
+ const primary = session["primaryAccounts"];
87
+ if (!primary || typeof primary !== "object") {
88
+ throw new Error("JMAP session missing primaryAccounts.");
89
+ }
90
+ const id = primary[JMAP_MAIL_URN];
91
+ if (typeof id !== "string" || id.length === 0) {
92
+ throw new Error(`JMAP session missing primaryAccounts['${JMAP_MAIL_URN}'].`);
93
+ }
94
+ return id;
95
+ }
96
+ export async function fetchJmapWellKnown(apiUrl, capabilityJwt) {
97
+ const base = apiUrl.replace(/\/+$/, "");
98
+ const res = await fetch(`${base}/.well-known/jmap`, {
99
+ headers: { Authorization: `Bearer ${capabilityJwt}` },
100
+ });
101
+ const text = await res.text();
102
+ if (!res.ok) {
103
+ throw new Error(`JMAP session fetch failed (HTTP ${res.status}): ${text}`);
104
+ }
105
+ try {
106
+ return JSON.parse(text);
107
+ }
108
+ catch {
109
+ throw new Error("JMAP session response is not valid JSON.");
110
+ }
111
+ }
112
+ /**
113
+ * Parse ops JSON, substitute `$VAR_NAME` tokens (session + caller vars), POST to JMAP.
114
+ */
115
+ export async function runJmapRequest(input) {
116
+ const { text: raw } = await substituteVars({
117
+ raw: input.opsJson,
118
+ vars: input.vars,
119
+ autoResolvers: {
120
+ ACCOUNT_ID: () => input.session.getPrimaryMailAccountId(),
121
+ INBOX: async () => input.session.currentInboxId ??
122
+ (await readCredentials(input.session.files.credentialsFile)).inboxId,
123
+ },
124
+ });
125
+ const envelope = parseJmapEnvelope(raw, input.defaultUsing, input.sourceLabel);
126
+ if (input.dryRun) {
127
+ return {
128
+ ok: true,
129
+ status: 200,
130
+ bodyText: JSON.stringify({
131
+ dryRun: true,
132
+ url: `${input.session.apiUrl.replace(/\/+$/, "")}/jmap/`,
133
+ envelope,
134
+ }, null, 2),
135
+ };
136
+ }
137
+ const capabilityJwt = await input.session.getCapabilityToken();
138
+ const { ok, status, bodyText } = await postJmap(input.session.apiUrl, capabilityJwt, envelope);
139
+ if (!ok) {
140
+ return { ok, status, bodyText };
141
+ }
142
+ return { ok, status, bodyText: attachJmapNextHints(bodyText) };
143
+ }
144
+ export async function postJmap(apiUrl, capabilityJwt, envelope) {
145
+ const base = apiUrl.replace(/\/+$/, "");
146
+ const res = await fetch(`${base}/jmap/`, {
147
+ method: "POST",
148
+ headers: {
149
+ "Content-Type": "application/json",
150
+ Authorization: `Bearer ${capabilityJwt}`,
151
+ },
152
+ body: JSON.stringify(envelope),
153
+ });
154
+ const bodyText = await res.text();
155
+ return { ok: res.ok, status: res.status, bodyText };
156
+ }
157
+ const JMAP_NEXT_HINTS = [
158
+ "Use jmap_request with Mailbox/get or Email/query to work with mail data.",
159
+ "Use presets with $VAR placeholders — $ACCOUNT_ID and $INBOX come from the session; pass others via vars / --vars.",
160
+ "Call help for the JMAP cheatsheet and troubleshooting.",
161
+ ];
162
+ /** Attach _next hints to a successful JMAP JSON object when parseable. */
163
+ export function attachJmapNextHints(bodyText) {
164
+ try {
165
+ const obj = JSON.parse(bodyText);
166
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
167
+ return JSON.stringify({ ...obj, _next: [...JMAP_NEXT_HINTS] }, null, 2);
168
+ }
169
+ }
170
+ catch {
171
+ // not JSON — return raw
172
+ }
173
+ return bodyText;
174
+ }
@@ -0,0 +1,23 @@
1
+ /** Matches `$FOO_BAR`; excludes JMAP keywords like `$draft` (lowercase). */
2
+ export declare const VAR_PATTERN: RegExp;
3
+ /** Names substituted from JMAP session / credentials when not overridden in `vars`. */
4
+ export declare const SESSION_VAR_NAMES: Set<string>;
5
+ export interface SubstituteVarsInput {
6
+ raw: string;
7
+ /** Caller-supplied values; keys are names without `$` (e.g. `TO`, `SUBJECT`). */
8
+ vars?: Record<string, string>;
9
+ /** Invoked only when the name appears in `raw`, is absent from `vars`, and a resolver exists. */
10
+ autoResolvers?: Record<string, () => Promise<string> | string>;
11
+ }
12
+ export interface SubstituteVarsResult {
13
+ text: string;
14
+ }
15
+ /** Unique variable names in order of first occurrence (without leading `$`). */
16
+ export declare function findVarReferences(raw: string): string[];
17
+ /**
18
+ * Replaces every `$VAR_NAME` in `raw` with the corresponding string.
19
+ * Single pass — values are not scanned for further `$` tokens.
20
+ * Throws if any referenced variable has no value (after vars + autoResolvers).
21
+ */
22
+ export declare function substituteVars(input: SubstituteVarsInput): Promise<SubstituteVarsResult>;
23
+ //# sourceMappingURL=agent-vars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-vars.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/jmap/agent-vars.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,eAAO,MAAM,WAAW,QAAyB,CAAC;AAMlD,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,aAA2C,CAAC;AAE1E,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAWvD;AAeD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CA+B/B"}
@@ -0,0 +1,65 @@
1
+ // Variable substitution for JMAP presets / inline ops ($VAR_NAME tokens).
2
+ /** Matches `$FOO_BAR`; excludes JMAP keywords like `$draft` (lowercase). */
3
+ export const VAR_PATTERN = /\$([A-Z][A-Z0-9_]*)/g;
4
+ function varPattern() {
5
+ return new RegExp(VAR_PATTERN.source, VAR_PATTERN.flags);
6
+ }
7
+ /** Names substituted from JMAP session / credentials when not overridden in `vars`. */
8
+ export const SESSION_VAR_NAMES = new Set(["ACCOUNT_ID", "INBOX"]);
9
+ /** Unique variable names in order of first occurrence (without leading `$`). */
10
+ export function findVarReferences(raw) {
11
+ const seen = new Set();
12
+ const order = [];
13
+ for (const m of raw.matchAll(varPattern())) {
14
+ const name = m[1];
15
+ if (!seen.has(name)) {
16
+ seen.add(name);
17
+ order.push(name);
18
+ }
19
+ }
20
+ return order;
21
+ }
22
+ function formatMissingError(missing) {
23
+ const tokens = missing.map((n) => `$${n}`);
24
+ const hasSession = missing.some((n) => SESSION_VAR_NAMES.has(n));
25
+ let msg = `Missing values for variables: ${tokens.join(", ")}. ` +
26
+ "Pass custom placeholders in vars (MCP) or --vars (skill).";
27
+ if (hasSession) {
28
+ msg +=
29
+ " For $ACCOUNT_ID and $INBOX, ensure register completed and credentials are valid, " +
30
+ "or pass overrides in vars.";
31
+ }
32
+ return new Error(msg);
33
+ }
34
+ /**
35
+ * Replaces every `$VAR_NAME` in `raw` with the corresponding string.
36
+ * Single pass — values are not scanned for further `$` tokens.
37
+ * Throws if any referenced variable has no value (after vars + autoResolvers).
38
+ */
39
+ export async function substituteVars(input) {
40
+ const names = findVarReferences(input.raw);
41
+ if (names.length === 0) {
42
+ return { text: input.raw };
43
+ }
44
+ const userVars = input.vars ?? {};
45
+ const resolved = new Map();
46
+ for (const name of names) {
47
+ if (Object.prototype.hasOwnProperty.call(userVars, name)) {
48
+ resolved.set(name, userVars[name]);
49
+ continue;
50
+ }
51
+ const resolver = input.autoResolvers?.[name];
52
+ if (resolver) {
53
+ resolved.set(name, await resolver());
54
+ continue;
55
+ }
56
+ }
57
+ const missing = names.filter((n) => !resolved.has(n));
58
+ if (missing.length > 0) {
59
+ throw formatMissingError(missing);
60
+ }
61
+ const text = input.raw.replace(varPattern(), (_full, name) => {
62
+ return resolved.get(name);
63
+ });
64
+ return { text };
65
+ }
@@ -13,8 +13,9 @@ export interface SkillFiles {
13
13
  export declare function defaultFilesFromOutDir(outDir: string): SkillFiles;
14
14
  export declare function writeCredentials(path: string, creds: Credentials): Promise<void>;
15
15
  export declare function readCredentials(path: string): Promise<Credentials>;
16
- /** Like readCredentials, but returns undefined when the file does not exist. */
17
16
  export declare function tryReadCredentials(path: string): Promise<Credentials | undefined>;
18
17
  export declare function writeJwtFile(path: string, jwt: string): Promise<void>;
19
18
  export declare function tryReadJwtFile(path: string): Promise<string | undefined>;
20
- //# sourceMappingURL=credentials.d.ts.map
19
+ /** Best-effort removal of credential artifacts (ignore missing files). */
20
+ export declare function unlinkCredentialArtifacts(files: SkillFiles): Promise<void>;
21
+ //# sourceMappingURL=agent-credentials-store.d.ts.map