@atomicmail/agent-skill-openclaw 0.3.24

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/SKILL.md +206 -0
  4. package/esm/_dnt.polyfills.d.ts +101 -0
  5. package/esm/_dnt.polyfills.d.ts.map +1 -0
  6. package/esm/_dnt.polyfills.js +127 -0
  7. package/esm/lib/agent/auth/agent-auth-http.d.ts +26 -0
  8. package/esm/lib/agent/auth/agent-auth-http.d.ts.map +1 -0
  9. package/esm/lib/agent/auth/agent-auth-http.js +85 -0
  10. package/esm/lib/agent/auth/agent-jwt.d.ts +12 -0
  11. package/esm/lib/agent/auth/agent-jwt.d.ts.map +1 -0
  12. package/esm/lib/agent/auth/agent-jwt.js +27 -0
  13. package/esm/lib/agent/auth/agent-pow.d.ts +5 -0
  14. package/esm/lib/agent/auth/agent-pow.d.ts.map +1 -0
  15. package/esm/lib/agent/auth/agent-pow.js +49 -0
  16. package/esm/lib/agent/jmap/agent-help-content.d.ts +2 -0
  17. package/esm/lib/agent/jmap/agent-help-content.d.ts.map +1 -0
  18. package/esm/lib/agent/jmap/agent-help-content.js +2 -0
  19. package/esm/lib/agent/jmap/agent-jmap-blob-limits.d.ts +27 -0
  20. package/esm/lib/agent/jmap/agent-jmap-blob-limits.d.ts.map +1 -0
  21. package/esm/lib/agent/jmap/agent-jmap-blob-limits.js +166 -0
  22. package/esm/lib/agent/jmap/agent-jmap-blob-upload.d.ts +24 -0
  23. package/esm/lib/agent/jmap/agent-jmap-blob-upload.d.ts.map +1 -0
  24. package/esm/lib/agent/jmap/agent-jmap-blob-upload.js +104 -0
  25. package/esm/lib/agent/jmap/agent-jmap-email-charset.d.ts +8 -0
  26. package/esm/lib/agent/jmap/agent-jmap-email-charset.d.ts.map +1 -0
  27. package/esm/lib/agent/jmap/agent-jmap-email-charset.js +61 -0
  28. package/esm/lib/agent/jmap/agent-jmap-run.d.ts +52 -0
  29. package/esm/lib/agent/jmap/agent-jmap-run.d.ts.map +1 -0
  30. package/esm/lib/agent/jmap/agent-jmap-run.js +260 -0
  31. package/esm/lib/agent/jmap/agent-jmap-verify.d.ts +9 -0
  32. package/esm/lib/agent/jmap/agent-jmap-verify.d.ts.map +1 -0
  33. package/esm/lib/agent/jmap/agent-jmap-verify.js +50 -0
  34. package/esm/lib/agent/jmap/agent-jmap.d.ts +89 -0
  35. package/esm/lib/agent/jmap/agent-jmap.d.ts.map +1 -0
  36. package/esm/lib/agent/jmap/agent-jmap.js +373 -0
  37. package/esm/lib/agent/jmap/agent-vars.d.ts +30 -0
  38. package/esm/lib/agent/jmap/agent-vars.d.ts.map +1 -0
  39. package/esm/lib/agent/jmap/agent-vars.js +96 -0
  40. package/esm/lib/agent/jmap/help-content/auth.d.ts +2 -0
  41. package/esm/lib/agent/jmap/help-content/auth.d.ts.map +1 -0
  42. package/esm/lib/agent/jmap/help-content/auth.js +33 -0
  43. package/esm/lib/agent/jmap/help-content/cron.d.ts +6 -0
  44. package/esm/lib/agent/jmap/help-content/cron.d.ts.map +1 -0
  45. package/esm/lib/agent/jmap/help-content/cron.js +159 -0
  46. package/esm/lib/agent/jmap/help-content/index.d.ts +6 -0
  47. package/esm/lib/agent/jmap/help-content/index.d.ts.map +1 -0
  48. package/esm/lib/agent/jmap/help-content/index.js +61 -0
  49. package/esm/lib/agent/jmap/help-content/installation.d.ts +2 -0
  50. package/esm/lib/agent/jmap/help-content/installation.d.ts.map +1 -0
  51. package/esm/lib/agent/jmap/help-content/installation.js +47 -0
  52. package/esm/lib/agent/jmap/help-content/jmap-cheatsheet.d.ts +2 -0
  53. package/esm/lib/agent/jmap/help-content/jmap-cheatsheet.d.ts.map +1 -0
  54. package/esm/lib/agent/jmap/help-content/jmap-cheatsheet.js +230 -0
  55. package/esm/lib/agent/jmap/help-content/multi-account.d.ts +2 -0
  56. package/esm/lib/agent/jmap/help-content/multi-account.d.ts.map +1 -0
  57. package/esm/lib/agent/jmap/help-content/multi-account.js +49 -0
  58. package/esm/lib/agent/jmap/help-content/overview.d.ts +2 -0
  59. package/esm/lib/agent/jmap/help-content/overview.d.ts.map +1 -0
  60. package/esm/lib/agent/jmap/help-content/overview.js +49 -0
  61. package/esm/lib/agent/jmap/help-content/presets.d.ts +2 -0
  62. package/esm/lib/agent/jmap/help-content/presets.d.ts.map +1 -0
  63. package/esm/lib/agent/jmap/help-content/presets.js +51 -0
  64. package/esm/lib/agent/jmap/help-content/tools.d.ts +2 -0
  65. package/esm/lib/agent/jmap/help-content/tools.d.ts.map +1 -0
  66. package/esm/lib/agent/jmap/help-content/tools.js +49 -0
  67. package/esm/lib/agent/jmap/help-content/troubleshooting.d.ts +2 -0
  68. package/esm/lib/agent/jmap/help-content/troubleshooting.d.ts.map +1 -0
  69. package/esm/lib/agent/jmap/help-content/troubleshooting.js +65 -0
  70. package/esm/lib/agent/session/agent-credentials-store.d.ts +45 -0
  71. package/esm/lib/agent/session/agent-credentials-store.d.ts.map +1 -0
  72. package/esm/lib/agent/session/agent-credentials-store.js +121 -0
  73. package/esm/lib/agent/session/agent-resolve-config.d.ts +29 -0
  74. package/esm/lib/agent/session/agent-resolve-config.d.ts.map +1 -0
  75. package/esm/lib/agent/session/agent-resolve-config.js +71 -0
  76. package/esm/lib/agent/session/agent-session-for-dir.d.ts +8 -0
  77. package/esm/lib/agent/session/agent-session-for-dir.d.ts.map +1 -0
  78. package/esm/lib/agent/session/agent-session-for-dir.js +33 -0
  79. package/esm/lib/agent/session/agent-session.d.ts +89 -0
  80. package/esm/lib/agent/session/agent-session.d.ts.map +1 -0
  81. package/esm/lib/agent/session/agent-session.js +320 -0
  82. package/esm/lib/agent/session/inbox-id-to-mailbox-email.d.ts +6 -0
  83. package/esm/lib/agent/session/inbox-id-to-mailbox-email.d.ts.map +1 -0
  84. package/esm/lib/agent/session/inbox-id-to-mailbox-email.js +21 -0
  85. package/esm/lib/core/consts.d.ts +17 -0
  86. package/esm/lib/core/consts.d.ts.map +1 -0
  87. package/esm/lib/core/consts.js +28 -0
  88. package/esm/lib/core/jmap-hints.d.ts +3 -0
  89. package/esm/lib/core/jmap-hints.d.ts.map +1 -0
  90. package/esm/lib/core/jmap-hints.js +6 -0
  91. package/esm/lib/core/messages.d.ts +6 -0
  92. package/esm/lib/core/messages.d.ts.map +1 -0
  93. package/esm/lib/core/messages.js +19 -0
  94. package/esm/lib/core/read-npm-package-readme.d.ts +6 -0
  95. package/esm/lib/core/read-npm-package-readme.d.ts.map +1 -0
  96. package/esm/lib/core/read-npm-package-readme.js +81 -0
  97. package/esm/lib/core/shared-assets.d.ts +6 -0
  98. package/esm/lib/core/shared-assets.d.ts.map +1 -0
  99. package/esm/lib/core/shared-assets.js +46 -0
  100. package/esm/lib/core/types.d.ts +2 -0
  101. package/esm/lib/core/types.d.ts.map +1 -0
  102. package/esm/lib/core/types.js +2 -0
  103. package/esm/lib/core/utils.d.ts +12 -0
  104. package/esm/lib/core/utils.d.ts.map +1 -0
  105. package/esm/lib/core/utils.js +29 -0
  106. package/esm/lib/integrations/create-agent-session.d.ts +25 -0
  107. package/esm/lib/integrations/create-agent-session.d.ts.map +1 -0
  108. package/esm/lib/integrations/create-agent-session.js +36 -0
  109. package/esm/lib/integrations/key-value-credential-store.d.ts +21 -0
  110. package/esm/lib/integrations/key-value-credential-store.d.ts.map +1 -0
  111. package/esm/lib/integrations/key-value-credential-store.js +71 -0
  112. package/esm/lib/integrations/n8n-credential-store.d.ts +18 -0
  113. package/esm/lib/integrations/n8n-credential-store.d.ts.map +1 -0
  114. package/esm/lib/integrations/n8n-credential-store.js +62 -0
  115. package/esm/lib/mod.d.ts +23 -0
  116. package/esm/lib/mod.d.ts.map +1 -0
  117. package/esm/lib/mod.js +22 -0
  118. package/esm/lib/network/auth-client.d.ts +57 -0
  119. package/esm/lib/network/auth-client.d.ts.map +1 -0
  120. package/esm/lib/network/auth-client.js +210 -0
  121. package/esm/package.json +3 -0
  122. package/esm/skill/cli.d.ts +3 -0
  123. package/esm/skill/cli.d.ts.map +1 -0
  124. package/esm/skill/cli.js +321 -0
  125. package/package.json +45 -0
  126. package/presets/list_inbox.json +46 -0
  127. package/presets/reply.json +97 -0
  128. package/presets/send_mail.json +70 -0
  129. package/presets/send_mail_attachment.json +92 -0
  130. package/presets/send_mail_blob_attachment.json +74 -0
  131. package/shared/consts.json +11 -0
  132. package/shared/fixtures/pow_vectors.json +32 -0
  133. package/shared/help/fragments/inbox_cron_agent_prompt.md +1 -0
  134. package/shared/help/fragments/post_register_cron_reminder.md +5 -0
  135. package/shared/help/readme_stub.md +3 -0
  136. package/shared/help/topics/auth.md +8 -0
  137. package/shared/help/topics/cron.md +217 -0
  138. package/shared/help/topics/installation.md +35 -0
  139. package/shared/help/topics/jmap_cheatsheet.md +19 -0
  140. package/shared/help/topics/multi_account.md +9 -0
  141. package/shared/help/topics/overview.md +27 -0
  142. package/shared/help/topics/presets.md +12 -0
  143. package/shared/help/topics/tools.md +16 -0
  144. package/shared/help/topics/troubleshooting.md +6 -0
  145. package/shared/manifest.json +31 -0
  146. package/shared/messages/errors.json +68 -0
  147. package/shared/messages/hints.json +8 -0
  148. package/shared/presets/list_inbox.json +46 -0
  149. package/shared/presets/reply.json +97 -0
  150. package/shared/presets/send_mail.json +70 -0
  151. package/shared/presets/send_mail_attachment.json +92 -0
  152. package/shared/presets/send_mail_blob_attachment.json +74 -0
  153. package/shared/skill/SKILL.template.md +202 -0
  154. package/shared/skill/manifest.json +89 -0
