@atomicmail/agent-skill 0.2.1 → 0.2.2

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 CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: Install and run the @atomicmail/agent-skill CLI (register, jmap_request, help) for shell-capable agents and automation.
3
+ ---
4
+
1
5
  # @atomicmail/agent-skill
2
6
 
3
7
  Atomic Mail AgentSkill CLI for shell-capable AI agents. It exposes three
package/SKILL.md CHANGED
@@ -13,7 +13,8 @@ rotation. This skill ships a single CLI entrypoint with three commands:
13
13
 
14
14
  - Register a new inbox or log in with an existing API key.
15
15
  - Send JMAP batches (inline JSON or preset files).
16
- - Read built-in documentation (JMAP cheatsheet, presets, troubleshooting).
16
+ - Read built-in documentation (JMAP cheatsheet, presets, troubleshooting) or
17
+ the package README (`atomicmail help --topic readme`).
17
18
 
18
19
  ## Commands
19
20
 
@@ -57,9 +58,10 @@ npx --package=@atomicmail/agent-skill atomicmail jmap_request \
57
58
  --ops '[["Mailbox/get", {"accountId": "$ACCOUNT_ID"}, "m0"]]'
58
59
  ```
59
60
 
60
- `$ACCOUNT_ID` and `$INBOX` resolve from the session/credentials. Other
61
- placeholders such as `$TO` or `$SUBJECT` require `--vars` with a JSON object of
62
- strings (same substitution applies to `--ops` and `--ops-file`).
61
+ `$ACCOUNT_ID`, `$INBOX`, `$UPLOAD_URL`, and `$DOWNLOAD_URL` resolve from the
62
+ session/credentials. Other placeholders such as `$TO` or `$SUBJECT` require
63
+ `--vars` with a JSON object of strings (same substitution applies to `--ops` and
64
+ `--ops-file`).
63
65
 
64
66
  Preset file:
65
67
 
@@ -94,6 +96,42 @@ npx --package=@atomicmail/agent-skill atomicmail help --topic jmap_cheatsheet
94
96
  - `credentials.json` holds the API key (mode `0600`). Do not commit it.
95
97
  - JWT files are bearer secrets — do not log them.
96
98
 
99
+ ## Attachments and blobs
100
+
101
+ ### Inline blobs in JMAP (RFC 9404)
102
+
103
+ Use `Blob/upload` and `Blob/get` through `jmap_request` by adding
104
+ `urn:ietf:params:jmap:blob` to `using`.
105
+
106
+ ```bash
107
+ npx --package=@atomicmail/agent-skill atomicmail jmap_request \
108
+ --ops '{
109
+ "using":[
110
+ "urn:ietf:params:jmap:core",
111
+ "urn:ietf:params:jmap:mail",
112
+ "urn:ietf:params:jmap:submission",
113
+ "urn:ietf:params:jmap:blob"
114
+ ],
115
+ "methodCalls":[
116
+ ["Blob/upload",{
117
+ "accountId":"$ACCOUNT_ID",
118
+ "create":{"b1":{"data:asText":"Hello attachment","type":"text/plain"}}
119
+ },"b0"]
120
+ ]
121
+ }'
122
+ ```
123
+
124
+ ### Separate upload/download templates (RFC 8620)
125
+
126
+ `credentials.json` stores `uploadUrl` and `downloadUrl` from
127
+ `GET /.well-known/jmap`.
128
+
129
+ - `$UPLOAD_URL` is the RFC 8620 upload template.
130
+ - `$DOWNLOAD_URL` is the RFC 8620 download template.
131
+
132
+ Use these placeholders when building out-of-band blob transfer steps and attach
133
+ the resulting `blobId` in follow-up JMAP calls.
134
+
97
135
  ## Overriding defaults
98
136
 
99
137
  - Endpoints: `--auth-url`, `--api-url` or `ATOMIC_MAIL_AUTH_URL`,
@@ -1 +1 @@
1
- {"version":3,"file":"agent-auth-http.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/auth/agent-auth-http.ts"],"names":[],"mappings":"AAoCA,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CAqBD;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,eAAe,CAAC,CAS1B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
1
+ {"version":3,"file":"agent-auth-http.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/auth/agent-auth-http.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7D,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA8BD;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,eAAe,CAAC,CA8B1B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,eAAe,CAAC,CAgB1B"}
@@ -1,66 +1,65 @@
1
1
  // auth-service HTTP: challenge → session → capability.
2
2
  import { decodeJwtPayload } from "./agent-jwt.js";
3
3
  import { solvePow } from "./agent-pow.js";
4
- async function postJson(url, body, headers = {}) {
5
- const res = await fetch(url, {
4
+ export async function fetchChallenge(authUrl) {
5
+ const res = await fetch(`${authUrl}/api/v1/challenge`, {
6
6
  method: "POST",
7
- headers: {
8
- ...(body ? { "Content-Type": "application/json" } : {}),
9
- ...headers,
10
- },
11
- body: body ? JSON.stringify(body) : undefined,
12
7
  });
13
8
  const text = await res.text();
14
- const path = (() => {
15
- try {
16
- return new URL(url).pathname;
17
- }
18
- catch {
19
- return url;
20
- }
21
- })();
22
9
  if (!res.ok) {
23
- throw new Error(`auth-service ${path} returned ${res.status}: ${text}`);
24
- }
25
- try {
26
- return JSON.parse(text);
10
+ throw new Error(`auth-service /api/v1/challenge returned ${res.status}: ${text}`);
27
11
  }
28
- catch {
29
- throw new Error(`auth-service ${path} returned non-JSON body: ${text}`);
30
- }
31
- }
32
- export async function fetchChallenge(authUrl) {
33
- const data = await postJson(`${authUrl}/api/v1/challenge`, undefined);
34
- if (typeof data.challengeJWT !== "string") {
35
- throw new Error("Challenge response missing challengeJWT.");
36
- }
37
- const payload = decodeJwtPayload(data.challengeJWT);
12
+ const challengeJWT = readBearerToken(res.headers.get("Authorization"), "Challenge response missing Authorization bearer token.");
13
+ const payload = decodeJwtPayload(challengeJWT);
38
14
  if (typeof payload.jti !== "string" ||
39
15
  typeof payload.difficulty !== "number") {
40
16
  throw new Error("Challenge JWT payload malformed (missing jti or difficulty).");
41
17
  }
42
18
  return {
43
- challengeJWT: data.challengeJWT,
19
+ challengeJWT,
44
20
  challenge: payload.jti,
45
21
  difficulty: payload.difficulty,
46
22
  };
47
23
  }
48
24
  export async function exchangeSession(authUrl, body) {
49
- const data = await postJson(`${authUrl}/api/v1/session`, { ...body });
50
- if (typeof data.sessionJWT !== "string") {
51
- throw new Error("Session response missing sessionJWT.");
25
+ const { challengeJWT, ...payload } = body;
26
+ const res = await fetch(`${authUrl}/api/v1/session`, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ Authorization: `Bearer ${challengeJWT}`,
31
+ },
32
+ body: JSON.stringify(payload),
33
+ });
34
+ const text = await res.text();
35
+ if (!res.ok) {
36
+ throw new Error(`auth-service /api/v1/session returned ${res.status}: ${text}`);
37
+ }
38
+ const sessionJWT = readBearerToken(res.headers.get("Authorization"), "Session response missing Authorization bearer token.");
39
+ let data = {};
40
+ if (text.trim().length > 0) {
41
+ try {
42
+ data = JSON.parse(text);
43
+ }
44
+ catch {
45
+ throw new Error("auth-service /api/v1/session returned non-JSON body.");
46
+ }
52
47
  }
53
48
  return {
54
- sessionJWT: data.sessionJWT,
49
+ sessionJWT,
55
50
  apiKey: typeof data.apiKey === "string" ? data.apiKey : undefined,
56
51
  };
57
52
  }
58
53
  export async function fetchCapability(authUrl, sessionJWT) {
59
- const data = await postJson(`${authUrl}/api/v1/capability`, undefined, { Authorization: `Bearer ${sessionJWT}` });
60
- if (typeof data.capabilityJWT !== "string") {
61
- throw new Error("Capability response missing capabilityJWT.");
54
+ const res = await fetch(`${authUrl}/api/v1/capability`, {
55
+ method: "POST",
56
+ headers: { Authorization: `Bearer ${sessionJWT}` },
57
+ });
58
+ const text = await res.text();
59
+ if (!res.ok) {
60
+ throw new Error(`auth-service /api/v1/capability returned ${res.status}: ${text}`);
62
61
  }
63
- return data.capabilityJWT;
62
+ return readBearerToken(res.headers.get("Authorization"), "Capability response missing Authorization bearer token.");
64
63
  }
65
64
  export async function performPoWAndSession(input) {
66
65
  const { authUrl, scryptSalt } = input;
@@ -74,3 +73,13 @@ export async function performPoWAndSession(input) {
74
73
  username: input.username,
75
74
  });
76
75
  }
76
+ function readBearerToken(headerValue, missingError) {
77
+ if (!headerValue) {
78
+ throw new Error(missingError);
79
+ }
80
+ const match = /^\s*Bearer\s+(.+?)\s*$/i.exec(headerValue);
81
+ if (!match || !match[1]) {
82
+ throw new Error("Authorization header must use Bearer scheme.");
83
+ }
84
+ return match[1];
85
+ }
@@ -1,5 +1,3 @@
1
- export declare const SESSION_TTL_MS: number;
2
- export declare const CAPABILITY_TTL_MS: number;
3
1
  export declare const SESSION_SAFETY_MARGIN_MS = 60000;
4
2
  export declare const CAPABILITY_SAFETY_MARGIN_MS = 20000;
5
3
  export interface JwtPayload {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-jwt.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/auth/agent-jwt.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,QAAqB,CAAC;AACjD,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAE/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"}
1
+ {"version":3,"file":"agent-jwt.d.ts","sourceRoot":"","sources":["../../../../src/lib/agent/auth/agent-jwt.ts"],"names":[],"mappings":"AAEA,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"}
@@ -1,6 +1,4 @@
1
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
2
  export const SESSION_SAFETY_MARGIN_MS = 60_000;
5
3
  export const CAPABILITY_SAFETY_MARGIN_MS = 20_000;
6
4
  export function decodeJwtPayload(jwt) {
@@ -1,4 +1,5 @@
1
1
  export declare const HELP_TOPICS: Record<string, string>;
2
2
  export declare const HELP_TOPIC_LIST: string[];
3
+ export declare function normalizeHelpTopic(topic: string): string;
3
4
  export declare function getHelp(topic?: string): string;
4
5
  //# sourceMappingURL=agent-help-content.d.ts.map
@@ -1 +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"}
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,CA0P9C,CAAC;AAEF,eAAO,MAAM,eAAe,UAA2B,CAAC;AAExD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB9C"}
@@ -16,10 +16,12 @@ Three operations only:
16
16
  2. **jmap_request** — Send a JMAP method-call batch. Auth and JWT rotation are
17
17
  automatic. Pass inline \`ops\` JSON or an \`ops_file\` preset path (same
18
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\`).
19
+ \`$INBOX\`, \`$UPLOAD_URL\`, \`$DOWNLOAD_URL\`, \`$TO\`, \`$SUBJECT\` are
20
+ replaced before the request is sent. \`$ACCOUNT_ID\` / \`$INBOX\` /
21
+ \`$UPLOAD_URL\` / \`$DOWNLOAD_URL\` come from the JMAP session and
22
+ credentials; pass any other names via MCP \`vars\` or skill \`--vars\`.
23
+ 3. **help** — This documentation (optional \`topic\` / \`--topic\`), or the
24
+ published package README (\`topic\` / \`--topic\` \`readme\`).
23
25
 
24
26
  ## Typical workflow
25
27
 
@@ -29,7 +31,7 @@ Three operations only:
29
31
  3. If stuck, read error hints and call \`help\`.
30
32
 
31
33
  Available topics: overview, installation, auth, jmap_cheatsheet, tools,
32
- presets, troubleshooting.`,
34
+ presets, troubleshooting. Use \`readme\` for the npm package \`README.md\`.`,
33
35
  installation: `\
34
36
  # Atomic Mail — Installation
35
37
 
@@ -86,18 +88,22 @@ See each package README for details.`,
86
88
  Auth is automatic after \`register\` (or when \`credentials.json\` + API key
87
89
  exist).
88
90
 
89
- 1. **Challenge** — \`POST /api/v1/challenge\`
91
+ 1. **Challenge** — \`POST /api/v1/challenge\`, read challenge JWT from
92
+ \`Authorization: Bearer <challengeJWT>\`
90
93
  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
94
+ 3. **Session JWT** — \`POST /api/v1/session\` with challenge JWT in
95
+ \`Authorization: Bearer ...\` and PoW fields (\`powHex\`, \`nonce\`) in JSON
96
+ body; read session JWT from response \`Authorization: Bearer ...\` (1h TTL);
97
+ signup returns \`apiKey\` once
98
+ 4. **Capability JWT** — \`POST /api/v1/capability\` with session JWT in
99
+ \`Authorization: Bearer ...\`; read capability JWT from response
100
+ \`Authorization: Bearer ...\` (2 min TTL) used as the JMAP bearer
95
101
 
96
102
  JWTs are rotated before expiry and written back to disk.
97
103
 
98
104
  ## Credential files (mode 0600)
99
105
 
100
- \`credentials.json\` — \`{ apiKey, inboxId, authUrl, apiUrl, scryptSalt }\`
106
+ \`credentials.json\` — \`{ apiKey, inboxId, authUrl, apiUrl, scryptSalt, uploadUrl, downloadUrl }\`
101
107
  \`session.jwt\` — session token
102
108
  \`capability.jwt\` — capability token
103
109
 
@@ -119,7 +125,8 @@ JWTs are rotated before expiry and written back to disk.
119
125
 
120
126
  ## Examples
121
127
 
122
- Use \`$ACCOUNT_ID\` and \`$INBOX\` for session fields; use \`$TO\`, \`$SUBJECT\`,
128
+ Use \`$ACCOUNT_ID\`, \`$INBOX\`, \`$UPLOAD_URL\`, and \`$DOWNLOAD_URL\` for
129
+ session fields; use \`$TO\`, \`$SUBJECT\`,
123
130
  etc., and supply values via MCP \`vars\` or \`--vars\` (JSON object of strings).
124
131
 
125
132
  ### Mailboxes
@@ -174,7 +181,8 @@ replaces credentials in the directory and registers a new inbox.
174
181
  **Skill:** \`help [--topic TOPIC]\`
175
182
 
176
183
  Topics: overview, installation, auth, jmap_cheatsheet, tools, presets,
177
- troubleshooting.`,
184
+ troubleshooting. Topic \`readme\` prints the published package \`README.md\`
185
+ (same layout as npm; requires install from npm).`,
178
186
  presets: `\
179
187
  # JMAP presets
180
188
 
@@ -199,11 +207,14 @@ keywords like \`$draft\` stay untouched).
199
207
  - \`$ACCOUNT_ID\` — primary mail account id (from \`GET /.well-known/jmap\` when
200
208
  referenced).
201
209
  - \`$INBOX\` — inbox email address from credentials.
210
+ - \`$UPLOAD_URL\` — RFC 8620 upload URL template from JMAP session.
211
+ - \`$DOWNLOAD_URL\` — RFC 8620 download URL template from JMAP session.
202
212
  - Any other \`$FOO\` — must appear in MCP \`vars\` or skill \`--vars\` as
203
213
  \`"FOO": "..."\` (string values only; JSON escaping in the preset body is your
204
214
  responsibility).
205
215
 
206
- You may override \`ACCOUNT_ID\` / \`INBOX\` via \`vars\` / \`--vars\` if needed.`,
216
+ You may override \`ACCOUNT_ID\` / \`INBOX\` / \`UPLOAD_URL\` / \`DOWNLOAD_URL\`
217
+ via \`vars\` / \`--vars\` if needed.`,
207
218
  troubleshooting: `\
208
219
  # Troubleshooting
209
220
 
@@ -234,11 +245,19 @@ Pass every custom placeholder in MCP \`vars\` or \`--vars\` as a JSON object of
234
245
  strings. Ensure \`register\` completed so \`$ACCOUNT_ID\` / \`$INBOX\` can resolve.`,
