aamp-openclaw-plugin 0.1.38 → 0.1.40

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
@@ -22,6 +22,18 @@ the installer will prompt for:
22
22
 
23
23
  The answers are written into the OpenClaw plugin config automatically, so users do not need to hand-edit `openclaw.json`.
24
24
 
25
+ When the plugin starts, it also prints a five-minute `aamp://connect?...`
26
+ pairing URL and terminal QR code. Scan it with AAMP App, paste it into User UI,
27
+ or run `aamp-cli pair --url ...` to authorize that sender. A valid
28
+ `pair.request` writes the sender and optional dispatch-context rules to the
29
+ paired sender policy file, then consumes the code. The plugin replies with
30
+ `pair.respond`; rejected responses include the failure reason.
31
+
32
+ You can generate a fresh pairing QR code later without restarting OpenClaw:
33
+
34
+ - Ask the agent to use the `aamp_pairing_code` tool.
35
+ - Or run the `/aamp-pair` command in OpenClaw.
36
+
25
37
  ## Build
26
38
 
27
39
  ```bash
@@ -41,6 +53,8 @@ npm run build
41
53
  "taskDispatchConcurrency": 10,
42
54
  "slug": "openclaw-agent",
43
55
  "credentialsFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
56
+ "pairingFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json",
57
+ "senderPoliciesFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json",
44
58
  "senderPolicies": [
45
59
  {
46
60
  "sender": "meegle-bot@meshmail.ai",
@@ -57,7 +71,7 @@ npm run build
57
71
  }
58
72
  ```
59
73
 
60
- If `senderPolicies` is omitted, all senders are accepted. If set, the dispatch sender must match one policy and all configured dispatch-context rules for that sender must pass.
74
+ If `senderPolicies` is omitted, no senders are authorized by default. Use the printed pairing QR/URL or configure at least one policy before sending `task.dispatch`; matching policies can also require all configured dispatch-context rules to pass.
61
75
  `taskDispatchConcurrency` is optional and defaults to `10`.
62
76
 
63
77
  The plugin also understands:
@@ -13,6 +13,8 @@ import { AampClient } from 'aamp-sdk'
13
13
  const PLUGIN_ID = 'aamp-openclaw-plugin'
14
14
  const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
15
15
  const DEFAULT_CREDENTIALS_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json'
