aamp-openclaw-plugin 0.1.7 → 0.1.10
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 +3 -3
- package/bin/aamp-openclaw-plugin.mjs +328 -0
- package/dist/file-store.js +43 -0
- package/dist/file-store.js.map +7 -0
- package/dist/index.js +110 -1270
- package/dist/index.js.map +4 -4
- package/openclaw.plugin.json +5 -5
- package/package.json +14 -4
- package/skills/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -27,14 +27,14 @@ npm run build
|
|
|
27
27
|
{
|
|
28
28
|
"plugins": {
|
|
29
29
|
"entries": {
|
|
30
|
-
"aamp": {
|
|
30
|
+
"aamp-openclaw-plugin": {
|
|
31
31
|
"enabled": true,
|
|
32
32
|
"config": {
|
|
33
33
|
"aampHost": "https://meshmail.ai",
|
|
34
34
|
"slug": "openclaw-agent",
|
|
35
|
-
"credentialsFile": "
|
|
35
|
+
"credentialsFile": "~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json",
|
|
36
36
|
"allowedSenders": [
|
|
37
|
-
"
|
|
37
|
+
"meegle-bot@meshmail.ai"
|
|
38
38
|
]
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { dirname, join } from 'node:path'
|
|
6
|
+
import { createInterface } from 'node:readline/promises'
|
|
7
|
+
import { stdin as input, stdout as output, stderr } from 'node:process'
|
|
8
|
+
import { spawnSync } from 'node:child_process'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const PLUGIN_ID = 'aamp-openclaw-plugin'
|
|
12
|
+
const DEFAULT_AAMP_HOST = 'https://meshmail.ai'
|
|
13
|
+
const DEFAULT_CREDENTIALS_FILE = '~/.openclaw/extensions/aamp-openclaw-plugin/.credentials.json'
|
|
14
|
+
|
|
15
|
+
function resolveOpenClawHome() {
|
|
16
|
+
return process.env.OPENCLAW_HOME?.trim() || join(homedir(), '.openclaw')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveOpenClawConfigPath() {
|
|
20
|
+
return join(resolveOpenClawHome(), 'openclaw.json')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveExtensionDir() {
|
|
24
|
+
return join(resolveOpenClawHome(), 'extensions', PLUGIN_ID)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function expandHome(pathValue) {
|
|
28
|
+
if (!pathValue) return pathValue
|
|
29
|
+
if (pathValue === '~') return homedir()
|
|
30
|
+
if (pathValue.startsWith('~/')) return join(homedir(), pathValue.slice(2))
|
|
31
|
+
return pathValue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readJsonFile(path) {
|
|
35
|
+
if (!existsSync(path)) return null
|
|
36
|
+
return JSON.parse(readFileSync(path, 'utf-8'))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeJsonFile(path, value) {
|
|
40
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
41
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + '\n', 'utf-8')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeBaseUrl(url) {
|
|
45
|
+
if (url.startsWith('http://') || url.startsWith('https://')) return url.replace(/\/$/, '')
|
|
46
|
+
return `https://${url.replace(/\/$/, '')}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function ensurePluginConfig(config, pluginConfig) {
|
|
50
|
+
const next = config && typeof config === 'object' ? structuredClone(config) : {}
|
|
51
|
+
if (!next.plugins || typeof next.plugins !== 'object') next.plugins = {}
|
|
52
|
+
if (!Array.isArray(next.plugins.allow)) next.plugins.allow = []
|
|
53
|
+
if (!next.plugins.entries || typeof next.plugins.entries !== 'object') next.plugins.entries = {}
|
|
54
|
+
|
|
55
|
+
if (!next.plugins.allow.includes(PLUGIN_ID)) {
|
|
56
|
+
next.plugins.allow.push(PLUGIN_ID)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const legacyEntry = next.plugins.entries.aamp
|
|
60
|
+
const prevEntry = next.plugins.entries[PLUGIN_ID] ?? legacyEntry
|
|
61
|
+
const mergedConfig = {
|
|
62
|
+
...(prevEntry?.config && typeof prevEntry.config === 'object' ? prevEntry.config : {}),
|
|
63
|
+
...pluginConfig,
|
|
64
|
+
}
|
|
65
|
+
if (!pluginConfig.allowedSenders) {
|
|
66
|
+
delete mergedConfig.allowedSenders
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
next.plugins.entries[PLUGIN_ID] = {
|
|
70
|
+
enabled: true,
|
|
71
|
+
...(prevEntry && typeof prevEntry === 'object' ? prevEntry : {}),
|
|
72
|
+
config: mergedConfig,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (next.plugins.entries.aamp) {
|
|
76
|
+
delete next.plugins.entries.aamp
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return next
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseAllowedSenders(raw) {
|
|
83
|
+
const trimmed = raw.trim()
|
|
84
|
+
if (!trimmed) return undefined
|
|
85
|
+
return trimmed
|
|
86
|
+
.split(',')
|
|
87
|
+
.map((s) => s.trim())
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function packageRootFromEntry(entryPath) {
|
|
92
|
+
let current = dirname(entryPath)
|
|
93
|
+
while (current !== dirname(current)) {
|
|
94
|
+
if (existsSync(join(current, 'package.json'))) return current
|
|
95
|
+
current = dirname(current)
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Could not locate package root for ${entryPath}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function copyIntoDir(src, dest) {
|
|
101
|
+
cpSync(src, dest, { recursive: true, force: true })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function installPluginFiles(credentialsFile = DEFAULT_CREDENTIALS_FILE) {
|
|
105
|
+
const extensionDir = resolveExtensionDir()
|
|
106
|
+
const packageRoot = packageRootFromEntry(fileURLToPath(import.meta.url))
|
|
107
|
+
const packageJson = readJsonFile(join(packageRoot, 'package.json'))
|
|
108
|
+
const credentialsPath = expandHome(credentialsFile)
|
|
109
|
+
const existingCredentials = existsSync(credentialsPath)
|
|
110
|
+
? readFileSync(credentialsPath)
|
|
111
|
+
: null
|
|
112
|
+
|
|
113
|
+
rmSync(extensionDir, { recursive: true, force: true })
|
|
114
|
+
mkdirSync(extensionDir, { recursive: true })
|
|
115
|
+
|
|
116
|
+
for (const rel of packageJson.files ?? []) {
|
|
117
|
+
const src = join(packageRoot, rel)
|
|
118
|
+
if (existsSync(src)) {
|
|
119
|
+
copyIntoDir(src, join(extensionDir, rel))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
writeJsonFile(join(extensionDir, 'package.json'), packageJson)
|
|
124
|
+
|
|
125
|
+
const dependencyPackages = ['aamp-sdk', 'ws', 'nodemailer']
|
|
126
|
+
const nodeModulesDir = join(extensionDir, 'node_modules')
|
|
127
|
+
mkdirSync(nodeModulesDir, { recursive: true })
|
|
128
|
+
|
|
129
|
+
for (const dep of dependencyPackages) {
|
|
130
|
+
const depRoot = join(packageRoot, 'node_modules', dep)
|
|
131
|
+
if (!existsSync(depRoot)) {
|
|
132
|
+
throw new Error(`Missing dependency directory: ${depRoot}`)
|
|
133
|
+
}
|
|
134
|
+
copyIntoDir(depRoot, join(nodeModulesDir, dep))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (existingCredentials) {
|
|
138
|
+
mkdirSync(dirname(credentialsPath), { recursive: true })
|
|
139
|
+
writeFileSync(credentialsPath, existingCredentials)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return extensionDir
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function restartGateway() {
|
|
146
|
+
const result = spawnSync('openclaw', ['gateway', 'restart'], {
|
|
147
|
+
encoding: 'utf-8',
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (result.error) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
reason: result.error.message,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (result.status !== 0) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
reason: (result.stderr || result.stdout || `exit code ${result.status}`).trim(),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
message: (result.stdout || 'Gateway restart requested successfully.').trim(),
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function ensureMailboxIdentity({ aampHost, slug, credentialsFile }) {
|
|
171
|
+
const resolvedCreds = expandHome(credentialsFile)
|
|
172
|
+
if (existsSync(resolvedCreds)) {
|
|
173
|
+
return { created: false, email: null, credentialsPath: resolvedCreds }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const base = normalizeBaseUrl(aampHost)
|
|
177
|
+
const registerRes = await fetch(`${base}/api/nodes/self-register`, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
slug,
|
|
182
|
+
description: 'OpenClaw AAMP agent node',
|
|
183
|
+
}),
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
if (!registerRes.ok) {
|
|
187
|
+
const text = await registerRes.text().catch(() => '')
|
|
188
|
+
throw new Error(`AAMP self-register failed (${registerRes.status}): ${text || registerRes.statusText}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const registerData = await registerRes.json()
|
|
192
|
+
const code = registerData?.registrationCode
|
|
193
|
+
if (!code) {
|
|
194
|
+
throw new Error('AAMP self-register succeeded but no registrationCode was returned')
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const credRes = await fetch(`${base}/api/nodes/credentials?code=${encodeURIComponent(code)}`)
|
|
198
|
+
if (!credRes.ok) {
|
|
199
|
+
const text = await credRes.text().catch(() => '')
|
|
200
|
+
throw new Error(`AAMP credential exchange failed (${credRes.status}): ${text || credRes.statusText}`)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const credData = await credRes.json()
|
|
204
|
+
const identity = {
|
|
205
|
+
email: credData?.email,
|
|
206
|
+
jmapToken: credData?.jmap?.token,
|
|
207
|
+
smtpPassword: credData?.smtp?.password,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!identity.email || !identity.jmapToken || !identity.smtpPassword) {
|
|
211
|
+
throw new Error('AAMP credential exchange returned an incomplete identity payload')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
writeJsonFile(resolvedCreds, identity)
|
|
215
|
+
return { created: true, email: identity.email, credentialsPath: resolvedCreds }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function printHelp() {
|
|
219
|
+
output.write(
|
|
220
|
+
[
|
|
221
|
+
'aamp-openclaw-plugin',
|
|
222
|
+
'',
|
|
223
|
+
'Commands:',
|
|
224
|
+
' init Install the OpenClaw plugin and write ~/.openclaw/openclaw.json',
|
|
225
|
+
' help Show this help',
|
|
226
|
+
'',
|
|
227
|
+
].join('\n'),
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function runInit() {
|
|
232
|
+
let aampHost = DEFAULT_AAMP_HOST
|
|
233
|
+
let allowedSenders
|
|
234
|
+
|
|
235
|
+
if (input.isTTY) {
|
|
236
|
+
const rl = createInterface({ input, output })
|
|
237
|
+
try {
|
|
238
|
+
output.write('AAMP OpenClaw Plugin Setup\n\n')
|
|
239
|
+
|
|
240
|
+
const aampHostAnswer = await rl.question(`AAMP Host (${DEFAULT_AAMP_HOST}): `)
|
|
241
|
+
aampHost = aampHostAnswer.trim() || DEFAULT_AAMP_HOST
|
|
242
|
+
|
|
243
|
+
const allowedSendersAnswer = await rl.question(
|
|
244
|
+
'Allowed Senders (comma-separated, leave blank to allow all): ',
|
|
245
|
+
)
|
|
246
|
+
allowedSenders = parseAllowedSenders(allowedSendersAnswer)
|
|
247
|
+
} finally {
|
|
248
|
+
rl.close()
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
const [hostLine = '', sendersLine = ''] = readFileSync(0, 'utf-8').split(/\r?\n/)
|
|
252
|
+
aampHost = hostLine.trim() || DEFAULT_AAMP_HOST
|
|
253
|
+
allowedSenders = parseAllowedSenders(sendersLine)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const configPath = resolveOpenClawConfigPath()
|
|
257
|
+
const existing = readJsonFile(configPath)
|
|
258
|
+
const previousEntry = existing?.plugins?.entries?.[PLUGIN_ID] ?? existing?.plugins?.entries?.aamp
|
|
259
|
+
const previousCredentialsFile = previousEntry?.config?.credentialsFile || DEFAULT_CREDENTIALS_FILE
|
|
260
|
+
|
|
261
|
+
output.write('\nInstalling OpenClaw plugin files...\n')
|
|
262
|
+
const extensionDir = installPluginFiles(previousCredentialsFile)
|
|
263
|
+
|
|
264
|
+
const next = ensurePluginConfig(existing, {
|
|
265
|
+
aampHost,
|
|
266
|
+
slug: 'openclaw-agent',
|
|
267
|
+
credentialsFile: DEFAULT_CREDENTIALS_FILE,
|
|
268
|
+
...(allowedSenders ? { allowedSenders } : {}),
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
writeJsonFile(configPath, next)
|
|
272
|
+
|
|
273
|
+
const identityResult = await ensureMailboxIdentity({
|
|
274
|
+
aampHost,
|
|
275
|
+
slug: 'openclaw-agent',
|
|
276
|
+
credentialsFile: DEFAULT_CREDENTIALS_FILE,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const restartResult = restartGateway()
|
|
280
|
+
|
|
281
|
+
output.write(
|
|
282
|
+
[
|
|
283
|
+
'',
|
|
284
|
+
`Updated ${configPath}`,
|
|
285
|
+
`Installed files to ${extensionDir}`,
|
|
286
|
+
'',
|
|
287
|
+
'Configured plugin entry:',
|
|
288
|
+
` plugins.entries["${PLUGIN_ID}"]`,
|
|
289
|
+
` aampHost: ${aampHost}`,
|
|
290
|
+
` credentialsFile: ${DEFAULT_CREDENTIALS_FILE}`,
|
|
291
|
+
` allowedSenders: ${allowedSenders ? allowedSenders.join(', ') : '(allow all)'}`,
|
|
292
|
+
identityResult.created
|
|
293
|
+
? ` mailbox: ${identityResult.email} (registered and saved to ${identityResult.credentialsPath})`
|
|
294
|
+
: ` mailbox: existing credentials reused from ${identityResult.credentialsPath}`,
|
|
295
|
+
'',
|
|
296
|
+
restartResult.ok
|
|
297
|
+
? `Gateway restart: ${restartResult.message}`
|
|
298
|
+
: `Gateway restart failed: ${restartResult.reason}`,
|
|
299
|
+
restartResult.ok
|
|
300
|
+
? 'Plugin changes should now be active.'
|
|
301
|
+
: 'Please restart the OpenClaw gateway manually for the plugin changes to take effect.',
|
|
302
|
+
'',
|
|
303
|
+
].join('\n'),
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function main() {
|
|
308
|
+
const command = process.argv[2] || 'help'
|
|
309
|
+
|
|
310
|
+
if (command === 'help' || command === '--help' || command === '-h') {
|
|
311
|
+
printHelp()
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (command === 'init') {
|
|
316
|
+
await runInit()
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
stderr.write(`Unknown command: ${command}\n\n`)
|
|
321
|
+
printHelp()
|
|
322
|
+
process.exitCode = 1
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
main().catch((err) => {
|
|
326
|
+
stderr.write(`${err instanceof Error ? err.message : String(err)}\n`)
|
|
327
|
+
process.exit(1)
|
|
328
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
function defaultCredentialsPath() {
|
|
5
|
+
return join(homedir(), ".openclaw", "extensions", "aamp-openclaw-plugin", ".credentials.json");
|
|
6
|
+
}
|
|
7
|
+
function loadCachedIdentity(file) {
|
|
8
|
+
const resolved = file ?? defaultCredentialsPath();
|
|
9
|
+
if (!existsSync(resolved))
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(readFileSync(resolved, "utf-8"));
|
|
13
|
+
if (!parsed.email || !parsed.jmapToken || !parsed.smtpPassword)
|
|
14
|
+
return null;
|
|
15
|
+
return parsed;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveCachedIdentity(identity, file) {
|
|
21
|
+
const resolved = file ?? defaultCredentialsPath();
|
|
22
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
23
|
+
writeFileSync(resolved, JSON.stringify(identity, null, 2), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
function ensureDir(dir) {
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
function readBinaryFile(path) {
|
|
29
|
+
return readFileSync(path);
|
|
30
|
+
}
|
|
31
|
+
function writeBinaryFile(path, content) {
|
|
32
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
33
|
+
writeFileSync(path, content);
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
defaultCredentialsPath,
|
|
37
|
+
ensureDir,
|
|
38
|
+
loadCachedIdentity,
|
|
39
|
+
readBinaryFile,
|
|
40
|
+
saveCachedIdentity,
|
|
41
|
+
writeBinaryFile
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=file-store.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 jmapToken: string\n smtpPassword: string\n}\n\nexport function defaultCredentialsPath(): string {\n return join(homedir(), '.openclaw', 'extensions', 'aamp-openclaw-plugin', '.credentials.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.jmapToken || !parsed.smtpPassword) return null\n return parsed as Identity\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(identity, 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;AAQjB,SAAS,yBAAiC;AAC/C,SAAO,KAAK,QAAQ,GAAG,aAAa,cAAc,wBAAwB,mBAAmB;AAC/F;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,aAAa,CAAC,OAAO;AAAc,aAAO;AACvE,WAAO;AAAA,EACT,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,UAAU,MAAM,CAAC,GAAG,OAAO;AACpE;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;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|