235
246
  };
236
247
  export const HELP_TOPIC_LIST = Object.keys(HELP_TOPICS);
248
+ export function normalizeHelpTopic(topic) {
249
+ return topic.toLowerCase().replace(/[\s-]/g, "_");
250
+ }
237
251
  export function getHelp(topic) {
238
252
  if (!topic) {
239
253
  return HELP_TOPICS["overview"];
240
254
  }
241
- const key = topic.toLowerCase().replace(/[\s-]/g, "_");
255
+ const key = normalizeHelpTopic(topic);
256
+ if (key === "readme") {
257
+ return ("Topic \"readme\" prints the package README.md from the npm install. " +
258
+ "From MCP use {\"topic\":\"readme\"}; from the CLI: " +
259
+ "`atomicmail help --topic readme`.");
260
+ }
242
261
  return (HELP_TOPICS[key] ??
243
- `Unknown topic "${topic}". Available topics: ${HELP_TOPIC_LIST.join(", ")}`);
262
+ `Unknown topic "${topic}". Available topics: ${HELP_TOPIC_LIST.join(", ")}, readme`);
244
263
  }
@@ -8,6 +8,11 @@ export declare function parseJmapEnvelope(raw: string, defaultUsing: string[], s
8
8
  export declare function resolveOpsFilePath(credentialDir: string, opsFile: string): string;
9
9
  export declare function readOpsFile(credentialDir: string, opsFile: string): Promise<string>;
10
10
  export declare function extractPrimaryMailAccountId(session: Record<string, unknown>): string;
11
+ export interface JmapBlobEndpoints {
12
+ uploadUrl: string;
13
+ downloadUrl: string;
14
+ }
15
+ export declare function extractBlobEndpoints(session: Record<string, unknown>): JmapBlobEndpoints;
11
16
  export declare function fetchJmapWellKnown(apiUrl: string, capabilityJwt: string): Promise<Record<string, unknown>>;
12
17
  /** Minimal surface for JMAP execution (implemented by AgentSession). */
13
18
  export interface JmapSessionPort {
@@ -18,6 +23,8 @@ export interface JmapSessionPort {
18
23
  getPrimaryMailAccountId(): Promise<string>;
19
24
  getCapabilityToken(): Promise<string>;
20
25
  readonly currentInboxId?: string;
26
+ readonly currentUploadUrl?: string;
27
+ readonly currentDownloadUrl?: string;
21
28
  }
22
29
  export interface RunJmapRequestInput {
23
30
  session: JmapSessionPort;
@@ -1 +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"}
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,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,iBAAiB,CAUnB;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;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;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,CAmD5D;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"}
@@ -93,6 +93,17 @@ export function extractPrimaryMailAccountId(session) {
93
93
  }
94
94
  return id;
95
95
  }
96
+ export function extractBlobEndpoints(session) {
97
+ const uploadUrl = session["uploadUrl"];
98
+ const downloadUrl = session["downloadUrl"];
99
+ if (typeof uploadUrl !== "string" || uploadUrl.length === 0) {
100
+ throw new Error("JMAP session missing uploadUrl.");
101
+ }
102
+ if (typeof downloadUrl !== "string" || downloadUrl.length === 0) {
103
+ throw new Error("JMAP session missing downloadUrl.");
104
+ }
105
+ return { uploadUrl, downloadUrl };
106
+ }
96
107
  export async function fetchJmapWellKnown(apiUrl, capabilityJwt) {
97
108
  const base = apiUrl.replace(/\/+$/, "");
98
109
  const res = await fetch(`${base}/.well-known/jmap`, {
@@ -120,6 +131,11 @@ export async function runJmapRequest(input) {
120
131
  ACCOUNT_ID: () => input.session.getPrimaryMailAccountId(),
121
132
  INBOX: async () => input.session.currentInboxId ??
122
133
  (await readCredentials(input.session.files.credentialsFile)).inboxId,
134
+ UPLOAD_URL: async () => input.session.currentUploadUrl ??
135
+ (await readCredentials(input.session.files.credentialsFile)).uploadUrl,
136
+ DOWNLOAD_URL: async () => input.session.currentDownloadUrl ??
137
+ (await readCredentials(input.session.files.credentialsFile))
138
+ .downloadUrl,
123
139
  },
124
140
  });
125
141
  const envelope = parseJmapEnvelope(raw, input.defaultUsing, input.sourceLabel);
@@ -4,6 +4,8 @@ export interface Credentials {
4
4
  authUrl: string;
5
5
  apiUrl: string;
6
6
  scryptSalt: string;
7
+ uploadUrl: string;
8
+ downloadUrl: string;
7
9
  }
8
10
  export interface SkillFiles {
9
11
  credentialsFile: string;
@@ -1 +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
+ {"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;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;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,CAmCxE;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"}
@@ -39,6 +39,8 @@ export async function readCredentials(path) {
39
39
  "authUrl",
40
40
  "apiUrl",
41
41
  "scryptSalt",
42
+ "uploadUrl",
43
+ "downloadUrl",
42
44
  ];
43
45
  for (const k of required) {
44
46
  if (typeof obj[k] !== "string" || obj[k].length === 0) {
@@ -28,16 +28,21 @@ export declare class AgentSession {
28
28
  private sessionJWT;
29
29
  private capabilityJWT;
30
30
  private cachedMailAccountId;
31
+ private cachedUploadUrl;
32
+ private cachedDownloadUrl;
31
33
  constructor(cfg: AgentSessionConfig);
32
34
  static create(cfg: AgentSessionConfig): Promise<AgentSession>;
33
35
  get hasApiKey(): boolean;
34
36
  get currentInboxId(): string | undefined;
37
+ get currentUploadUrl(): string | undefined;
38
+ get currentDownloadUrl(): string | undefined;
35
39
  private loadFromDisk;
36
40
  /**
37
41
  * Primary JMAP mail accountId from GET /.well-known/jmap (cached).
38
42
  */
39
43
  getPrimaryMailAccountId(): Promise<string>;
40
44
  invalidateJmapSessionCache(): void;
45
+ private refreshJmapSessionData;
41
46
  /**
42
47
  * Register or return existing inbox when username matches (idempotent).
43
48
  * Different username replaces on-disk credentials and creates a new inbox.
@@ -1 +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"}
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;AActC,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;IAChD,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,iBAAiB,CAAqB;gBAElC,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;IAED,IAAI,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED,IAAI,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAE3C;YAEa,YAAY;IAY1B;;OAEG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAehD,0BAA0B,IAAI,IAAI;YAMpB,sBAAsB;IASpC;;;OAGG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA8EnD,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;YA4B7B,aAAa;IA2B3B,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,CA6B9B"}
@@ -1,7 +1,7 @@
1
1
  // Stateful PoW + capability JWT + optional cached JMAP session (accountId).
2
2
  import { tryReadCredentials, tryReadJwtFile, unlinkCredentialArtifacts, writeCredentials, writeJwtFile, } from "./agent-credentials-store.js";
3
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";
4
+ import { extractBlobEndpoints, extractPrimaryMailAccountId, fetchJmapWellKnown, } from "../jmap/agent-jmap.js";
5
5
  import { fetchCapability, performPoWAndSession } from "../auth/agent-auth-http.js";
6
6
  function normalizeUsername(u) {
7
7
  return u.trim().toLowerCase();
@@ -24,6 +24,8 @@ export class AgentSession {
24
24
  sessionJWT;
25
25
  capabilityJWT;
26
26
  cachedMailAccountId;
27
+ cachedUploadUrl;
28
+ cachedDownloadUrl;
27
29
  constructor(cfg) {
28
30
  this.authUrl = cfg.authUrl.replace(/\/+$/, "");
29
31
  this.apiUrl = cfg.apiUrl.replace(/\/+$/, "");
@@ -44,6 +46,12 @@ export class AgentSession {
44
46
  get currentInboxId() {
45
47
  return this.inboxId;
46
48
  }
49
+ get currentUploadUrl() {
50
+ return this.cachedUploadUrl;
51
+ }
52
+ get currentDownloadUrl() {
53
+ return this.cachedDownloadUrl;
54
+ }
47
55
  async loadFromDisk() {
48
56
  this.sessionJWT = await tryReadJwtFile(this.files.sessionFile);
49
57
  this.capabilityJWT = await tryReadJwtFile(this.files.capabilityFile);
@@ -51,22 +59,37 @@ export class AgentSession {
51
59
  if (disk) {
52
60
  this.apiKey = this.apiKey ?? disk.apiKey;
53
61
  this.inboxId = this.inboxId ?? disk.inboxId;
62
+ this.cachedUploadUrl = disk.uploadUrl;
63
+ this.cachedDownloadUrl = disk.downloadUrl;
54
64
  }
55
65
  }
56
66
  /**
57
67
  * Primary JMAP mail accountId from GET /.well-known/jmap (cached).
58
68
  */
59
69
  async getPrimaryMailAccountId() {
60
- if (this.cachedMailAccountId)
70
+ if (this.cachedMailAccountId &&
71
+ this.cachedUploadUrl &&
72
+ this.cachedDownloadUrl) {
61
73
  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;
74
+ }
75
+ await this.refreshJmapSessionData();
76
+ if (!this.cachedMailAccountId) {
77
+ throw new Error("JMAP session missing primary mail account id.");
78
+ }
79
+ return this.cachedMailAccountId;
67
80
  }
68
81
  invalidateJmapSessionCache() {
69
82
  this.cachedMailAccountId = undefined;
83
+ this.cachedUploadUrl = undefined;
84
+ this.cachedDownloadUrl = undefined;
85
+ }
86
+ async refreshJmapSessionData() {
87
+ const cap = await this.getCapabilityToken();
88
+ const session = await fetchJmapWellKnown(this.apiUrl, cap);
89
+ this.cachedMailAccountId = extractPrimaryMailAccountId(session);
90
+ const blobs = extractBlobEndpoints(session);
91
+ this.cachedUploadUrl = blobs.uploadUrl;
92
+ this.cachedDownloadUrl = blobs.downloadUrl;
70
93
  }
71
94
  /**
72
95
  * Register or return existing inbox when username matches (idempotent).
@@ -115,15 +138,22 @@ export class AgentSession {
115
138
  }
116
139
  this.inboxId = claims.inboxId;
117
140
  this.cachedMailAccountId = undefined;
141
+ this.cachedUploadUrl = undefined;
142
+ this.cachedDownloadUrl = undefined;
143
+ const accountId = await this.getPrimaryMailAccountId();
144
+ if (!this.cachedUploadUrl || !this.cachedDownloadUrl) {
145
+ throw new Error("JMAP session did not provide upload/download URLs.");
146
+ }
118
147
  const creds = {
119
148
  apiKey: this.apiKey,
120
149
  inboxId: this.inboxId,
121
150
  authUrl: this.authUrl,
122
151
  apiUrl: this.apiUrl,
123
152
  scryptSalt: this.scryptSalt,
153
+ uploadUrl: this.cachedUploadUrl,
154
+ downloadUrl: this.cachedDownloadUrl,
124
155
  };
125
156
  await writeCredentials(this.files.credentialsFile, creds);
126
- const accountId = await this.getPrimaryMailAccountId();
127
157
  return {
128
158
  inbox: this.inboxId,
129
159
  accountId,
@@ -171,6 +201,8 @@ export class AgentSession {
171
201
  this.sessionJWT = result.sessionJWT;
172
202
  this.capabilityJWT = undefined;
173
203
  this.cachedMailAccountId = undefined;
204
+ this.cachedUploadUrl = undefined;
205
+ this.cachedDownloadUrl = undefined;
174
206
  await writeJwtFile(this.files.sessionFile, this.sessionJWT);
175
207
  }
176
208
  destroy() {
@@ -193,12 +225,16 @@ export async function persistLoginWithApiKey(input) {
193
225
  if (typeof inboxId !== "string" || inboxId.length === 0) {
194
226
  throw new Error("Capability JWT did not contain an inboxId claim.");
195
227
  }
228
+ const jmapSession = await fetchJmapWellKnown(apiUrl, capabilityJWT);
229
+ const blobs = extractBlobEndpoints(jmapSession);
196
230
  await writeCredentials(input.files.credentialsFile, {
197
231
  apiKey: input.apiKey,
198
232
  inboxId,
199
233
  authUrl,
200
234
  apiUrl,
201
235
  scryptSalt: input.scryptSalt,
236
+ uploadUrl: blobs.uploadUrl,
237
+ downloadUrl: blobs.downloadUrl,
202
238
  });
203
239
  await writeJwtFile(input.files.sessionFile, session.sessionJWT);
204
240
  await writeJwtFile(input.files.capabilityFile, capabilityJWT);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Reads README.md from the npm package root (next to package.json).
3
+ * Intended for published @atomicmail/mcp and @atomicmail/agent-skill layouts.
4
+ */
5
+ export declare function readNpmPackageReadme(): Promise<string>;
6
+ //# sourceMappingURL=read-npm-package-readme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-npm-package-readme.d.ts","sourceRoot":"","sources":["../../../src/lib/core/read-npm-package-readme.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CA+C5D"}
@@ -0,0 +1,66 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const MAX_DEPTH = 16;
5
+ const ATOMICMAIL_NPM_NAMES = new Set([
6
+ "@atomicmail/mcp",
7
+ "@atomicmail/agent-skill",
8
+ ]);
9
+ function isEnoent(err) {
10
+ if (!(err instanceof Error))
11
+ return false;
12
+ const code = err.code;
13
+ return code === "ENOENT" || code === "ENOTDIR";
14
+ }
15
+ /**
16
+ * Reads README.md from the npm package root (next to package.json).
17
+ * Intended for published @atomicmail/mcp and @atomicmail/agent-skill layouts.
18
+ */
19
+ export async function readNpmPackageReadme() {
20
+ const moduleDir = dirname(fileURLToPath(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url));
21
+ let currentDir = moduleDir;
22
+ for (let i = 0; i < MAX_DEPTH; i++) {
23
+ const pkgPath = resolve(currentDir, "package.json");
24
+ const readmePath = resolve(currentDir, "README.md");
25
+ let pkgRaw;
26
+ try {
27
+ pkgRaw = await readFile(pkgPath, "utf-8");
28
+ }
29
+ catch (err) {
30
+ if (!isEnoent(err))
31
+ throw err;
32
+ const parent = resolve(currentDir, "..");
33
+ if (parent === currentDir)
34
+ break;
35
+ currentDir = parent;
36
+ continue;
37
+ }
38
+ let name;
39
+ try {
40
+ name = JSON.parse(pkgRaw).name;
41
+ }
42
+ catch {
43
+ name = undefined;
44
+ }
45
+ if (!name || !ATOMICMAIL_NPM_NAMES.has(name)) {
46
+ const parent = resolve(currentDir, "..");
47
+ if (parent === currentDir)
48
+ break;
49
+ currentDir = parent;
50
+ continue;
51
+ }
52
+ try {
53
+ return await readFile(readmePath, "utf-8");
54
+ }
55
+ catch (err) {
56
+ if (!isEnoent(err))
57
+ throw err;
58
+ }
59
+ const parent = resolve(currentDir, "..");
60
+ if (parent === currentDir)
61
+ break;
62
+ currentDir = parent;
63
+ }
64
+ throw new Error("Could not find Atomic Mail package README.md — use a published npm install " +
65
+ "(@atomicmail/mcp or @atomicmail/agent-skill) for --topic readme.");
66
+ }
package/esm/lib/mod.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./network/auth-client.js";
2
2
  export * from "./core/utils.js";
3
+ export * from "./core/read-npm-package-readme.js";
3
4
  export * from "./core/consts.js";
4
5
  export * from "./core/types.js";
5
6
  export * from "./agent/session/agent-credentials-store.js";
@@ -1 +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"}
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,mCAAmC,CAAC;AAClD,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 CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./network/auth-client.js";
2
2
  export * from "./core/utils.js";
3
+ export * from "./core/read-npm-package-readme.js";
3
4
  export * from "./core/consts.js";
4
5
  export * from "./core/types.js";
5
6
  export * from "./agent/session/agent-credentials-store.js";
@@ -1 +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"}
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;IAyBrD,4DAA4D;IACtD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAgBjD;;;OAGG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;YAoBvC,cAAc;YAsCd,WAAW;YAyCX,gBAAgB;IAuB9B;;;;;;;OAOG;YACW,QAAQ;CAgBvB"}
@@ -40,32 +40,26 @@ export class AuthClient {
40
40
  async signup(username) {
41
41
  const { challengeJWT, challenge, difficulty } = await this.fetchChallenge();
42
42
  const { powHex, nonce } = await this.solvePoW(challenge, difficulty);
43
- const data = await this.postSession({
44
- challengeJWT,
43
+ const { sessionJWT, data } = await this.postSession(challengeJWT, {
45
44
  powHex,
46
45
  nonce: nonce.toString(),
47
46
  username,
48
47
  });
49
- if (typeof data.apiKey !== "string" ||
50
- typeof data.sessionJWT !== "string") {
51
- throw new AuthClientError(200, JSON.stringify(data), "Signup response missing apiKey or sessionJWT.");
48
+ if (typeof data.apiKey !== "string") {
49
+ throw new AuthClientError(200, JSON.stringify(data), "Signup response missing apiKey.");
52
50
  }
53
- return { apiKey: data.apiKey, sessionJWT: data.sessionJWT };
51
+ return { apiKey: data.apiKey, sessionJWT };
54
52
  }
55
53
  /** Exchange an existing API key for a fresh session JWT. */
56
54
  async login(apiKey) {
57
55
  const { challengeJWT, challenge, difficulty } = await this.fetchChallenge();
58
56
  const { powHex, nonce } = await this.solvePoW(challenge, difficulty);
59
- const data = await this.postSession({
60
- challengeJWT,
57
+ const { sessionJWT } = await this.postSession(challengeJWT, {
61
58
  powHex,
62
59
  nonce: nonce.toString(),
63
60
  apiKey,
64
61
  });
65
- if (typeof data.sessionJWT !== "string") {
66
- throw new AuthClientError(200, JSON.stringify(data), "Login response missing sessionJWT.");
67
- }
68
- return { sessionJWT: data.sessionJWT };
62
+ return { sessionJWT };
69
63
  }
70
64
  /**
71
65
  * Exchange a session JWT for a short-lived capability JWT (audience:
@@ -76,38 +70,57 @@ export class AuthClient {
76
70
  method: "POST",
77
71
  headers: { Authorization: `Bearer ${sessionJWT}` },
78
72
  });
79
- const data = await this.parseJsonOrThrow(res, "capability");
80
- if (typeof data.capabilityJWT !== "string") {
81
- throw new AuthClientError(res.status, JSON.stringify(data), "Capability response missing capabilityJWT.");
73
+ const text = await res.text();
74
+ if (!res.ok) {
75
+ throw new AuthClientError(res.status, text, `auth-service capability returned ${res.status}: ${text}`);
82
76
  }
83
- return { capabilityJWT: data.capabilityJWT };
77
+ const capabilityJWT = readBearerToken(res.headers.get("Authorization"), "Capability response missing Authorization bearer token.");
78
+ return { capabilityJWT };
84
79
  }
85
80
  async fetchChallenge() {
86
81
  const res = await fetch(`${this.baseUrl}/api/v1/challenge`, {
87
82
  method: "POST",
88
83
  });
89
- const data = await this.parseJsonOrThrow(res, "challenge");
90
- if (typeof data.challengeJWT !== "string") {
91
- throw new AuthClientError(res.status, JSON.stringify(data), "Challenge response missing challengeJWT.");
84
+ const text = await res.text();
85
+ if (!res.ok) {
86
+ throw new AuthClientError(res.status, text, `auth-service challenge returned ${res.status}: ${text}`);
92
87
  }
93
- const payload = decodeJwtPayload(data.challengeJWT);
88
+ const challengeJWT = readBearerToken(res.headers.get("Authorization"), "Challenge response missing Authorization bearer token.");
89
+ const payload = decodeJwtPayload(challengeJWT);
94
90
  if (typeof payload.jti !== "string" ||
95
91
  typeof payload.difficulty !== "number") {
96
- throw new AuthClientError(res.status, data.challengeJWT, "Challenge JWT payload is malformed (missing jti or difficulty).");
92
+ throw new AuthClientError(res.status, challengeJWT, "Challenge JWT payload is malformed (missing jti or difficulty).");
97
93
  }
98
94
  return {
99
- challengeJWT: data.challengeJWT,
95
+ challengeJWT,
100
96
  challenge: payload.jti,
101
97
  difficulty: payload.difficulty,
102
98
  };
103
99
  }
104
- async postSession(body) {
100
+ async postSession(challengeJWT, body) {
105
101
  const res = await fetch(`${this.baseUrl}/api/v1/session`, {
106
102
  method: "POST",
107
- headers: { "Content-Type": "application/json" },
103
+ headers: {
104
+ "Content-Type": "application/json",
105
+ Authorization: `Bearer ${challengeJWT}`,
106
+ },
108
107
  body: JSON.stringify(body),
109
108
  });
110
- return await this.parseJsonOrThrow(res, "session");
109
+ const text = await res.text();
110
+ if (!res.ok) {
111
+ throw new AuthClientError(res.status, text, `auth-service session returned ${res.status}: ${text}`);
112
+ }
113
+ const sessionJWT = readBearerToken(res.headers.get("Authorization"), "Session response missing Authorization bearer token.");
114
+ let data = {};
115
+ if (text.trim().length > 0) {
116
+ try {
117
+ data = JSON.parse(text);
118
+ }
119
+ catch {
120
+ throw new AuthClientError(res.status, text, "auth-service session returned non-JSON body.");
121
+ }
122
+ }
123
+ return { sessionJWT, data };
111
124
  }
112
125
  async parseJsonOrThrow(res, endpoint) {
113
126
  const text = await res.text();
@@ -186,3 +199,13 @@ function decodeJwtPayload(jwt) {
186
199
  .padEnd(payloadB64Url.length + padLen, "=");
187
200
  return JSON.parse(atob(base64));
188
201
  }
202
+ function readBearerToken(headerValue, missingError) {
203
+ if (!headerValue) {
204
+ throw new Error(missingError);
205
+ }
206
+ const match = /^\s*Bearer\s+(.+?)\s*$/i.exec(headerValue);
207
+ if (!match || !match[1]) {
208
+ throw new Error("Authorization header must use Bearer scheme.");
209
+ }
210
+ return match[1];
211
+ }
package/esm/skill/cli.js CHANGED
@@ -5,7 +5,7 @@ import process from "node:process";
5
5
  import { parseArgs } from "node:util";
6
6
  import { resolve } from "node:path";
7
7
  import { homedir } from "node:os";
8
- import { AgentSession, DEFAULT_JMAP_USING, DEFAULT_POW_SCRYPT_SALT_HEX, defaultFilesFromOutDir, getHelp, persistLoginWithApiKey, readCredentials, readOpsFile, runJmapRequest, } from "../lib/mod.js";
8
+ import { AgentSession, DEFAULT_JMAP_USING, DEFAULT_POW_SCRYPT_SALT_HEX, defaultFilesFromOutDir, getHelp, normalizeHelpTopic, persistLoginWithApiKey, readCredentials, readNpmPackageReadme, readOpsFile, runJmapRequest, } from "../lib/mod.js";
9
9
  const USAGE = `Atomic Mail — AgentSkill
10
10
 
11
11
  Usage:
@@ -14,7 +14,7 @@ Usage:
14
14
  Commands:
15
15
  register PoW signup or login with API key (writes credentials)
16
16
  jmap_request Send a JMAP batch (inline --ops or --ops-file preset)
17
- help Full documentation [--topic TOPIC]
17
+ help Full documentation [--topic TOPIC] (topic readme = package README)
18
18
 
19
19
  Examples:
20
20
  atomicmail register --username alice
@@ -22,6 +22,7 @@ Examples:
22
22
  atomicmail jmap_request --credentials-dir ./.atomic-mail --ops-file fetch.json
23
23
  atomicmail jmap_request --credentials-dir ./.atomic-mail --ops-file send.json --vars '{"TO":"a@b.com","SUBJECT":"Hi"}'
24
24
  atomicmail help --topic presets
25
+ atomicmail help --topic readme
25
26
 
26
27
  Run atomicmail <command> --help for command-specific flags.
27
28
  `;
@@ -252,7 +253,7 @@ Options:
252
253
  }
253
254
  process.stdout.write(bodyText.endsWith("\n") ? bodyText : bodyText + "\n");
254
255
  }
255
- function cmdHelp(argv) {
256
+ async function cmdHelp(argv) {
256
257
  let parsed;
257
258
  try {
258
259
  parsed = parseArgs({
@@ -271,11 +272,22 @@ function cmdHelp(argv) {
271
272
  if (parsed.values.help) {
272
273
  process.stdout.write(`Usage: atomicmail help [--topic TOPIC]
273
274
 
274
- Topics include: overview, installation, auth, jmap_cheatsheet, tools, presets, troubleshooting.
275
+ Topics include: overview, installation, auth, jmap_cheatsheet, tools, presets, troubleshooting, readme.
276
+ Topic readme prints the npm package README.md (requires install from npm).
275
277
  `);
276
278
  process.exit(0);
277
279
  }
278
280
  const topic = parsed.values.topic;
281
+ if (topic !== undefined && normalizeHelpTopic(topic) === "readme") {
282
+ try {
283
+ const text = await readNpmPackageReadme();
284
+ process.stdout.write(text.endsWith("\n") ? text : text + "\n");
285
+ }
286
+ catch (err) {
287
+ fail(err instanceof Error ? err.message : String(err));
288
+ }
289
+ return;
290
+ }
279
291
  process.stdout.write(getHelp(topic) + "\n");
280
292
  }
281
293
  async function main() {
@@ -293,7 +305,7 @@ async function main() {
293
305
  await cmdJmapRequest(rest);
294
306
  break;
295
307
  case "help":
296
- cmdHelp(rest);
308
+ await cmdHelp(rest);
297
309
  break;
298
310
  default:
299
311
  process.stderr.write(`Unknown command: ${cmd}\n\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomicmail/agent-skill",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Atomic Mail AgentSkill — register, jmap_request, and help CLI for AI agents.",
5
5
  "keywords": [
6
6
  "atomic-mail",