@@ -0,0 +1,210 @@
1
+ // Thin HTTP client for auth-service (PoW challenge → session → capability).
2
+ //
3
+ // Encapsulates the full PoW challenge → session → capability flow so callers
4
+ // (integration tests, the future agent skill, etc.) don't have to reimplement scrypt grinding.
5
+ //
6
+ // The PoW digest is scrypt-based and uses the SAME salt the auth-service
7
+ // uses on the verify path (see services/auth-service/src/crypto.ts). The
8
+ // client must therefore be configured with that salt — there is no public
9
+ // hash function here, the salt is part of the protocol.
10
+ import { scrypt } from "node:crypto";
11
+ import { DEFAULT_POW_SCRYPT_SALT_HEX } from "../core/consts.js";
12
+ // Mirror services/auth-service/src/crypto.ts exactly. Changing any of these
13
+ // constants on either side breaks PoW interop.
14
+ const SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
15
+ const POW_HASH_BYTES = 64;
16
+ /** Thrown for any non-2xx HTTP response or malformed payload. */
17
+ export class AuthClientError extends Error {
18
+ status;
19
+ bodyText;
20
+ constructor(status, bodyText, message) {
21
+ super(message);
22
+ this.name = "AuthClientError";
23
+ this.status = status;
24
+ this.bodyText = bodyText;
25
+ }
26
+ }
27
+ export class AuthClient {
28
+ baseUrl;
29
+ scryptSaltHex;
30
+ constructor(options) {
31
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
32
+ this.scryptSaltHex = options.scryptSaltHex ?? DEFAULT_POW_SCRYPT_SALT_HEX;
33
+ }
34
+ /**
35
+ * Register a new inbox under `username`. Returns the freshly minted API key
36
+ * (the server only ever returns it once — the caller MUST persist it) and
37
+ * a session JWT.
38
+ */
39
+ async signup(username) {
40
+ const { challengeJWT, challenge, difficulty } = await this.fetchChallenge();
41
+ const { powHex, nonce } = await this.solvePoW(challenge, difficulty);
42
+ const { sessionJWT, data } = await this.postSession(challengeJWT, {
43
+ powHex,
44
+ nonce: nonce.toString(),
45
+ username,
46
+ });
47
+ if (typeof data.apiKey !== "string") {
48
+ throw new AuthClientError(200, JSON.stringify(data), "Signup response missing apiKey.");
49
+ }
50
+ return { apiKey: data.apiKey, sessionJWT };
51
+ }
52
+ /** Exchange an existing API key for a fresh session JWT. */
53
+ async login(apiKey) {
54
+ const { challengeJWT, challenge, difficulty } = await this.fetchChallenge();
55
+ const { powHex, nonce } = await this.solvePoW(challenge, difficulty);
56
+ const { sessionJWT } = await this.postSession(challengeJWT, {
57
+ powHex,
58
+ nonce: nonce.toString(),
59
+ apiKey,
60
+ });
61
+ return { sessionJWT };
62
+ }
63
+ /**
64
+ * Exchange a session JWT for a short-lived capability JWT (audience:
65
+ * api-service).
66
+ */
67
+ async renew(sessionJWT) {
68
+ const res = await fetch(`${this.baseUrl}/api/v1/capability`, {
69
+ method: "POST",
70
+ headers: { Authorization: `Bearer ${sessionJWT}` },
71
+ });
72
+ const text = await res.text();
73
+ if (!res.ok) {
74
+ throw new AuthClientError(res.status, text, `auth-service capability returned ${res.status}: ${text}`);
75
+ }
76
+ const capabilityJWT = readBearerToken(res.headers.get("Authorization"), "Capability response missing Authorization bearer token.");
77
+ return { capabilityJWT };
78
+ }
79
+ async fetchChallenge() {
80
+ const res = await fetch(`${this.baseUrl}/api/v1/challenge`, {
81
+ method: "POST",
82
+ });
83
+ const text = await res.text();
84
+ if (!res.ok) {
85
+ throw new AuthClientError(res.status, text, `auth-service challenge returned ${res.status}: ${text}`);
86
+ }
87
+ const challengeJWT = readBearerToken(res.headers.get("Authorization"), "Challenge response missing Authorization bearer token.");
88
+ const payload = decodeJwtPayload(challengeJWT);
89
+ if (typeof payload.jti !== "string" ||
90
+ typeof payload.difficulty !== "number") {
91
+ throw new AuthClientError(res.status, challengeJWT, "Challenge JWT payload is malformed (missing jti or difficulty).");
92
+ }
93
+ return {
94
+ challengeJWT,
95
+ challenge: payload.jti,
96
+ difficulty: payload.difficulty,
97
+ };
98
+ }
99
+ async postSession(challengeJWT, body) {
100
+ const res = await fetch(`${this.baseUrl}/api/v1/session`, {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ Authorization: `Bearer ${challengeJWT}`,
105
+ },
106
+ body: JSON.stringify(body),
107
+ });
108
+ const text = await res.text();
109
+ if (!res.ok) {
110
+ throw new AuthClientError(res.status, text, `auth-service session returned ${res.status}: ${text}`);
111
+ }
112
+ const sessionJWT = readBearerToken(res.headers.get("Authorization"), "Session response missing Authorization bearer token.");
113
+ let data = {};
114
+ if (text.trim().length > 0) {
115
+ try {
116
+ data = JSON.parse(text);
117
+ }
118
+ catch {
119
+ throw new AuthClientError(res.status, text, "auth-service session returned non-JSON body.");
120
+ }
121
+ }
122
+ return { sessionJWT, data };
123
+ }
124
+ async parseJsonOrThrow(res, endpoint) {
125
+ const text = await res.text();
126
+ if (!res.ok) {
127
+ throw new AuthClientError(res.status, text, `auth-service ${endpoint} returned ${res.status}: ${text}`);
128
+ }
129
+ try {
130
+ return JSON.parse(text);
131
+ }
132
+ catch {
133
+ throw new AuthClientError(res.status, text, `auth-service ${endpoint} returned non-JSON body.`);
134
+ }
135
+ }
136
+ /**
137
+ * Brute-force a PoW nonce. Mirrors `generatePow` in
138
+ * services/auth-service/src/crypto.ts: scrypt(`${challenge}:${nonce}`, salt,
139
+ * 64) until `difficulty` leading bits of the digest are zero.
140
+ *
141
+ * Expected work at the server's POW_DIFFICULTY=6 is ~2^6 = 64 attempts; well
142
+ * within the challenge JWT's 3-minute TTL.
143
+ */
144
+ async solvePoW(challenge, difficulty) {
145
+ let nonce = 0n;
146
+ while (true) {
147
+ const digest = await scryptHash(`${challenge}:${nonce}`, this.scryptSaltHex);
148
+ if (hasLeadingZeroBits(digest, difficulty)) {
149
+ return { powHex: bytesToHex(digest), nonce };
150
+ }
151
+ nonce++;
152
+ }
153
+ }
154
+ }
155
+ function scryptHash(data, salt) {
156
+ const bytes = new TextEncoder().encode(data);
157
+ return new Promise((resolve, reject) => {
158
+ scrypt(bytes, salt, POW_HASH_BYTES, SCRYPT_PARAMS, (err, derived) => {
159
+ if (err)
160
+ return reject(err);
161
+ resolve(new Uint8Array(derived));
162
+ });
163
+ });
164
+ }
165
+ function hasLeadingZeroBits(hash, bits) {
166
+ if (bits > hash.length * 8)
167
+ return false;
168
+ const fullBytes = Math.floor(bits / 8);
169
+ const remainingBits = bits % 8;
170
+ for (let i = 0; i < fullBytes; i++) {
171
+ if (hash[i] !== 0)
172
+ return false;
173
+ }
174
+ if (remainingBits > 0) {
175
+ const mask = (0xff << (8 - remainingBits)) & 0xff;
176
+ if ((hash[fullBytes] & mask) !== 0)
177
+ return false;
178
+ }
179
+ return true;
180
+ }
181
+ function bytesToHex(bytes) {
182
+ let hex = "";
183
+ for (let i = 0; i < bytes.length; i++) {
184
+ hex += bytes[i].toString(16).padStart(2, "0");
185
+ }
186
+ return hex;
187
+ }
188
+ function decodeJwtPayload(jwt) {
189
+ const parts = jwt.split(".");
190
+ if (parts.length < 2) {
191
+ throw new Error("Malformed JWT: expected at least 2 dot-separated segments.");
192
+ }
193
+ const payloadB64Url = parts[1];
194
+ const padLen = (4 - (payloadB64Url.length % 4)) % 4;
195
+ const base64 = payloadB64Url
196
+ .replace(/-/g, "+")
197
+ .replace(/_/g, "/")
198
+ .padEnd(payloadB64Url.length + padLen, "=");
199
+ return JSON.parse(atob(base64));
200
+ }
201
+ function readBearerToken(headerValue, missingError) {
202
+ if (!headerValue) {
203
+ throw new Error(missingError);
204
+ }
205
+ const match = /^\s*Bearer\s+(.+?)\s*$/i.exec(headerValue);
206
+ if (!match || !match[1]) {
207
+ throw new Error("Authorization header must use Bearer scheme.");
208
+ }
209
+ return match[1];
210
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "../_dnt.polyfills.js";
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/skill/cli.ts"],"names":[],"mappings":";AAEA,OAAO,sBAAsB,CAAC"}
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+ // Atomic Mail AgentSkill — register | jmap_request | help
3
+ import "../_dnt.polyfills.js";
4
+ import process from "node:process";
5
+ import { parseArgs } from "node:util";
6
+ import { AgentSession, DEFAULT_API_URL, DEFAULT_AUTH_URL, DEFAULT_JMAP_USING, DEFAULT_POW_SCRYPT_SALT_HEX, defaultFilesFromOutDir, expandCredentialDirInput, getHelp, parseUserVarsJson, persistLoginWithApiKey, readCredentials, readOpsFile, runJmapRequest, sharedError, } from "../lib/mod.js";
7
+ const USAGE = `Atomic Mail — AgentSkill
8
+
9
+ Usage:
10
+ atomicmail <command> [options]
11
+
12
+ Commands:
13
+ register PoW signup or login with API key (writes credentials)
14
+ jmap_request Send a JMAP batch (inline --ops or --ops-file; optional --attachment)
15
+ help Full documentation [--topic TOPIC] (topic readme = built-in stub)
16
+
17
+ Examples:
18
+ atomicmail register --username alice
19
+ atomicmail register --api-key UUID
20
+ atomicmail jmap_request --ops-file list_inbox.json
21
+ atomicmail jmap_request --credentials-dir ./.atomic-mail --ops-file send.json --vars '{"TO":"a@b.com","SUBJECT":"Hi"}'
22
+ atomicmail jmap_request --ops-file send_mail_blob_attachment.json --attachment ./notes.txt --vars '{"TO":"self@example.com","SUBJECT":"File","BODY":"See attach."}'
23
+ atomicmail help --topic presets
24
+ atomicmail help --topic readme
25
+
26
+ Call atomicmail help early and often — topics match this install; prefer help
27
+ over guessing JMAP or preset details.
28
+
29
+ Run atomicmail <command> --help for command-specific flags.
30
+ `;
31
+ function exitUsage(code = 0) {
32
+ process.stdout.write(USAGE);
33
+ process.exit(code);
34
+ }
35
+ function fail(message, code = 1) {
36
+ process.stderr.write(`Error: ${message}\n`);
37
+ process.exit(code);
38
+ }
39
+ async function cmdRegister(argv) {
40
+ let parsed;
41
+ try {
42
+ parsed = parseArgs({
43
+ args: argv,
44
+ options: {
45
+ "auth-url": { type: "string" },
46
+ "api-url": { type: "string" },
47
+ "scrypt-salt": { type: "string" },
48
+ username: { type: "string" },
49
+ "api-key": { type: "string" },
50
+ "credentials-dir": { type: "string" },
51
+ forced: { type: "boolean" },
52
+ quiet: { type: "boolean" },
53
+ help: { type: "boolean", short: "h" },
54
+ },
55
+ strict: true,
56
+ allowPositionals: false,
57
+ });
58
+ }
59
+ catch (err) {
60
+ fail(err.message, 2);
61
+ }
62
+ if (parsed.values.help) {
63
+ process.stdout.write(`Usage: atomicmail register [OPTIONS]
64
+
65
+ Register a new inbox (--username) or log in with an existing API key (--api-key).
66
+
67
+ Options:
68
+ --auth-url URL Auth-service base URL [env: ATOMIC_MAIL_AUTH_URL, default: https://auth.atomicmail.ai]
69
+ --api-url URL API / JMAP base URL [env: ATOMIC_MAIL_API_URL, default: https://api.atomicmail.ai]
70
+ --scrypt-salt SALT PoW salt override [env: ATOMIC_MAIL_SCRYPT_SALT]
71
+ --username NAME New account (5–21 characters; mutually exclusive with --api-key)
72
+ --api-key KEY Existing API key (mutually exclusive with --username)
73
+ --credentials-dir DIR Credential directory (default: ~/.atomicmail)
74
+ --forced Allow replacing existing credentials with a new account
75
+ --quiet Less stderr output
76
+ --help, -h This message
77
+
78
+ After register, arrange hourly inbox polling per your runtime (atomicmail help
79
+ --topic cron): native cron hosts schedule an AGENT turn with list_inbox.json;
80
+ no-native-cron hosts ask the operator or remind manual fetch — do not work
81
+ around with OS crontab or cross-platform scheduling. Do NOT cron atomicmail
82
+ jmap_request alone.
83
+ `);
84
+ process.exit(0);
85
+ }
86
+ const env = process.env;
87
+ const authUrl = parsed.values["auth-url"] ??
88
+ env.ATOMIC_MAIL_AUTH_URL ?? DEFAULT_AUTH_URL;
89
+ const apiUrl = parsed.values["api-url"] ??
90
+ env.ATOMIC_MAIL_API_URL ?? DEFAULT_API_URL;
91
+ const scryptSalt = parsed.values["scrypt-salt"] ??
92
+ env.ATOMIC_MAIL_SCRYPT_SALT ?? DEFAULT_POW_SCRYPT_SALT_HEX;
93
+ const dir = parsed.values["credentials-dir"];
94
+ const credentialDir = expandCredentialDirInput(dir);
95
+ const username = parsed.values.username;
96
+ const apiKey = parsed.values["api-key"];
97
+ if (!!username === !!apiKey) {
98
+ fail("Provide exactly one of --username (new account) or --api-key (login).", 2);
99
+ }
100
+ const files = defaultFilesFromOutDir(credentialDir);
101
+ const log = (msg) => {
102
+ if (!parsed.values.quiet)
103
+ process.stderr.write(msg + "\n");
104
+ };
105
+ if (username) {
106
+ log(`Registering "${username}"...`);
107
+ const session = await AgentSession.create({
108
+ authUrl,
109
+ apiUrl,
110
+ scryptSalt,
111
+ credentialDir,
112
+ files,
113
+ });
114
+ const result = await session.register(username, {
115
+ forced: parsed.values.forced === true,
116
+ });
117
+ log(`Wrote credentials under ${credentialDir}`);
118
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
119
+ return;
120
+ }
121
+ log("Logging in with API key...");
122
+ const { inboxId } = await persistLoginWithApiKey({
123
+ authUrl,
124
+ apiUrl,
125
+ scryptSalt,
126
+ apiKey: apiKey,
127
+ files,
128
+ });
129
+ log(`Wrote ${files.credentialsFile}`);
130
+ process.stdout.write(JSON.stringify({ inboxId }, null, 2) + "\n");
131
+ }
132
+ async function cmdJmapRequest(argv) {
133
+ let parsed;
134
+ try {
135
+ parsed = parseArgs({
136
+ args: argv,
137
+ options: {
138
+ "credentials-dir": { type: "string" },
139
+ "credentials-file": { type: "string" },
140
+ "session-file": { type: "string" },
141
+ "capability-file": { type: "string" },
142
+ ops: { type: "string" },
143
+ "ops-file": { type: "string" },
144
+ using: { type: "string" },
145
+ "dry-run": { type: "boolean" },
146
+ vars: { type: "string" },
147
+ attachment: { type: "string", multiple: true },
148
+ "attachment-path-base": { type: "string" },
149
+ help: { type: "boolean", short: "h" },
150
+ },
151
+ strict: true,
152
+ allowPositionals: false,
153
+ });
154
+ }
155
+ catch (err) {
156
+ fail(err.message, 2);
157
+ }
158
+ if (parsed.values.help) {
159
+ process.stdout.write(`Usage: atomicmail jmap_request [OPTIONS]
160
+
161
+ Send a JMAP request using saved credentials.
162
+
163
+ Options:
164
+ --credentials-dir DIR Directory with credentials.json + JWTs (default: ~/.atomicmail)
165
+ --credentials-file PATH Override credentials.json path
166
+ --session-file PATH Override session.jwt path
167
+ --capability-file PATH Override capability.jwt path
168
+ --ops JSON Inline JMAP JSON (methodCalls or envelope)
169
+ --ops-file PATH Preset file ($VAR_NAME placeholders supported)
170
+ --vars JSON JSON object { VAR_NAME: string } for $VAR_NAME in ops / ops-file
171
+ --attachment PATH Repeatable; each file is RFC 8620–uploaded before JMAP (injects $ATTACHMENT_N_*)
172
+ --attachment-path-base DIR Base for relative --attachment paths (default: cwd)
173
+ --using LIST Comma-separated capability URNs (optional)
174
+ --dry-run Print resolved request only (not compatible with --attachment)
175
+ --help, -h This message
176
+ `);
177
+ process.exit(0);
178
+ }
179
+ const dir = parsed.values["credentials-dir"];
180
+ const credentialDir = expandCredentialDirInput(dir);
181
+ const defaults = defaultFilesFromOutDir(credentialDir);
182
+ const credentialsFile = parsed.values["credentials-file"] ??
183
+ defaults.credentialsFile;
184
+ const sessionFile = parsed.values["session-file"] ??
185
+ defaults.sessionFile;
186
+ const capabilityFile = parsed.values["capability-file"] ??
187
+ defaults.capabilityFile;
188
+ const ops = parsed.values.ops;
189
+ const opsFile = parsed.values["ops-file"];
190
+ if (ops && opsFile) {
191
+ fail(sharedError("cli_ops_mutually_exclusive"), 2);
192
+ }
193
+ if (!ops && !opsFile) {
194
+ fail(sharedError("cli_ops_required"), 2);
195
+ }
196
+ const rawAttachments = parsed.values.attachment;
197
+ const attachmentPaths = rawAttachments === undefined
198
+ ? []
199
+ : Array.isArray(rawAttachments)
200
+ ? rawAttachments
201
+ : [rawAttachments];
202
+ if (parsed.values["dry-run"] === true && attachmentPaths.length > 0) {
203
+ fail(sharedError("cli_dry_run_with_attachment"), 2);
204
+ }
205
+ const usingFlag = parsed.values.using;
206
+ const defaultUsing = usingFlag
207
+ ? usingFlag.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
208
+ : [...DEFAULT_JMAP_USING];
209
+ let userVars;
210
+ const varsFlag = parsed.values.vars;
211
+ if (varsFlag !== undefined) {
212
+ try {
213
+ userVars = parseUserVarsJson(varsFlag);
214
+ }
215
+ catch (err) {
216
+ fail(err instanceof Error ? err.message : String(err), 2);
217
+ }
218
+ }
219
+ const creds = await readCredentials(credentialsFile);
220
+ const files = {
221
+ credentialsFile,
222
+ sessionFile,
223
+ capabilityFile,
224
+ };
225
+ const session = await AgentSession.create({
226
+ authUrl: creds.authUrl,
227
+ apiUrl: creds.apiUrl,
228
+ scryptSalt: creds.scryptSalt,
229
+ apiKey: creds.apiKey,
230
+ inboxId: creds.inboxId,
231
+ credentialDir,
232
+ files,
233
+ });
234
+ let raw;
235
+ let sourceLabel;
236
+ if (opsFile) {
237
+ try {
238
+ raw = await readOpsFile(credentialDir, opsFile);
239
+ }
240
+ catch (err) {
241
+ fail(`Could not read --ops-file: ${err.message}`, 2);
242
+ }
243
+ sourceLabel = `ops_file '${opsFile}'`;
244
+ }
245
+ else {
246
+ raw = ops;
247
+ sourceLabel = "ops";
248
+ }
249
+ const attachmentPathBase = parsed.values["attachment-path-base"];
250
+ // Same JMAP path as MCP: built-in `$INBOX` is normalized to a full mailbox
251
+ // address via shared `runJmapRequest` (see `inboxIdToMailboxEmail`).
252
+ const { ok, status, bodyText } = await runJmapRequest({
253
+ session,
254
+ opsJson: raw,
255
+ defaultUsing,
256
+ sourceLabel,
257
+ dryRun: parsed.values["dry-run"] === true,
258
+ vars: userVars,
259
+ attachments: attachmentPaths.length > 0
260
+ ? attachmentPaths.map((path) => ({ path }))
261
+ : undefined,
262
+ attachmentPathBase,
263
+ });
264
+ if (!ok) {
265
+ fail(`JMAP request failed (HTTP ${status}): ${bodyText}`, 1);
266
+ }
267
+ process.stdout.write(bodyText.endsWith("\n") ? bodyText : bodyText + "\n");
268
+ }
269
+ async function cmdHelp(argv) {
270
+ let parsed;
271
+ try {
272
+ parsed = parseArgs({
273
+ args: argv,
274
+ options: {
275
+ topic: { type: "string" },
276
+ help: { type: "boolean", short: "h" },
277
+ },
278
+ strict: true,
279
+ allowPositionals: false,
280
+ });
281
+ }
282
+ catch (err) {
283
+ fail(err.message, 2);
284
+ }
285
+ if (parsed.values.help) {
286
+ process.stdout.write(`Usage: atomicmail help [--topic TOPIC]
287
+
288
+ Topics include: overview, installation, auth, jmap_cheatsheet, tools, presets, troubleshooting, readme.
289
+ Topic readme prints the built-in SKILL stub.
290
+ `);
291
+ process.exit(0);
292
+ }
293
+ const topic = parsed.values.topic;
294
+ process.stdout.write(await getHelp(topic, "skill") + "\n");
295
+ }
296
+ async function main() {
297
+ const argv = process.argv.slice(2);
298
+ if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
299
+ exitUsage(0);
300
+ }
301
+ const cmd = argv[0];
302
+ const rest = argv.slice(1);
303
+ switch (cmd) {
304
+ case "register":
305
+ await cmdRegister(rest);
306
+ break;
307
+ case "jmap_request":
308
+ await cmdJmapRequest(rest);
309
+ break;
310
+ case "help":
311
+ await cmdHelp(rest);
312
+ break;
313
+ default:
314
+ process.stderr.write(`Unknown command: ${cmd}\n\n`);
315
+ process.stdout.write(USAGE);
316
+ process.exit(2);
317
+ }
318
+ }
319
+ main().catch((err) => {
320
+ fail(err instanceof Error ? err.message : String(err));
321
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@atomicmail/agent-skill-openclaw",
3
+ "version": "0.3.24",
4
+ "description": "Atomic Mail AgentSkill — register, jmap_request, and help CLI for AI agents. (openclaw install channel)",
5
+ "keywords": [
6
+ "atomic-mail",
7
+ "atomicmail",
8
+ "agentskills",
9
+ "agent",
10
+ "ai",
11
+ "jmap",
12
+ "esp",
13
+ "email",
14
+ "mcp",
15
+ "proof-of-work",
16
+ "openclaw"
17
+ ],
18
+ "author": "Atomic Mail",
19
+ "homepage": "https://atomicmail.ai?utm_source=npm&utm_medium=package&utm_campaign=openclaw",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Atomic-Mail/atomic-mail-agentic.git"
23
+ },
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/Atomic-Mail/atomic-mail-agentic/issues"
27
+ },
28
+ "scripts": {},
29
+ "bin": {
30
+ "atomicmail": "./esm/skill/cli.js"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "atomicmail": {
36
+ "channel": "openclaw"
37
+ },
38
+ "engines": {
39
+ "node": ">=20"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.12.0"
43
+ },
44
+ "_generatedBy": "dnt@dev"
45
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "using": [
3
+ "urn:ietf:params:jmap:core",
4
+ "urn:ietf:params:jmap:mail"
5
+ ],
6
+ "methodCalls": [
7
+ [
8
+ "Email/query",
9
+ {
10
+ "accountId": "$ACCOUNT_ID",
11
+ "filter": {
12
+ "inMailbox": "$INBOX_MAILBOX_ID"
13
+ },
14
+ "sort": [
15
+ {
16
+ "property": "receivedAt",
17
+ "isAscending": false
18
+ }
19
+ ],
20
+ "limit": 50
21
+ },
22
+ "q0"
23
+ ],
24
+ [
25
+ "Email/get",
26
+ {
27
+ "accountId": "$ACCOUNT_ID",
28
+ "#ids": {
29
+ "resultOf": "q0",
30
+ "name": "Email/query",
31
+ "path": "/ids"
32
+ },
33
+ "properties": [
34
+ "id",
35
+ "threadId",
36
+ "receivedAt",
37
+ "from",
38
+ "to",
39
+ "subject",
40
+ "preview"
41
+ ]
42
+ },
43
+ "g0"
44
+ ]
45
+ ]
46
+ }