@atomicmail/mcp 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -187
- package/esm/_dnt.polyfills.d.ts +101 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +127 -0
- package/esm/lib/agent/auth/agent-auth-http.d.ts +26 -0
- package/esm/lib/agent/auth/agent-auth-http.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-auth-http.js +76 -0
- package/esm/lib/agent/auth/agent-jwt.d.ts +14 -0
- package/esm/lib/agent/auth/agent-jwt.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-jwt.js +29 -0
- package/esm/lib/agent/auth/agent-pow.d.ts +5 -0
- package/esm/lib/agent/auth/agent-pow.d.ts.map +1 -0
- package/esm/lib/agent/auth/agent-pow.js +49 -0
- package/esm/lib/agent/jmap/agent-help-content.d.ts +4 -0
- package/esm/lib/agent/jmap/agent-help-content.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-help-content.js +244 -0
- package/esm/lib/agent/jmap/agent-jmap.d.ts +49 -0
- package/esm/lib/agent/jmap/agent-jmap.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-jmap.js +174 -0
- package/esm/lib/agent/jmap/agent-vars.d.ts +23 -0
- package/esm/lib/agent/jmap/agent-vars.d.ts.map +1 -0
- package/esm/lib/agent/jmap/agent-vars.js +65 -0
- package/esm/{mcp/src/credentials.d.ts → lib/agent/session/agent-credentials-store.d.ts} +3 -2
- package/esm/lib/agent/session/agent-credentials-store.d.ts.map +1 -0
- package/esm/{mcp/src/credentials.js → lib/agent/session/agent-credentials-store.js} +19 -16
- package/esm/lib/agent/session/agent-resolve-config.d.ts +24 -0
- package/esm/lib/agent/session/agent-resolve-config.d.ts.map +1 -0
- package/esm/lib/agent/session/agent-resolve-config.js +70 -0
- package/esm/lib/agent/session/agent-session.d.ts +62 -0
- package/esm/lib/agent/session/agent-session.d.ts.map +1 -0
- package/esm/lib/agent/session/agent-session.js +206 -0
- package/esm/lib/core/consts.d.ts.map +1 -0
- package/esm/lib/core/types.d.ts +2 -0
- package/esm/lib/core/types.d.ts.map +1 -0
- package/esm/lib/core/types.js +1 -0
- package/esm/lib/core/utils.d.ts +10 -0
- package/esm/lib/core/utils.d.ts.map +1 -0
- package/esm/lib/core/utils.js +28 -0
- package/esm/lib/mod.d.ts +14 -0
- package/esm/lib/mod.d.ts.map +1 -0
- package/esm/lib/mod.js +13 -0
- package/esm/lib/network/auth-client.d.ts +57 -0
- package/esm/lib/network/auth-client.d.ts.map +1 -0
- package/esm/lib/network/auth-client.js +188 -0
- package/esm/mcp/main.d.ts +3 -0
- package/esm/mcp/main.d.ts.map +1 -0
- package/esm/mcp/main.js +86 -0
- package/esm/mcp/tools/help.d.ts +3 -0
- package/esm/mcp/tools/help.d.ts.map +1 -0
- package/esm/mcp/tools/help.js +22 -0
- package/esm/mcp/{src/tools → tools}/jmap.d.ts +2 -2
- package/esm/mcp/tools/jmap.d.ts.map +1 -0
- package/esm/mcp/tools/jmap.js +115 -0
- package/esm/mcp/{src/tools → tools}/register.d.ts +2 -2
- package/esm/mcp/tools/register.d.ts.map +1 -0
- package/esm/mcp/tools/register.js +43 -0
- package/package.json +5 -5
- package/presets/list_inbox.json +39 -0
- package/presets/reply.json +75 -0
- package/presets/send_mail.json +42 -0
- package/esm/lib/src/consts.d.ts.map +0 -1
- package/esm/mcp/src/auth-session.d.ts +0 -88
- package/esm/mcp/src/auth-session.d.ts.map +0 -1
- package/esm/mcp/src/auth-session.js +0 -378
- package/esm/mcp/src/credentials.d.ts.map +0 -1
- package/esm/mcp/src/docs-content.d.ts +0 -4
- package/esm/mcp/src/docs-content.d.ts.map +0 -1
- package/esm/mcp/src/docs-content.js +0 -405
- package/esm/mcp/src/main.d.ts +0 -3
- package/esm/mcp/src/main.d.ts.map +0 -1
- package/esm/mcp/src/main.js +0 -116
- package/esm/mcp/src/tools/docs.d.ts +0 -3
- package/esm/mcp/src/tools/docs.d.ts.map +0 -1
- package/esm/mcp/src/tools/docs.js +0 -22
- package/esm/mcp/src/tools/jmap.d.ts.map +0 -1
- package/esm/mcp/src/tools/jmap.js +0 -202
- package/esm/mcp/src/tools/register.d.ts.map +0 -1
- package/esm/mcp/src/tools/register.js +0 -79
- /package/esm/lib/{src → core}/consts.d.ts +0 -0
- /package/esm/lib/{src → core}/consts.js +0 -0
|
@@ -0,0 +1,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 @@
|
|
|
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 @@
|
|
|
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
|
-
|
|
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
|