feishu-docs-cli 0.1.0-beta.2
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/LICENSE +21 -0
- package/README.md +333 -0
- package/README.zh.md +332 -0
- package/bin/feishu-docs.js +8 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +512 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +166 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.js +269 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/cat.d.ts +6 -0
- package/dist/commands/cat.js +151 -0
- package/dist/commands/cat.js.map +1 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.js +120 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/delete.d.ts +6 -0
- package/dist/commands/delete.js +89 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.js +69 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/login.d.ts +10 -0
- package/dist/commands/login.js +90 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/ls.d.ts +6 -0
- package/dist/commands/ls.js +76 -0
- package/dist/commands/ls.js.map +1 -0
- package/dist/commands/read.d.ts +6 -0
- package/dist/commands/read.js +404 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +87 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/share.d.ts +13 -0
- package/dist/commands/share.js +210 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/spaces.d.ts +6 -0
- package/dist/commands/spaces.js +43 -0
- package/dist/commands/spaces.js.map +1 -0
- package/dist/commands/tree.d.ts +6 -0
- package/dist/commands/tree.js +101 -0
- package/dist/commands/tree.js.map +1 -0
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +211 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/wiki.d.ts +6 -0
- package/dist/commands/wiki.js +284 -0
- package/dist/commands/wiki.js.map +1 -0
- package/dist/parser/block-types.d.ts +141 -0
- package/dist/parser/block-types.js +167 -0
- package/dist/parser/block-types.js.map +1 -0
- package/dist/parser/blocks-to-md.d.ts +26 -0
- package/dist/parser/blocks-to-md.js +576 -0
- package/dist/parser/blocks-to-md.js.map +1 -0
- package/dist/parser/text-elements.d.ts +13 -0
- package/dist/parser/text-elements.js +91 -0
- package/dist/parser/text-elements.js.map +1 -0
- package/dist/services/block-writer.d.ts +30 -0
- package/dist/services/block-writer.js +131 -0
- package/dist/services/block-writer.js.map +1 -0
- package/dist/services/doc-blocks.d.ts +9 -0
- package/dist/services/doc-blocks.js +30 -0
- package/dist/services/doc-blocks.js.map +1 -0
- package/dist/services/markdown-convert.d.ts +51 -0
- package/dist/services/markdown-convert.js +109 -0
- package/dist/services/markdown-convert.js.map +1 -0
- package/dist/services/wiki-nodes.d.ts +21 -0
- package/dist/services/wiki-nodes.js +47 -0
- package/dist/services/wiki-nodes.js.map +1 -0
- package/dist/types/index.d.ts +234 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/document-resolver.d.ts +28 -0
- package/dist/utils/document-resolver.js +47 -0
- package/dist/utils/document-resolver.js.map +1 -0
- package/dist/utils/drive-types.d.ts +4 -0
- package/dist/utils/drive-types.js +16 -0
- package/dist/utils/drive-types.js.map +1 -0
- package/dist/utils/errors.d.ts +21 -0
- package/dist/utils/errors.js +110 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/member.d.ts +11 -0
- package/dist/utils/member.js +30 -0
- package/dist/utils/member.js.map +1 -0
- package/dist/utils/url-parser.d.ts +5 -0
- package/dist/utils/url-parser.js +55 -0
- package/dist/utils/url-parser.js.map +1 -0
- package/dist/utils/validate.d.ts +8 -0
- package/dist/utils/validate.js +15 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +54 -0
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication module: OAuth login, token persistence, auto-refresh.
|
|
3
|
+
*/
|
|
4
|
+
import type { AuthMode, AuthInfo, TokenData } from "./types/index.js";
|
|
5
|
+
interface StoredTokens {
|
|
6
|
+
appId: string;
|
|
7
|
+
tokens: TokenData & {
|
|
8
|
+
scope?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface OauthLoginOptions {
|
|
12
|
+
appSecret?: string;
|
|
13
|
+
scope?: string;
|
|
14
|
+
port?: number | string;
|
|
15
|
+
redirectUri?: string;
|
|
16
|
+
useLark?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface OAuthCallbackConfig {
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
callbackHost: string;
|
|
21
|
+
callbackPath: string;
|
|
22
|
+
callbackPort: number;
|
|
23
|
+
}
|
|
24
|
+
interface BuildAuthorizationUrlOptions {
|
|
25
|
+
appId: string;
|
|
26
|
+
redirectUri: string;
|
|
27
|
+
state: string;
|
|
28
|
+
scope?: string;
|
|
29
|
+
useLark?: boolean;
|
|
30
|
+
codeChallenge?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Save auth tokens to encrypted config file.
|
|
34
|
+
*/
|
|
35
|
+
export declare function saveTokens(appId: string, tokenData: TokenData & {
|
|
36
|
+
scope?: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Load auth tokens from encrypted config file.
|
|
40
|
+
* Returns null if not found.
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadTokens(): Promise<StoredTokens | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Clear saved auth tokens.
|
|
45
|
+
*/
|
|
46
|
+
export declare function clearTokens(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve credentials based on auth mode.
|
|
49
|
+
* Returns { mode, appId, appSecret, userToken }
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveAuth(authMode?: AuthMode | string): Promise<AuthInfo>;
|
|
52
|
+
export declare function resolveOAuthCallbackConfig(options?: OauthLoginOptions): OAuthCallbackConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Generate PKCE code_verifier and code_challenge (S256).
|
|
55
|
+
*/
|
|
56
|
+
export declare function generatePkce(): {
|
|
57
|
+
codeVerifier: string;
|
|
58
|
+
codeChallenge: string;
|
|
59
|
+
};
|
|
60
|
+
export declare function buildAuthorizationUrl({ appId, redirectUri, state, scope, useLark, codeChallenge, }: BuildAuthorizationUrlOptions): string;
|
|
61
|
+
/**
|
|
62
|
+
* Run OAuth login flow (v2 OAuth with PKCE S256).
|
|
63
|
+
*/
|
|
64
|
+
export declare function oauthLogin(appId: string, options?: OauthLoginOptions): Promise<TokenData>;
|
|
65
|
+
/**
|
|
66
|
+
* Acquire an exclusive file lock for token refresh.
|
|
67
|
+
* Returns a release function, or null if lock is held by another process.
|
|
68
|
+
*/
|
|
69
|
+
export declare function acquireRefreshLock(): Promise<(() => Promise<void>) | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Refresh user_access_token using refresh_token (v2 OAuth).
|
|
72
|
+
*/
|
|
73
|
+
export declare function refreshUserToken(appId: string, appSecret: string, refreshToken: string, { useLark }?: {
|
|
74
|
+
useLark?: boolean;
|
|
75
|
+
}): Promise<TokenData>;
|
|
76
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication module: OAuth login, token persistence, auto-refresh.
|
|
3
|
+
*/
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { createHash, randomBytes, scryptSync, createCipheriv, createDecipheriv, } from "node:crypto";
|
|
6
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { readFile, writeFile, mkdir, unlink, open } from "node:fs/promises";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { CliError } from "./utils/errors.js";
|
|
12
|
+
const CONFIG_DIR = join(homedir(), ".feishu-docs");
|
|
13
|
+
const AUTH_FILE = join(CONFIG_DIR, "auth.json");
|
|
14
|
+
const LOCK_FILE = join(CONFIG_DIR, ".refresh.lock");
|
|
15
|
+
const ALGORITHM = "aes-256-gcm";
|
|
16
|
+
const KEY_LENGTH = 32;
|
|
17
|
+
const IV_LENGTH = 16;
|
|
18
|
+
const AUTH_TAG_LENGTH = 16;
|
|
19
|
+
const DEFAULT_OAUTH_HOST = "127.0.0.1";
|
|
20
|
+
const DEFAULT_OAUTH_PORT = 3456;
|
|
21
|
+
const DEFAULT_OAUTH_PATH = "/callback";
|
|
22
|
+
const LOCAL_CALLBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
23
|
+
/**
|
|
24
|
+
* Derive encryption key from machine identity + random salt.
|
|
25
|
+
*/
|
|
26
|
+
function deriveKey(salt) {
|
|
27
|
+
const identity = `${hostname()}:${userInfo().username}:feishu-docs-cli`;
|
|
28
|
+
return scryptSync(identity, salt, KEY_LENGTH);
|
|
29
|
+
}
|
|
30
|
+
function encrypt(data) {
|
|
31
|
+
const salt = randomBytes(32);
|
|
32
|
+
const key = deriveKey(salt);
|
|
33
|
+
const iv = randomBytes(IV_LENGTH);
|
|
34
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
35
|
+
const encrypted = Buffer.concat([
|
|
36
|
+
cipher.update(JSON.stringify(data), "utf8"),
|
|
37
|
+
cipher.final(),
|
|
38
|
+
]);
|
|
39
|
+
const authTag = cipher.getAuthTag();
|
|
40
|
+
return (salt.toString("hex") +
|
|
41
|
+
":" +
|
|
42
|
+
iv.toString("hex") +
|
|
43
|
+
":" +
|
|
44
|
+
authTag.toString("hex") +
|
|
45
|
+
":" +
|
|
46
|
+
encrypted.toString("hex"));
|
|
47
|
+
}
|
|
48
|
+
function decrypt(encryptedData) {
|
|
49
|
+
const parts = encryptedData.split(":");
|
|
50
|
+
if (parts.length !== 4) {
|
|
51
|
+
throw new Error("Malformed encrypted data");
|
|
52
|
+
}
|
|
53
|
+
const [saltHex, ivHex, authTagHex, dataHex] = parts;
|
|
54
|
+
const salt = Buffer.from(saltHex, "hex");
|
|
55
|
+
const key = deriveKey(salt);
|
|
56
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
57
|
+
const authTag = Buffer.from(authTagHex, "hex");
|
|
58
|
+
const encrypted = Buffer.from(dataHex, "hex");
|
|
59
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
60
|
+
decipher.setAuthTag(authTag);
|
|
61
|
+
const decrypted = Buffer.concat([
|
|
62
|
+
decipher.update(encrypted),
|
|
63
|
+
decipher.final(),
|
|
64
|
+
]);
|
|
65
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Save auth tokens to encrypted config file.
|
|
69
|
+
*/
|
|
70
|
+
export async function saveTokens(appId, tokenData) {
|
|
71
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
72
|
+
const payload = {
|
|
73
|
+
version: 2,
|
|
74
|
+
app_id: appId,
|
|
75
|
+
encrypted_data: encrypt(tokenData),
|
|
76
|
+
};
|
|
77
|
+
await writeFile(AUTH_FILE, JSON.stringify(payload, null, 2), {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
mode: 0o600,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Load auth tokens from encrypted config file.
|
|
84
|
+
* Returns null if not found.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadTokens() {
|
|
87
|
+
if (!existsSync(AUTH_FILE))
|
|
88
|
+
return null;
|
|
89
|
+
try {
|
|
90
|
+
const raw = await readFile(AUTH_FILE, "utf8");
|
|
91
|
+
if (!raw.trim())
|
|
92
|
+
return null;
|
|
93
|
+
const payload = JSON.parse(raw);
|
|
94
|
+
if (payload.version !== 2)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
appId: payload.app_id,
|
|
98
|
+
tokens: decrypt(payload.encrypted_data),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Clear saved auth tokens.
|
|
107
|
+
*/
|
|
108
|
+
export async function clearTokens() {
|
|
109
|
+
if (existsSync(AUTH_FILE)) {
|
|
110
|
+
await unlink(AUTH_FILE);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve credentials based on auth mode.
|
|
115
|
+
* Returns { mode, appId, appSecret, userToken }
|
|
116
|
+
*/
|
|
117
|
+
export async function resolveAuth(authMode = "auto") {
|
|
118
|
+
const envUserToken = process.env.FEISHU_USER_TOKEN;
|
|
119
|
+
const envAppId = process.env.FEISHU_APP_ID;
|
|
120
|
+
const envAppSecret = process.env.FEISHU_APP_SECRET;
|
|
121
|
+
if (authMode === "user") {
|
|
122
|
+
if (envUserToken) {
|
|
123
|
+
return {
|
|
124
|
+
mode: "user",
|
|
125
|
+
appId: envAppId,
|
|
126
|
+
appSecret: envAppSecret,
|
|
127
|
+
userToken: envUserToken,
|
|
128
|
+
useLark: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const saved = await loadTokens();
|
|
132
|
+
if (saved?.tokens?.user_access_token) {
|
|
133
|
+
return {
|
|
134
|
+
mode: "user",
|
|
135
|
+
appId: saved.appId,
|
|
136
|
+
appSecret: envAppSecret,
|
|
137
|
+
userToken: saved.tokens.user_access_token,
|
|
138
|
+
refreshToken: saved.tokens.refresh_token,
|
|
139
|
+
expiresAt: saved.tokens.expires_at,
|
|
140
|
+
useLark: false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
throw new CliError("AUTH_REQUIRED", "未找到 user_access_token,请运行 feishu-docs login", {
|
|
144
|
+
recovery: "运行 feishu-docs login 获取 user 身份认证",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (authMode === "tenant") {
|
|
148
|
+
if (!envAppId || !envAppSecret) {
|
|
149
|
+
throw new CliError("AUTH_REQUIRED", "缺少 FEISHU_APP_ID 或 FEISHU_APP_SECRET 环境变量", {
|
|
150
|
+
recovery: "设置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
mode: "tenant",
|
|
155
|
+
appId: envAppId,
|
|
156
|
+
appSecret: envAppSecret,
|
|
157
|
+
useLark: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// auto mode: try user first, then tenant
|
|
161
|
+
if (envUserToken) {
|
|
162
|
+
return {
|
|
163
|
+
mode: "user",
|
|
164
|
+
appId: envAppId,
|
|
165
|
+
appSecret: envAppSecret,
|
|
166
|
+
userToken: envUserToken,
|
|
167
|
+
useLark: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const saved = await loadTokens();
|
|
171
|
+
if (saved?.tokens?.user_access_token) {
|
|
172
|
+
const isExpired = saved.tokens.expires_at && Date.now() >= saved.tokens.expires_at;
|
|
173
|
+
const canRefresh = !!saved.tokens.refresh_token;
|
|
174
|
+
const hasTenantCreds = envAppId && envAppSecret;
|
|
175
|
+
// In auto mode, skip stale user token when tenant creds are available
|
|
176
|
+
// and the token cannot be refreshed (no refresh_token).
|
|
177
|
+
if (isExpired && !canRefresh && hasTenantCreds) {
|
|
178
|
+
// Fall through to tenant mode
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
return {
|
|
182
|
+
mode: "user",
|
|
183
|
+
appId: saved.appId || envAppId,
|
|
184
|
+
appSecret: envAppSecret,
|
|
185
|
+
userToken: saved.tokens.user_access_token,
|
|
186
|
+
refreshToken: saved.tokens.refresh_token,
|
|
187
|
+
expiresAt: saved.tokens.expires_at,
|
|
188
|
+
useLark: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (envAppId && envAppSecret) {
|
|
193
|
+
return {
|
|
194
|
+
mode: "tenant",
|
|
195
|
+
appId: envAppId,
|
|
196
|
+
appSecret: envAppSecret,
|
|
197
|
+
useLark: false,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
throw new CliError("AUTH_REQUIRED", "未找到任何认证凭证。请设置环境变量或运行 feishu-docs login", {
|
|
201
|
+
recovery: "设置 FEISHU_APP_ID + FEISHU_APP_SECRET 环境变量,或运行 feishu-docs login",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Open URL in the default browser.
|
|
206
|
+
*/
|
|
207
|
+
function openBrowser(url) {
|
|
208
|
+
const openCmd = process.platform === "darwin"
|
|
209
|
+
? "open"
|
|
210
|
+
: process.platform === "win32"
|
|
211
|
+
? "start"
|
|
212
|
+
: "xdg-open";
|
|
213
|
+
execFile(openCmd, [url], (err) => {
|
|
214
|
+
if (err) {
|
|
215
|
+
process.stderr.write(`feishu-docs: warning: 无法自动打开浏览器: ${err.message}\n`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function parseCallbackPort(value) {
|
|
220
|
+
const port = Number(value);
|
|
221
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
222
|
+
throw new CliError("INVALID_ARGS", `无效的 OAuth 回调端口: ${value}`, {
|
|
223
|
+
recovery: "使用 1-65535 之间的端口,或通过 --redirect-uri / FEISHU_REDIRECT_URI 传入完整回调地址",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return port;
|
|
227
|
+
}
|
|
228
|
+
export function resolveOAuthCallbackConfig(options = {}) {
|
|
229
|
+
const rawPort = options.port ?? process.env.FEISHU_OAUTH_PORT ?? DEFAULT_OAUTH_PORT;
|
|
230
|
+
const fallbackRedirectUri = `http://${DEFAULT_OAUTH_HOST}:${parseCallbackPort(rawPort)}${DEFAULT_OAUTH_PATH}`;
|
|
231
|
+
const rawRedirectUri = options.redirectUri ||
|
|
232
|
+
process.env.FEISHU_REDIRECT_URI ||
|
|
233
|
+
fallbackRedirectUri;
|
|
234
|
+
let parsed;
|
|
235
|
+
try {
|
|
236
|
+
parsed = new URL(rawRedirectUri);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
throw new CliError("INVALID_ARGS", `无效的 redirect_uri: ${rawRedirectUri}`, {
|
|
240
|
+
recovery: "使用完整 URL,例如 http://localhost:3456/callback,并确保它与飞书开放平台登记值完全一致",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (parsed.protocol !== "http:") {
|
|
244
|
+
throw new CliError("INVALID_ARGS", "redirect_uri 必须使用本机回调地址(仅支持 http://localhost 或 http://127.0.0.1)", {
|
|
245
|
+
recovery: "将飞书应用的重定向地址登记为本机 HTTP 回调,再通过 --redirect-uri 或 FEISHU_REDIRECT_URI 传入完全一致的值",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (!LOCAL_CALLBACK_HOSTS.has(parsed.hostname)) {
|
|
249
|
+
throw new CliError("INVALID_ARGS", "redirect_uri 必须使用本机回调地址(仅支持 localhost、127.0.0.1、::1)", {
|
|
250
|
+
recovery: "使用本机回调地址,例如 http://localhost:3456/callback,并确保它与飞书开放平台登记值完全一致",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (parsed.search || parsed.hash) {
|
|
254
|
+
throw new CliError("INVALID_ARGS", "redirect_uri 不能包含 query 或 hash", {
|
|
255
|
+
recovery: "请传入纯净的回调地址,例如 http://localhost:3456/callback",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const callbackPort = parseCallbackPort(parsed.port || "80");
|
|
259
|
+
const callbackPath = parsed.pathname || "/";
|
|
260
|
+
const redirectUri = `${parsed.origin}${callbackPath}`;
|
|
261
|
+
return {
|
|
262
|
+
redirectUri,
|
|
263
|
+
callbackHost: parsed.hostname,
|
|
264
|
+
callbackPath,
|
|
265
|
+
callbackPort,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Generate PKCE code_verifier and code_challenge (S256).
|
|
270
|
+
*/
|
|
271
|
+
export function generatePkce() {
|
|
272
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
273
|
+
const codeChallenge = createHash("sha256")
|
|
274
|
+
.update(codeVerifier)
|
|
275
|
+
.digest("base64url");
|
|
276
|
+
return { codeVerifier, codeChallenge };
|
|
277
|
+
}
|
|
278
|
+
export function buildAuthorizationUrl({ appId, redirectUri, state, scope, useLark = false, codeChallenge, }) {
|
|
279
|
+
const params = new URLSearchParams({
|
|
280
|
+
app_id: appId,
|
|
281
|
+
redirect_uri: redirectUri,
|
|
282
|
+
state,
|
|
283
|
+
});
|
|
284
|
+
if (scope) {
|
|
285
|
+
params.set("scope", scope);
|
|
286
|
+
}
|
|
287
|
+
if (codeChallenge) {
|
|
288
|
+
params.set("code_challenge", codeChallenge);
|
|
289
|
+
params.set("code_challenge_method", "S256");
|
|
290
|
+
}
|
|
291
|
+
const authBase = useLark
|
|
292
|
+
? "https://accounts.larksuite.com/open-apis/authen/v1/authorize"
|
|
293
|
+
: "https://open.feishu.cn/open-apis/authen/v1/authorize";
|
|
294
|
+
return `${authBase}?${params.toString()}`;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Exchange authorization code for tokens via v2 OAuth endpoint.
|
|
298
|
+
*/
|
|
299
|
+
async function exchangeCodeForToken(appId, appSecret, code, redirectUri, { useLark = false, codeVerifier } = {}) {
|
|
300
|
+
const host = useLark
|
|
301
|
+
? "https://open.larksuite.com"
|
|
302
|
+
: "https://open.feishu.cn";
|
|
303
|
+
const requestBody = {
|
|
304
|
+
grant_type: "authorization_code",
|
|
305
|
+
client_id: appId,
|
|
306
|
+
client_secret: appSecret,
|
|
307
|
+
code,
|
|
308
|
+
redirect_uri: redirectUri,
|
|
309
|
+
};
|
|
310
|
+
if (codeVerifier) {
|
|
311
|
+
requestBody.code_verifier = codeVerifier;
|
|
312
|
+
}
|
|
313
|
+
const res = await fetch(`${host}/open-apis/authen/v2/oauth/token`, {
|
|
314
|
+
method: "POST",
|
|
315
|
+
headers: { "Content-Type": "application/json" },
|
|
316
|
+
body: JSON.stringify(requestBody),
|
|
317
|
+
});
|
|
318
|
+
const body = (await res.json());
|
|
319
|
+
if (body.code !== 0) {
|
|
320
|
+
throw new CliError("AUTH_REQUIRED", `Token 交换失败: ${body.msg}`, {
|
|
321
|
+
apiCode: body.code,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return body;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Run OAuth login flow (v2 OAuth with PKCE S256).
|
|
328
|
+
*/
|
|
329
|
+
export async function oauthLogin(appId, options = {}) {
|
|
330
|
+
const appSecret = options.appSecret || process.env.FEISHU_APP_SECRET;
|
|
331
|
+
const scope = options.scope ||
|
|
332
|
+
"wiki:wiki docx:document docx:document.block:convert drive:drive contact:contact.base:readonly board:whiteboard:node:read bitable:app:readonly";
|
|
333
|
+
const { redirectUri, callbackHost, callbackPath, callbackPort } = resolveOAuthCallbackConfig(options);
|
|
334
|
+
const state = randomBytes(16).toString("hex");
|
|
335
|
+
const { codeVerifier, codeChallenge } = generatePkce();
|
|
336
|
+
return new Promise((resolve, reject) => {
|
|
337
|
+
let timeout;
|
|
338
|
+
const openSockets = new Set();
|
|
339
|
+
const server = createServer(async (req, res) => {
|
|
340
|
+
if (req.method !== "GET") {
|
|
341
|
+
res.writeHead(405, { Allow: "GET" });
|
|
342
|
+
res.end("Method Not Allowed");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!req.url) {
|
|
346
|
+
res.writeHead(404);
|
|
347
|
+
res.end("Not found");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const url = new URL(req.url, redirectUri);
|
|
351
|
+
if (url.pathname !== callbackPath) {
|
|
352
|
+
res.writeHead(404);
|
|
353
|
+
res.end("Not found");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const code = url.searchParams.get("code");
|
|
357
|
+
const returnedState = url.searchParams.get("state");
|
|
358
|
+
// Validate state to prevent CSRF
|
|
359
|
+
if (!returnedState || returnedState !== state) {
|
|
360
|
+
res.writeHead(400);
|
|
361
|
+
res.end("Invalid state parameter");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (!code) {
|
|
365
|
+
res.writeHead(400);
|
|
366
|
+
res.end("Missing authorization code");
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const tokenRes = await exchangeCodeForToken(appId, appSecret, code, redirectUri, { useLark: options.useLark, codeVerifier });
|
|
371
|
+
const tokenData = {
|
|
372
|
+
user_access_token: tokenRes.access_token,
|
|
373
|
+
refresh_token: tokenRes.refresh_token,
|
|
374
|
+
expires_at: Date.now() + (tokenRes.expires_in || 7200) * 1000,
|
|
375
|
+
token_type: tokenRes.token_type,
|
|
376
|
+
};
|
|
377
|
+
await saveTokens(appId, { ...tokenData, scope: tokenRes.scope });
|
|
378
|
+
res.writeHead(200, {
|
|
379
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
380
|
+
Connection: "close",
|
|
381
|
+
});
|
|
382
|
+
res.end("<h1>登录成功!</h1><p>你可以关闭此页面。</p>");
|
|
383
|
+
clearTimeout(timeout);
|
|
384
|
+
server.close();
|
|
385
|
+
for (const socket of openSockets)
|
|
386
|
+
socket.destroy();
|
|
387
|
+
resolve(tokenData);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
const error = err;
|
|
391
|
+
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
392
|
+
const safeMsg = (error.message || "").replace(/[&<>"']/g, (c) => ({
|
|
393
|
+
"&": "&",
|
|
394
|
+
"<": "<",
|
|
395
|
+
">": ">",
|
|
396
|
+
'"': """,
|
|
397
|
+
"'": "'",
|
|
398
|
+
})[c] ?? c);
|
|
399
|
+
res.end(`<h1>登录失败</h1><p>${safeMsg}</p>`);
|
|
400
|
+
clearTimeout(timeout);
|
|
401
|
+
server.close();
|
|
402
|
+
for (const socket of openSockets)
|
|
403
|
+
socket.destroy();
|
|
404
|
+
reject(err);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
// Track connections so we can force-close them after callback
|
|
408
|
+
server.on("connection", (socket) => {
|
|
409
|
+
openSockets.add(socket);
|
|
410
|
+
socket.on("close", () => openSockets.delete(socket));
|
|
411
|
+
});
|
|
412
|
+
server.listen(callbackPort, callbackHost, () => {
|
|
413
|
+
const authUrl = buildAuthorizationUrl({
|
|
414
|
+
appId,
|
|
415
|
+
redirectUri,
|
|
416
|
+
scope,
|
|
417
|
+
state,
|
|
418
|
+
useLark: options.useLark,
|
|
419
|
+
codeChallenge,
|
|
420
|
+
});
|
|
421
|
+
process.stderr.write("\n正在打开浏览器进行授权...\n" +
|
|
422
|
+
`回调地址: ${redirectUri}\n` +
|
|
423
|
+
"请确保该地址已在飞书开放平台应用的安全设置中登记,且与当前值完全一致。\n\n" +
|
|
424
|
+
`如果浏览器未自动打开,请手动访问:\n ${authUrl}\n\n等待回调中...\n`);
|
|
425
|
+
openBrowser(authUrl);
|
|
426
|
+
});
|
|
427
|
+
timeout = setTimeout(() => {
|
|
428
|
+
server.close();
|
|
429
|
+
for (const socket of openSockets)
|
|
430
|
+
socket.destroy();
|
|
431
|
+
reject(new Error("OAuth 登录超时(5分钟),请重试"));
|
|
432
|
+
}, 5 * 60 * 1000);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Acquire an exclusive file lock for token refresh.
|
|
437
|
+
* Returns a release function, or null if lock is held by another process.
|
|
438
|
+
*/
|
|
439
|
+
export async function acquireRefreshLock() {
|
|
440
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
441
|
+
try {
|
|
442
|
+
const fh = await open(LOCK_FILE, "wx");
|
|
443
|
+
await fh.write(String(process.pid));
|
|
444
|
+
return async () => {
|
|
445
|
+
await fh.close();
|
|
446
|
+
try {
|
|
447
|
+
await unlink(LOCK_FILE);
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// ignore cleanup errors
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
catch (err) {
|
|
455
|
+
const error = err;
|
|
456
|
+
if (error.code === "EEXIST") {
|
|
457
|
+
// Check for stale lock (process no longer alive)
|
|
458
|
+
try {
|
|
459
|
+
const pidStr = await readFile(LOCK_FILE, "utf8");
|
|
460
|
+
const pid = Number(pidStr.trim());
|
|
461
|
+
if (pid && pid !== process.pid) {
|
|
462
|
+
try {
|
|
463
|
+
process.kill(pid, 0); // check if process is alive
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
// Process is dead — stale lock, remove and retry
|
|
467
|
+
await unlink(LOCK_FILE);
|
|
468
|
+
return acquireRefreshLock();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// Can't read lock file — treat as held
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
throw err;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Refresh user_access_token using refresh_token (v2 OAuth).
|
|
482
|
+
*/
|
|
483
|
+
export async function refreshUserToken(appId, appSecret, refreshToken, { useLark = false } = {}) {
|
|
484
|
+
const host = useLark
|
|
485
|
+
? "https://open.larksuite.com"
|
|
486
|
+
: "https://open.feishu.cn";
|
|
487
|
+
const res = await fetch(`${host}/open-apis/authen/v2/oauth/token`, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
headers: { "Content-Type": "application/json" },
|
|
490
|
+
body: JSON.stringify({
|
|
491
|
+
grant_type: "refresh_token",
|
|
492
|
+
client_id: appId,
|
|
493
|
+
client_secret: appSecret,
|
|
494
|
+
refresh_token: refreshToken,
|
|
495
|
+
}),
|
|
496
|
+
});
|
|
497
|
+
const body = (await res.json());
|
|
498
|
+
if (body.code !== 0) {
|
|
499
|
+
throw new CliError("TOKEN_EXPIRED", `Token 刷新失败: ${body.msg}`, {
|
|
500
|
+
apiCode: body.code,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
const tokenData = {
|
|
504
|
+
user_access_token: body.access_token,
|
|
505
|
+
refresh_token: body.refresh_token,
|
|
506
|
+
expires_at: Date.now() + (body.expires_in || 7200) * 1000,
|
|
507
|
+
token_type: body.token_type,
|
|
508
|
+
};
|
|
509
|
+
await saveTokens(appId, { ...tokenData, scope: body.scope });
|
|
510
|
+
return tokenData;
|
|
511
|
+
}
|
|
512
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AACnD,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAChD,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AAEpD,MAAM,SAAS,GAAG,aAAa,CAAC;AAChC,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,kBAAkB,GAAG,WAAW,CAAC;AACvC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AACvC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;AAoDxE;;GAEG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,QAAQ,GAAG,GAAG,QAAQ,EAAE,IAAI,QAAQ,EAAE,CAAC,QAAQ,kBAAkB,CAAC;IACxE,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAW,CAAC;AAC1D,CAAC;AAED,SAAS,OAAO,CAAC,IAAa;IAC5B,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAC3C,MAAM,CAAC,KAAK,EAAE;KACf,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,OAAO,CACL,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpB,GAAG;QACH,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;QAClB,GAAG;QACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,GAAG;QACH,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC1B,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,aAAqB;IACpC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;QAC9B,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;QAC1B,QAAQ,CAAC,KAAK,EAAE;KACjB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAa,EACb,SAAyC;IAEzC,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAkB;QAC7B,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,KAAK;QACb,cAAc,EAAE,OAAO,CAAC,SAAS,CAAC;KACnC,CAAC;IACF,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC3D,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,OAAO,GAAkB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,MAAM;YACrB,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;SACxC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAA8B,MAAM;IAEpC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAEnD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,QAAQ;gBACf,SAAS,EAAE,YAAY;gBACvB,SAAS,EAAE,YAAY;gBACvB,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;QACjC,IAAI,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;YACrC,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,YAAY;gBACvB,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,iBAAiB;gBACzC,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,aAAa;gBACxC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU;gBAClC,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,QAAQ,CAChB,eAAe,EACf,6CAA6C,EAC7C;YACE,QAAQ,EAAE,mCAAmC;SAC9C,CACF,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/B,MAAM,IAAI,QAAQ,CAChB,eAAe,EACf,2CAA2C,EAC3C;gBACE,QAAQ,EAAE,2CAA2C;aACtD,CACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,SAAS,EAAE,YAAY;YACvB,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;IAED,yCAAyC;IACzC,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,QAAQ;YACf,SAAS,EAAE,YAAY;YACvB,SAAS,EAAE,YAAY;YACvB,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;IACjC,IAAI,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;QACrC,MAAM,SAAS,GACb,KAAK,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;QACnE,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC;QAChD,MAAM,cAAc,GAAG,QAAQ,IAAI,YAAY,CAAC;QAEhD,sEAAsE;QACtE,wDAAwD;QACxD,IAAI,SAAS,IAAI,CAAC,UAAU,IAAI,cAAc,EAAE,CAAC;YAC/C,8BAA8B;QAChC,CAAC;aAAM,CAAC;YACN,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,QAAQ;gBAC9B,SAAS,EAAE,YAAY;gBACvB,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,iBAAiB;gBACzC,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,aAAa;gBACxC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU;gBAClC,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,IAAI,YAAY,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,SAAS,EAAE,YAAY;YACvB,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,QAAQ,CAChB,eAAe,EACf,wCAAwC,EACxC;QACE,QAAQ,EACN,iEAAiE;KACpE,CACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,OAAO,GACX,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,UAAU,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/B,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oCAAoC,GAAG,CAAC,OAAO,IAAI,CACpD,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAsB;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACxD,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,mBAAmB,KAAK,EAAE,EAAE;YAC7D,QAAQ,EACN,oEAAoE;SACvE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,UAA6B,EAAE;IAE/B,MAAM,OAAO,GACX,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,kBAAkB,CAAC;IACtE,MAAM,mBAAmB,GAAG,UAAU,kBAAkB,IAAI,iBAAiB,CAAC,OAAO,CAAC,GAAG,kBAAkB,EAAE,CAAC;IAC9G,MAAM,cAAc,GAClB,OAAO,CAAC,WAAW;QACnB,OAAO,CAAC,GAAG,CAAC,mBAAmB;QAC/B,mBAAmB,CAAC;IAEtB,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAChB,cAAc,EACd,qBAAqB,cAAc,EAAE,EACrC;YACE,QAAQ,EACN,+DAA+D;SAClE,CACF,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,QAAQ,CAChB,cAAc,EACd,kEAAkE,EAClE;YACE,QAAQ,EACN,4EAA4E;SAC/E,CACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,QAAQ,CAChB,cAAc,EACd,sDAAsD,EACtD;YACE,QAAQ,EACN,+DAA+D;SAClE,CACF,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,gCAAgC,EAAE;YACnE,QAAQ,EAAE,8CAA8C;SACzD,CAAC,CAAC;IACL,CAAC;IAED,MAAM,YAAY,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAC5D,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,CAAC;IAC5C,MAAM,WAAW,GAAG,GAAG,MAAM,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;IAEtD,OAAO;QACL,WAAW;QACX,YAAY,EAAE,MAAM,CAAC,QAAQ;QAC7B,YAAY;QACZ,YAAY;KACb,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAI1B,MAAM,YAAY,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,UAAU,CAAC,QAAQ,CAAC;SACvC,MAAM,CAAC,YAAY,CAAC;SACpB,MAAM,CAAC,WAAW,CAAC,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,EACpC,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,EACL,OAAO,GAAG,KAAK,EACf,aAAa,GACgB;IAC7B,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;QACjC,MAAM,EAAE,KAAK;QACb,YAAY,EAAE,WAAW;QACzB,KAAK;KACN,CAAC,CAAC;IACH,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO;QACtB,CAAC,CAAC,8DAA8D;QAChE,CAAC,CAAC,sDAAsD,CAAC;IAE3D,OAAO,GAAG,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,oBAAoB,CACjC,KAAa,EACb,SAA6B,EAC7B,IAAY,EACZ,WAAmB,EACnB,EAAE,OAAO,GAAG,KAAK,EAAE,YAAY,KAA0B,EAAE;IAE3D,MAAM,IAAI,GAAG,OAAO;QAClB,CAAC,CAAC,4BAA4B;QAC9B,CAAC,CAAC,wBAAwB,CAAC;IAC7B,MAAM,WAAW,GAAuC;QACtD,UAAU,EAAE,oBAAoB;QAChC,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,SAAS;QACxB,IAAI;QACJ,YAAY,EAAE,WAAW;KAC1B,CAAC;IACF,IAAI,YAAY,EAAE,CAAC;QACjB,WAAW,CAAC,aAAa,GAAG,YAAY,CAAC;IAC3C,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,kCAAkC,EAAE;QACjE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;KAClC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;IACxD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,QAAQ,CAAC,eAAe,EAAE,eAAe,IAAI,CAAC,GAAG,EAAE,EAAE;YAC7D,OAAO,EAAE,IAAI,CAAC,IAAI;SACnB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAa,EACb,UAA6B,EAAE;IAE/B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACrE,MAAM,KAAK,GACT,OAAO,CAAC,KAAK;QACb,+IAA+I,CAAC;IAClJ,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,GAC7D,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,YAAY,EAAE,CAAC;IAEvD,OAAO,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAChD,IAAI,OAAsC,CAAC;QAC3C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,MAAM,MAAM,GAAW,YAAY,CACjC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE;YAClD,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;gBACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACrC,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;gBAC9B,OAAO;YACT,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YAC1C,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAClC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,aAAa,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAEpD,iCAAiC;YACjC,IAAI,CAAC,aAAa,IAAI,aAAa,KAAK,KAAK,EAAE,CAAC;gBAC9C,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;gBACnC,OAAO;YACT,CAAC;YAED,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;gBACtC,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CACzC,KAAK,EACL,SAAS,EACT,IAAI,EACJ,WAAW,EACX,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,CAC3C,CAAC;gBAEF,MAAM,SAAS,GAAc;oBAC3B,iBAAiB,EAAE,QAAQ,CAAC,YAAY;oBACxC,aAAa,EAAE,QAAQ,CAAC,aAAa;oBACrC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,IAAI;oBAC7D,UAAU,EAAE,QAAQ,CAAC,UAAU;iBAChC,CAAC;gBAEF,MAAM,UAAU,CAAC,KAAK,EAAE,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;gBAEjE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBACjB,cAAc,EAAE,0BAA0B;oBAC1C,UAAU,EAAE,OAAO;iBACpB,CAAC,CAAC;gBACH,GAAG,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;gBAE1C,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,KAAK,MAAM,MAAM,IAAI,WAAW;oBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnD,OAAO,CAAC,SAAS,CAAC,CAAC;YACrB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,GAAY,CAAC;gBAC3B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;gBACnE,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,OAAO,CAC3C,UAAU,EACV,CAAC,CAAC,EAAE,EAAE,CAEF,CAAC;oBACC,GAAG,EAAE,OAAO;oBACZ,GAAG,EAAE,MAAM;oBACX,GAAG,EAAE,MAAM;oBACX,GAAG,EAAE,QAAQ;oBACb,GAAG,EAAE,OAAO;iBACb,CACF,CAAC,CAAC,CAAC,IAAI,CAAC,CACZ,CAAC;gBACF,GAAG,CAAC,GAAG,CAAC,mBAAmB,OAAO,MAAM,CAAC,CAAC;gBAC1C,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,KAAK,MAAM,MAAM,IAAI,WAAW;oBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnD,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CACF,CAAC;QAEF,8DAA8D;QAC9D,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAc,EAAE,EAAE;YACzC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACxB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE;YAC7C,MAAM,OAAO,GAAG,qBAAqB,CAAC;gBACpC,KAAK;gBACL,WAAW;gBACX,KAAK;gBACL,KAAK;gBACL,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,aAAa;aACd,CAAC,CAAC;YAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oBAAoB;gBAClB,SAAS,WAAW,IAAI;gBACxB,yCAAyC;gBACzC,wBAAwB,OAAO,gBAAgB,CAClD,CAAC;YACF,WAAW,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,UAAU,CAClB,GAAG,EAAE;YACH,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,WAAW;gBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QAC3C,CAAC,EACD,CAAC,GAAG,EAAE,GAAG,IAAI,CACd,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IAGtC,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,OAAO,KAAK,IAAI,EAAE;YAChB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAA4B,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,iDAAiD;YACjD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;gBACjD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;gBAClC,IAAI,GAAG,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC;oBAC/B,IAAI,CAAC;wBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,4BAA4B;oBACpD,CAAC;oBAAC,MAAM,CAAC;wBACP,iDAAiD;wBACjD,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;wBACxB,OAAO,kBAAkB,EAAE,CAAC;oBAC9B,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,uCAAuC;YACzC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAa,EACb,SAAiB,EACjB,YAAoB,EACpB,EAAE,OAAO,GAAG,KAAK,KAA4B,EAAE;IAE/C,MAAM,IAAI,GAAG,OAAO;QAClB,CAAC,CAAC,4BAA4B;QAC9B,CAAC,CAAC,wBAAwB,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,kCAAkC,EAAE;QACjE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,UAAU,EAAE,eAAe;YAC3B,SAAS,EAAE,KAAK;YAChB,aAAa,EAAE,SAAS;YACxB,aAAa,EAAE,YAAY;SAC5B,CAAC;KACH,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC;IAExD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,QAAQ,CAAC,eAAe,EAAE,eAAe,IAAI,CAAC,GAAG,EAAE,EAAE;YAC7D,OAAO,EAAE,IAAI,CAAC,IAAI;SACnB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAc;QAC3B,iBAAiB,EAAE,IAAI,CAAC,YAAY;QACpC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,IAAI;QACzD,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC;IAEF,MAAM,UAAU,CAAC,KAAK,EAAE,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7D,OAAO,SAAS,CAAC;AACnB,CAAC"}
|