16
+ const DEFAULT_PAIRING_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.pairing.json'
17
+ const DEFAULT_SENDER_POLICIES_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.sender-policies.json'
16
18
  const CODING_TOOL_ALLOWLIST = [
17
19
  'read',
18
20
  'write',
@@ -36,9 +38,12 @@ const CODING_TOOL_ALLOWLIST = [
36
38
  'image_generate',
37
39
  ]
38
40
  const AAMP_PLUGIN_TOOL_ALLOWLIST = [
41
+ 'aamp_directory_search',
39
42
  'aamp_send_result',
40
43
  'aamp_send_help',
41
44
  'aamp_pending_tasks',
45
+ 'aamp_pairing_code',
46
+ 'aamp_cancel_task',
42
47
  'aamp_dispatch_task',
43
48
  'aamp_check_protocol',
44
49
  'aamp_download_attachment',
@@ -177,6 +182,19 @@ export function writeJsonFile(path, value) {
177
182
  writeFileSync(path, JSON.stringify(value, null, 2) + '\n', 'utf-8')
178
183
  }
179
184
 
185
+ async function renderTerminalQr(value) {
186
+ try {
187
+ const qrcode = await import('qrcode-terminal')
188
+ const generator = qrcode.default?.generate ?? qrcode.generate
189
+ if (!generator) return ''
190
+ return await new Promise((resolve) => {
191
+ generator(value, { small: true }, (qr) => resolve(qr))
192
+ })
193
+ } catch {
194
+ return ''
195
+ }
196
+ }
197
+
180
198
  export function normalizeBaseUrl(url) {
181
199
  if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
182
200
  return `https://${url.replace(/\/$/, '')}`
@@ -464,6 +482,18 @@ export async function ensureMailboxIdentity({ aampHost, slug, credentialsFile })
464
482
  return { created: true, email: identity.email, credentialsPath: resolvedCreds }
465
483
  }
466
484
 
485
+ export function createPairingFile({ email, pairingFile }) {
486
+ if (!email) return null
487
+ const pairing = AampClient.createPairingCode({ mailbox: email })
488
+ writeJsonFile(expandHome(pairingFile), {
489
+ mailbox: pairing.mailbox,
490
+ pairCode: pairing.pairCode,
491
+ expiresAt: pairing.expiresAt,
492
+ connectUrl: pairing.connectUrl,
493
+ })
494
+ return pairing
495
+ }
496
+
467
497
  function printHelp() {
468
498
  output.write(
469
499
  [
@@ -507,7 +537,7 @@ export async function runInit() {
507
537
  'Detected existing plugin config:',
508
538
  ` aampHost: ${previousConfig.aampHost ?? DEFAULT_AAMP_HOST}`,
509
539
  ` slug: ${previousConfig.slug ?? 'openclaw-agent'}`,
510
- ` senderPolicies: ${previousConfig.senderPolicies ? JSON.stringify(previousConfig.senderPolicies) : '(allow all)'}`,
540
+ ` senderPolicies: ${previousConfig.senderPolicies ? JSON.stringify(previousConfig.senderPolicies) : '(default deny until paired or configured)'}`,
511
541
  '',
512
542
  ].join('\n'),
513
543
  )
@@ -520,7 +550,7 @@ export async function runInit() {
520
550
  aampHost = aampHostAnswer.trim() || aampHost
521
551
 
522
552
  const senderAnswer = await rl.question(
523
- 'Primary trusted dispatch sender (e.g. meegle-bot@meshmail.ai, leave blank to allow all): ',
553
+ 'Primary trusted dispatch sender (e.g. meegle-bot@meshmail.ai, leave blank to pair later): ',
524
554
  )
525
555
  const sender = senderAnswer.trim()
526
556
  if (sender) {
@@ -586,6 +616,8 @@ export async function runInit() {
586
616
  aampHost,
587
617
  slug,
588
618
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
619
+ pairingFile: DEFAULT_PAIRING_FILE,
620
+ senderPoliciesFile: DEFAULT_SENDER_POLICIES_FILE,
589
621
  ...(senderPolicies ? { senderPolicies } : {}),
590
622
  }, {
591
623
  includeCodingBaseline,
@@ -612,6 +644,10 @@ export async function runInit() {
612
644
  slug,
613
645
  credentialsFile: DEFAULT_CREDENTIALS_FILE,
614
646
  })
647
+ const pairing = identityResult.email
648
+ ? createPairingFile({ email: identityResult.email, pairingFile: DEFAULT_PAIRING_FILE })
649
+ : null
650
+ const qr = pairing ? await renderTerminalQr(pairing.connectUrl) : ''
615
651
 
616
652
  const restartResult = restartGateway()
617
653
 
@@ -627,7 +663,9 @@ export async function runInit() {
627
663
  ` channels.aamp.enabled: ${next.channels?.aamp?.enabled === true ? 'true' : 'false'}`,
628
664
  ` aampHost: ${aampHost}`,
629
665
  ` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
630
- ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(allow all)'}`,
666
+ ` pairingFile: ${DEFAULT_PAIRING_FILE}`,
667
+ ` senderPoliciesFile: ${DEFAULT_SENDER_POLICIES_FILE}`,
668
+ ` senderPolicies: ${senderPolicies ? JSON.stringify(senderPolicies) : '(default deny until paired or configured)'}`,
631
669
  ` tools.allow: ${JSON.stringify(next.tools?.allow ?? [])}`,
632
670
  ` codingBaselineAdded: ${toolPolicyPlan.missingCodingTools.length > 0 && includeCodingBaseline ? 'yes' : 'no'}`,
633
671
  identityResult.created
@@ -642,6 +680,8 @@ export async function runInit() {
642
680
  restartResult.ok
643
681
  ? 'Plugin changes should now be active.'
644
682
  : 'Please restart the OpenClaw gateway manually for the plugin changes to take effect.',
683
+ pairing ? `Pairing URL (expires ${pairing.expiresAt}): ${pairing.connectUrl}` : '',
684
+ qr ? `\n${qr}` : '',
645
685
  '',
646
686
  ].join('\n'),
647
687
  )
@@ -2,12 +2,19 @@
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
+ import { randomBytes } from "node:crypto";
5
6
  function defaultCredentialsPath() {
6
7
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
7
8
  }
8
9
  function defaultTaskStatePath() {
9
10
  return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".task-state.json");
10
11
  }
12
+ function defaultPairingPath() {
13
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".pairing.json");
14
+ }
15
+ function defaultSenderPoliciesPath() {
16
+ return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".sender-policies.json");
17
+ }
11
18
  function loadCachedIdentity(file) {
12
19
  const resolved = file ?? defaultCredentialsPath();
13
20
  if (!existsSync(resolved))
@@ -54,6 +61,66 @@ function saveTaskState(state, file) {
54
61
  terminalTaskIds: state.terminalTaskIds ?? []
55
62
  }, null, 2), "utf-8");
56
63
  }
64
+ function createPairingCode(params) {
65
+ const pairCode = randomBytes(6).toString("base64url");
66
+ const expiresAt = new Date(Date.now() + (params.ttlSeconds ?? 300) * 1e3).toISOString();
67
+ const connectUrl = `aamp://connect?mailbox=${encodeURIComponent(params.mailbox.toLowerCase())}&pair_code=${encodeURIComponent(pairCode)}`;
68
+ const state = {
69
+ mailbox: params.mailbox.toLowerCase(),
70
+ pairCode,
71
+ expiresAt,
72
+ connectUrl
73
+ };
74
+ const resolved = params.file ?? defaultPairingPath();
75
+ mkdirSync(dirname(resolved), { recursive: true });
76
+ writeFileSync(resolved, JSON.stringify(state, null, 2), "utf-8");
77
+ return state;
78
+ }
79
+ function consumePairingCode(params) {
80
+ const resolved = params.file ?? defaultPairingPath();
81
+ if (!existsSync(resolved))
82
+ return null;
83
+ const state = JSON.parse(readFileSync(resolved, "utf-8"));
84
+ if (state.mailbox.toLowerCase() !== params.mailbox.toLowerCase())
85
+ return null;
86
+ if (state.pairCode !== params.pairCode)
87
+ return null;
88
+ if (state.consumedAt)
89
+ return null;
90
+ if (new Date(state.expiresAt).getTime() <= Date.now())
91
+ return null;
92
+ writeFileSync(resolved, JSON.stringify({ ...state, pairCode: "", consumedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "utf-8");
93
+ return state;
94
+ }
95
+ function isPairedSenderPolicy(value) {
96
+ if (!value || typeof value !== "object")
97
+ return false;
98
+ const policy = value;
99
+ return typeof policy.sender === "string" && typeof policy.dispatchContextRules === "object" && typeof policy.pairedAt === "string";
100
+ }
101
+ function loadPairedSenderPolicies(file) {
102
+ const resolved = file ?? defaultSenderPoliciesPath();
103
+ if (!existsSync(resolved))
104
+ return [];
105
+ try {
106
+ const data = JSON.parse(readFileSync(resolved, "utf-8"));
107
+ return Array.isArray(data) ? data.filter(isPairedSenderPolicy) : [];
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+ function addPairedSenderPolicy(file, policy) {
113
+ const resolved = file ?? defaultSenderPoliciesPath();
114
+ const policies = loadPairedSenderPolicies(resolved);
115
+ const normalizedSender = policy.sender.toLowerCase();
116
+ const next = [
117
+ ...policies.filter((item) => item.sender.toLowerCase() !== normalizedSender),
118
+ { ...policy, sender: normalizedSender }
119
+ ];
120
+ mkdirSync(dirname(resolved), { recursive: true });
121
+ writeFileSync(resolved, JSON.stringify(next, null, 2), "utf-8");
122
+ return next;
123
+ }
57
124
  function ensureDir(dir) {
58
125
  mkdirSync(dir, { recursive: true });
59
126
  }
@@ -65,10 +132,16 @@ function writeBinaryFile(path, content) {
65
132
  writeFileSync(path, content);
66
133
  }
67
134
  export {
135
+ addPairedSenderPolicy,
136
+ consumePairingCode,
137
+ createPairingCode,
68
138
  defaultCredentialsPath,
139
+ defaultPairingPath,
140
+ defaultSenderPoliciesPath,
69
141
  defaultTaskStatePath,
70
142
  ensureDir,
71
143
  loadCachedIdentity,
144
+ loadPairedSenderPolicies,
72
145
  loadTaskState,
73
146
  readBinaryFile,
74
147
  saveCachedIdentity,
@@ -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'\n\nexport interface Identity {\n email: string\n mailboxToken: string\n smtpPassword: string\n}\n\nexport interface CachedTaskState {\n terminalTaskIds?: 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 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 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;AAYjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;AAEO,SAAS,uBAA+B;AAC7C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,kBAAkB;AAC9F;AAEO,SAAS,mBAAmB,MAAgC;AACjE,QAAM,WAAW,QAAQ,uBAAuB;AAChD,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO;AAClC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,QAAI,CAAC,OAAO,SAAS,CAAC,OAAO,gBAAgB,CAAC,OAAO;AAAc,aAAO;AAC1E,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA,IACvB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB,UAAoB,MAAqB;AAC1E,QAAM,WAAW,QAAQ,uBAAuB;AAChD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU;AAAA,IACrC,OAAO,SAAS;AAAA,IAChB,cAAc,SAAS;AAAA,IACvB,cAAc,SAAS;AAAA,EACzB,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;AAEO,SAAS,cAAc,MAAgC;AAC5D,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,MAAI,CAAC,WAAW,QAAQ;AAAG,WAAO,EAAE,iBAAiB,CAAC,EAAE;AACxD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC;AACzD,WAAO;AAAA,MACL,iBAAiB,MAAM,QAAQ,OAAO,eAAe,IAAI,OAAO,gBAAgB,OAAO,OAAO,IAAI,CAAC;AAAA,IACrG;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,iBAAiB,CAAC,EAAE;AAAA,EAC/B;AACF;AAEO,SAAS,cAAc,OAAwB,MAAqB;AACzE,QAAM,WAAW,QAAQ,qBAAqB;AAC9C,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU;AAAA,IACrC,iBAAiB,MAAM,mBAAmB,CAAC;AAAA,EAC7C,GAAG,MAAM,CAAC,GAAG,OAAO;AACtB;AAEO,SAAS,UAAU,KAAmB;AAC3C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC;AAEO,SAAS,eAAe,MAAsB;AACnD,SAAO,aAAa,IAAI;AAC1B;AAEO,SAAS,gBAAgB,MAAc,SAAoC;AAChF,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,gBAAc,MAAM,OAAO;AAC7B;",
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": "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",
6
6
  "names": []
7
7
  }