aamp-openclaw-plugin 0.1.40 → 0.1.43
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 +8 -0
- package/bin/aamp-openclaw-plugin.mjs +119 -2
- package/dist/file-store.js +11 -0
- package/dist/file-store.js.map +2 -2
- package/dist/index.js +205 -73
- package/dist/index.js.map +3 -3
- package/package.json +5 -2
- package/skills/SKILL.md +19 -0
- package/dist/chunk-ORTVVAMV.js +0 -82
- package/dist/chunk-ORTVVAMV.js.map +0 -7
- package/dist/chunk-W4C7IUCH.js +0 -81
- package/dist/chunk-W4C7IUCH.js.map +0 -7
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ OpenClaw plugin that gives an OpenClaw agent an AAMP mailbox identity.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
Requires OpenClaw `>=2026.3.22`. Both `openclaw plugins install` and
|
|
8
|
+
`npx aamp-openclaw-plugin init` stop before installation when the detected
|
|
9
|
+
OpenClaw version is older.
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
12
|
npm install aamp-openclaw-plugin
|
|
9
13
|
```
|
|
@@ -32,8 +36,12 @@ paired sender policy file, then consumes the code. The plugin replies with
|
|
|
32
36
|
You can generate a fresh pairing QR code later without restarting OpenClaw:
|
|
33
37
|
|
|
34
38
|
- Ask the agent to use the `aamp_pairing_code` tool.
|
|
39
|
+
- Ask naturally, for example "发对接码", "生成配对码", or "show the connect QR".
|
|
35
40
|
- Or run the `/aamp-pair` command in OpenClaw.
|
|
36
41
|
|
|
42
|
+
The response includes both the QR target (`https://meshmail.ai/pair?...`) and
|
|
43
|
+
the raw `aamp://connect?...` URL for copy/paste pairing.
|
|
44
|
+
|
|
37
45
|
## Build
|
|
38
46
|
|
|
39
47
|
```bash
|
|
@@ -15,6 +15,8 @@ const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
|
|
|
15
15
|
const DEFAULT_CREDENTIALS_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json'
|
|
16
16
|
const DEFAULT_PAIRING_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json'
|
|
17
17
|
const DEFAULT_SENDER_POLICIES_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json'
|
|
18
|
+
export const MIN_OPENCLAW_VERSION = '2026.3.22'
|
|
19
|
+
export const MIN_OPENCLAW_VERSION_RANGE = `>=${MIN_OPENCLAW_VERSION}`
|
|
18
20
|
const CODING_TOOL_ALLOWLIST = [
|
|
19
21
|
'read',
|
|
20
22
|
'write',
|
|
@@ -195,11 +197,122 @@ async function renderTerminalQr(value) {
|
|
|
195
197
|
}
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
function pairingUrlToWebUrl(connectUrl) {
|
|
201
|
+
const parsed = new URL(connectUrl)
|
|
202
|
+
const url = new URL('https://meshmail.ai/pair')
|
|
203
|
+
for (const [key, value] of parsed.searchParams) {
|
|
204
|
+
url.searchParams.set(key, value)
|
|
205
|
+
}
|
|
206
|
+
return url.toString()
|
|
207
|
+
}
|
|
208
|
+
|
|
198
209
|
export function normalizeBaseUrl(url) {
|
|
199
210
|
if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
|
|
200
211
|
return `https://${url.replace(/\/$/, '')}`
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
export function parseOpenClawVersion(outputText) {
|
|
215
|
+
const match = String(outputText ?? '').match(/\b(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)\b/)
|
|
216
|
+
return match?.[1] ?? null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseVersionParts(version) {
|
|
220
|
+
const [withoutBuild] = String(version ?? '').split('+', 1)
|
|
221
|
+
const prereleaseIndex = withoutBuild.indexOf('-')
|
|
222
|
+
const core = prereleaseIndex === -1 ? withoutBuild : withoutBuild.slice(0, prereleaseIndex)
|
|
223
|
+
const prereleaseText = prereleaseIndex === -1 ? '' : withoutBuild.slice(prereleaseIndex + 1)
|
|
224
|
+
const numbers = core.split('.').map((part) => Number(part))
|
|
225
|
+
if (numbers.length !== 3 || numbers.some((part) => !Number.isInteger(part) || part < 0)) {
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
const prerelease = prereleaseText ? prereleaseText.split('.') : []
|
|
229
|
+
return { numbers, prerelease }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function comparePrereleaseIdentifier(left, right) {
|
|
233
|
+
const leftNumeric = /^\d+$/.test(left)
|
|
234
|
+
const rightNumeric = /^\d+$/.test(right)
|
|
235
|
+
if (leftNumeric && rightNumeric) return Number(left) - Number(right)
|
|
236
|
+
if (leftNumeric) return -1
|
|
237
|
+
if (rightNumeric) return 1
|
|
238
|
+
return left.localeCompare(right)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function compareOpenClawVersions(leftVersion, rightVersion) {
|
|
242
|
+
const left = parseVersionParts(leftVersion)
|
|
243
|
+
const right = parseVersionParts(rightVersion)
|
|
244
|
+
if (!left || !right) {
|
|
245
|
+
throw new Error(`Invalid OpenClaw version comparison: ${leftVersion} vs ${rightVersion}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (let idx = 0; idx < 3; idx += 1) {
|
|
249
|
+
const diff = left.numbers[idx] - right.numbers[idx]
|
|
250
|
+
if (diff !== 0) return diff
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0
|
|
254
|
+
if (left.prerelease.length === 0) return 1
|
|
255
|
+
if (right.prerelease.length === 0) return -1
|
|
256
|
+
|
|
257
|
+
const maxLength = Math.max(left.prerelease.length, right.prerelease.length)
|
|
258
|
+
for (let idx = 0; idx < maxLength; idx += 1) {
|
|
259
|
+
const leftPart = left.prerelease[idx]
|
|
260
|
+
const rightPart = right.prerelease[idx]
|
|
261
|
+
if (leftPart === undefined) return -1
|
|
262
|
+
if (rightPart === undefined) return 1
|
|
263
|
+
const diff = comparePrereleaseIdentifier(leftPart, rightPart)
|
|
264
|
+
if (diff !== 0) return diff
|
|
265
|
+
}
|
|
266
|
+
return 0
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function checkOpenClawVersion(currentVersion, minimumVersion = MIN_OPENCLAW_VERSION) {
|
|
270
|
+
const comparison = compareOpenClawVersions(currentVersion, minimumVersion)
|
|
271
|
+
return {
|
|
272
|
+
ok: comparison >= 0,
|
|
273
|
+
currentVersion,
|
|
274
|
+
minimumVersion,
|
|
275
|
+
range: `>=${minimumVersion}`,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function getCurrentOpenClawVersion() {
|
|
280
|
+
const result = spawnSync('openclaw', ['--version'], {
|
|
281
|
+
encoding: 'utf-8',
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (result.error) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`OpenClaw CLI was not found. Install OpenClaw ${MIN_OPENCLAW_VERSION_RANGE} or newer before installing ${PLUGIN_ID}.`,
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (result.status !== 0) {
|
|
291
|
+
const reason = (result.stderr || result.stdout || `exit code ${result.status}`).trim()
|
|
292
|
+
throw new Error(`Could not detect OpenClaw version: ${reason}`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const version = parseOpenClawVersion(`${result.stdout ?? ''}\n${result.stderr ?? ''}`)
|
|
296
|
+
if (!version) {
|
|
297
|
+
throw new Error(`Could not parse OpenClaw version from: ${(result.stdout || result.stderr || '').trim()}`)
|
|
298
|
+
}
|
|
299
|
+
return version
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function assertOpenClawVersionSupported(currentVersion = getCurrentOpenClawVersion()) {
|
|
303
|
+
const check = checkOpenClawVersion(currentVersion)
|
|
304
|
+
if (check.ok) return check
|
|
305
|
+
|
|
306
|
+
throw new Error(
|
|
307
|
+
[
|
|
308
|
+
`${PLUGIN_ID} requires OpenClaw ${check.range}.`,
|
|
309
|
+
`Detected OpenClaw ${check.currentVersion}.`,
|
|
310
|
+
'Please upgrade OpenClaw, then rerun this installer.',
|
|
311
|
+
'Example: npm install -g openclaw@latest',
|
|
312
|
+
].join('\n'),
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
203
316
|
export function ensurePluginConfig(config, pluginConfig, options = {}) {
|
|
204
317
|
const next = config && typeof config === 'object' ? structuredClone(config) : {}
|
|
205
318
|
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
@@ -508,6 +621,7 @@ function printHelp() {
|
|
|
508
621
|
}
|
|
509
622
|
|
|
510
623
|
export async function runInit() {
|
|
624
|
+
const openClawVersionCheck = assertOpenClawVersionSupported()
|
|
511
625
|
const configPath = resolveOpenClawConfigPath()
|
|
512
626
|
const existing = readJsonFile(configPath)
|
|
513
627
|
const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
|
|
@@ -530,6 +644,7 @@ export async function runInit() {
|
|
|
530
644
|
const rl = createInterface({ input, output })
|
|
531
645
|
try {
|
|
532
646
|
output.write('AAMP OpenClaw Plugin Setup\n\n')
|
|
647
|
+
output.write(`Detected OpenClaw ${openClawVersionCheck.currentVersion} (${MIN_OPENCLAW_VERSION_RANGE} required)\n\n`)
|
|
533
648
|
|
|
534
649
|
if (previousConfig) {
|
|
535
650
|
output.write(
|
|
@@ -647,7 +762,8 @@ export async function runInit() {
|
|
|
647
762
|
const pairing = identityResult.email
|
|
648
763
|
? createPairingFile({ email: identityResult.email, pairingFile: DEFAULT_PAIRING_FILE })
|
|
649
764
|
: null
|
|
650
|
-
const
|
|
765
|
+
const webPairingUrl = pairing ? pairingUrlToWebUrl(pairing.connectUrl) : ''
|
|
766
|
+
const qr = webPairingUrl ? await renderTerminalQr(webPairingUrl) : ''
|
|
651
767
|
|
|
652
768
|
const restartResult = restartGateway()
|
|
653
769
|
|
|
@@ -680,7 +796,8 @@ export async function runInit() {
|
|
|
680
796
|
restartResult.ok
|
|
681
797
|
? 'Plugin changes should now be active.'
|
|
682
798
|
: 'Please restart the OpenClaw gateway manually for the plugin changes to take effect.',
|
|
683
|
-
pairing ? `Pairing
|
|
799
|
+
pairing ? `Pairing link (expires ${pairing.expiresAt}): ${webPairingUrl}` : '',
|
|
800
|
+
pairing ? `Pairing URL: ${pairing.connectUrl}` : '',
|
|
684
801
|
qr ? `\n${qr}` : '',
|
|
685
802
|
'',
|
|
686
803
|
].join('\n'),
|
package/dist/file-store.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
+
var DEFAULT_PAIRING_WEB_URL = "https://meshmail.ai/pair";
|
|
6
7
|
function defaultCredentialsPath() {
|
|
7
8
|
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
8
9
|
}
|
|
@@ -76,6 +77,14 @@ function createPairingCode(params) {
|
|
|
76
77
|
writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
|
|
77
78
|
return state;
|
|
78
79
|
}
|
|
80
|
+
function pairingUrlToWebUrl(connectUrl) {
|
|
81
|
+
const parsed = new URL(connectUrl);
|
|
82
|
+
const url = new URL(DEFAULT_PAIRING_WEB_URL);
|
|
83
|
+
for (const [key, value] of parsed.searchParams) {
|
|
84
|
+
url.searchParams.set(key, value);
|
|
85
|
+
}
|
|
86
|
+
return url.toString();
|
|
87
|
+
}
|
|
79
88
|
function consumePairingCode(params) {
|
|
80
89
|
const resolved = params.file ?? defaultPairingPath();
|
|
81
90
|
if (!existsSync(resolved))
|
|
@@ -132,6 +141,7 @@ function writeBinaryFile(path, content) {
|
|
|
132
141
|
writeFileSync(path, content);
|
|
133
142
|
}
|
|
134
143
|
export {
|
|
144
|
+
DEFAULT_PAIRING_WEB_URL,
|
|
135
145
|
addPairedSenderPolicy,
|
|
136
146
|
consumePairingCode,
|
|
137
147
|
createPairingCode,
|
|
@@ -143,6 +153,7 @@ export {
|
|
|
143
153
|
loadCachedIdentity,
|
|
144
154
|
loadPairedSenderPolicies,
|
|
145
155
|
loadTaskState,
|
|
156
|
+
pairingUrlToWebUrl,
|
|
146
157
|
readBinaryFile,
|
|
147
158
|
saveCachedIdentity,
|
|
148
159
|
saveTaskState,
|
package/dist/file-store.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/file-store.ts"],
|
|
4
|
-
"sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: string[]\n}\n\nexport interface DispatchContextRules {\n [key: string]: string[]\n}\n\nexport interface PairingCodeState {\n mailbox: string\n pairCode: string\n expiresAt: string\n connectUrl: string\n consumedAt?: string\n}\n\nexport interface PairedSenderPolicy {\n sender: string\n dispatchContextRules: DispatchContextRules\n pairedAt: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function defaultTaskStatePath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.task-state.json')\n}\n\nexport function defaultPairingPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.pairing.json')\n}\n\nexport function defaultSenderPoliciesPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.sender-policies.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword) return null\n return {\n email: parsed.email,\n mailboxToken: parsed.mailboxToken,\n smtpPassword: parsed.smtpPassword,\n }\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n email: identity.email,\n mailboxToken: identity.mailboxToken,\n smtpPassword: identity.smtpPassword,\n }, null, 2), 'utf-8')\n}\n\nexport function loadTaskState(file?: string): CachedTaskState {\n const resolved = file ?? defaultTaskStatePath()\n if (!existsSync(resolved)) return { terminalTaskIds: [] }\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as CachedTaskState\n return {\n terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : [],\n }\n } catch {\n return { terminalTaskIds: [] }\n }\n}\n\nexport function saveTaskState(state: CachedTaskState, file?: string): void {\n const resolved = file ?? defaultTaskStatePath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n terminalTaskIds: state.terminalTaskIds ?? [],\n }, null, 2), 'utf-8')\n}\n\nexport function createPairingCode(params: {\n mailbox: string\n file?: string\n ttlSeconds?: number\n}): PairingCodeState {\n const pairCode = randomBytes(6).toString('base64url')\n const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1000).toISOString()\n const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`\n const state: PairingCodeState = {\n mailbox: params.mailbox.toLowerCase(),\n pairCode,\n expiresAt,\n connectUrl,\n }\n const resolved = params.file ?? defaultPairingPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(state, null, 2), 'utf-8')\n return state\n}\n\nexport function consumePairingCode(params: {\n mailbox: string\n pairCode: string\n file?: string\n}): PairingCodeState | null {\n const resolved = params.file ?? defaultPairingPath()\n if (!existsSync(resolved)) return null\n const state = JSON.parse(readFileSync(resolved, 'utf-8')) as PairingCodeState\n if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase()) return null\n if (state.pairCode !== params.pairCode) return null\n if (state.consumedAt) return null\n if (new Date(state.expiresAt).getTime() <= Date.now()) return null\n writeFileSync(resolved, JSON.stringify({ ...state, pairCode: '', consumedAt: new Date().toISOString() }, null, 2), 'utf-8')\n return state\n}\n\nfunction isPairedSenderPolicy(value: unknown): value is PairedSenderPolicy {\n if (!value || typeof value !== 'object') return false\n const policy = value as PairedSenderPolicy\n return typeof policy.sender === 'string'\n && typeof policy.dispatchContextRules === 'object'\n && typeof policy.pairedAt === 'string'\n}\n\nexport function loadPairedSenderPolicies(file?: string): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n if (!existsSync(resolved)) return []\n try {\n const data = JSON.parse(readFileSync(resolved, 'utf-8')) as unknown\n return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : []\n } catch {\n return []\n }\n}\n\nexport function addPairedSenderPolicy(file: string | undefined, policy: PairedSenderPolicy): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n const policies = loadPairedSenderPolicies(resolved)\n const normalizedSender = policy.sender.toLowerCase()\n const next = [\n ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),\n { ...policy, sender: normalizedSender },\n ]\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(next, null, 2), 'utf-8')\n return next\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,YAAY;AAC9B,SAAS,eAAe;AACxB,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { homedir } from 'node:os'\nimport { randomBytes } from 'node:crypto'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: string[]\n}\n\nexport interface DispatchContextRules {\n [key: string]: string[]\n}\n\nexport interface PairingCodeState {\n mailbox: string\n pairCode: string\n expiresAt: string\n connectUrl: string\n consumedAt?: string\n}\n\nexport const DEFAULT_PAIRING_WEB_URL = 'https://meshmail.ai/pair'\n\nexport interface PairedSenderPolicy {\n sender: string\n dispatchContextRules: DispatchContextRules\n pairedAt: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.json')\n}\n\nexport function defaultTaskStatePath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.task-state.json')\n}\n\nexport function defaultPairingPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.pairing.json')\n}\n\nexport function defaultSenderPoliciesPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.sender-policies.json')\n}\n\nexport function loadCachedIdentity(file?: string): Identity | null {\n const resolved = file ?? defaultCredentialsPath()\n if (!existsSync(resolved)) return null\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as Partial<Identity>\n if (!parsed.email || !parsed.mailboxToken || !parsed.smtpPassword) return null\n return {\n email: parsed.email,\n mailboxToken: parsed.mailboxToken,\n smtpPassword: parsed.smtpPassword,\n }\n } catch {\n return null\n }\n}\n\nexport function saveCachedIdentity(identity: Identity, file?: string): void {\n const resolved = file ?? defaultCredentialsPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n email: identity.email,\n mailboxToken: identity.mailboxToken,\n smtpPassword: identity.smtpPassword,\n }, null, 2), 'utf-8')\n}\n\nexport function loadTaskState(file?: string): CachedTaskState {\n const resolved = file ?? defaultTaskStatePath()\n if (!existsSync(resolved)) return { terminalTaskIds: [] }\n try {\n const parsed = JSON.parse(readFileSync(resolved, 'utf-8')) as CachedTaskState\n return {\n terminalTaskIds: Array.isArray(parsed.terminalTaskIds) ? parsed.terminalTaskIds.filter(Boolean) : [],\n }\n } catch {\n return { terminalTaskIds: [] }\n }\n}\n\nexport function saveTaskState(state: CachedTaskState, file?: string): void {\n const resolved = file ?? defaultTaskStatePath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify({\n terminalTaskIds: state.terminalTaskIds ?? [],\n }, null, 2), 'utf-8')\n}\n\nexport function createPairingCode(params: {\n mailbox: string\n file?: string\n ttlSeconds?: number\n}): PairingCodeState {\n const pairCode = randomBytes(6).toString('base64url')\n const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1000).toISOString()\n const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`\n const state: PairingCodeState = {\n mailbox: params.mailbox.toLowerCase(),\n pairCode,\n expiresAt,\n connectUrl,\n }\n const resolved = params.file ?? defaultPairingPath()\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(state, null, 2), 'utf-8')\n return state\n}\n\nexport function pairingUrlToWebUrl(connectUrl: string): string {\n const parsed = new URL(connectUrl)\n const url = new URL(DEFAULT_PAIRING_WEB_URL)\n for (const [key, value] of parsed.searchParams) {\n url.searchParams.set(key, value)\n }\n return url.toString()\n}\n\nexport function consumePairingCode(params: {\n mailbox: string\n pairCode: string\n file?: string\n}): PairingCodeState | null {\n const resolved = params.file ?? defaultPairingPath()\n if (!existsSync(resolved)) return null\n const state = JSON.parse(readFileSync(resolved, 'utf-8')) as PairingCodeState\n if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase()) return null\n if (state.pairCode !== params.pairCode) return null\n if (state.consumedAt) return null\n if (new Date(state.expiresAt).getTime() <= Date.now()) return null\n writeFileSync(resolved, JSON.stringify({ ...state, pairCode: '', consumedAt: new Date().toISOString() }, null, 2), 'utf-8')\n return state\n}\n\nfunction isPairedSenderPolicy(value: unknown): value is PairedSenderPolicy {\n if (!value || typeof value !== 'object') return false\n const policy = value as PairedSenderPolicy\n return typeof policy.sender === 'string'\n && typeof policy.dispatchContextRules === 'object'\n && typeof policy.pairedAt === 'string'\n}\n\nexport function loadPairedSenderPolicies(file?: string): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n if (!existsSync(resolved)) return []\n try {\n const data = JSON.parse(readFileSync(resolved, 'utf-8')) as unknown\n return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : []\n } catch {\n return []\n }\n}\n\nexport function addPairedSenderPolicy(file: string | undefined, policy: PairedSenderPolicy): PairedSenderPolicy[] {\n const resolved = file ?? defaultSenderPoliciesPath()\n const policies = loadPairedSenderPolicies(resolved)\n const normalizedSender = policy.sender.toLowerCase()\n const next = [\n ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),\n { ...policy, sender: normalizedSender },\n ]\n mkdirSync(dirname(resolved), { recursive: true })\n writeFileSync(resolved, JSON.stringify(next, null, 2), 'utf-8')\n return next\n}\n\nexport function ensureDir(dir: string): void {\n mkdirSync(dir, { recursive: true })\n}\n\nexport function readBinaryFile(path: string): Buffer {\n return readFileSync(path)\n}\n\nexport function writeBinaryFile(path: string, content: Uint8Array | Buffer): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|