@tjamescouch/agentchat 0.36.2 → 0.36.4
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 +20 -15
- package/dist/bin/agentchat.js +2 -0
- package/dist/bin/agentchat.js.map +1 -1
- package/dist/lib/captcha.d.ts +36 -0
- package/dist/lib/captcha.d.ts.map +1 -0
- package/dist/lib/captcha.js +215 -0
- package/dist/lib/captcha.js.map +1 -0
- package/dist/lib/client.d.ts +8 -0
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +118 -0
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/daemon.d.ts +2 -0
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/daemon.js +6 -0
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/env-doctor.d.ts +50 -0
- package/dist/lib/env-doctor.d.ts.map +1 -0
- package/dist/lib/env-doctor.js +209 -0
- package/dist/lib/env-doctor.js.map +1 -0
- package/dist/lib/hnsw.d.ts +91 -0
- package/dist/lib/hnsw.d.ts.map +1 -0
- package/dist/lib/hnsw.js +486 -0
- package/dist/lib/hnsw.js.map +1 -0
- package/dist/lib/proposals.d.ts +2 -0
- package/dist/lib/proposals.d.ts.map +1 -1
- package/dist/lib/proposals.js +1 -0
- package/dist/lib/proposals.js.map +1 -1
- package/dist/lib/protocol.d.ts +14 -0
- package/dist/lib/protocol.d.ts.map +1 -1
- package/dist/lib/protocol.js +37 -0
- package/dist/lib/protocol.js.map +1 -1
- package/dist/lib/reputation.d.ts +34 -0
- package/dist/lib/reputation.d.ts.map +1 -1
- package/dist/lib/reputation.js +128 -0
- package/dist/lib/reputation.js.map +1 -1
- package/dist/lib/server/handlers/admin.d.ts +10 -7
- package/dist/lib/server/handlers/admin.d.ts.map +1 -1
- package/dist/lib/server/handlers/admin.js +73 -0
- package/dist/lib/server/handlers/admin.js.map +1 -1
- package/dist/lib/server/handlers/ban.d.ts +1 -7
- package/dist/lib/server/handlers/ban.d.ts.map +1 -1
- package/dist/lib/server/handlers/ban.js.map +1 -1
- package/dist/lib/server/handlers/captcha.d.ts +49 -0
- package/dist/lib/server/handlers/captcha.d.ts.map +1 -0
- package/dist/lib/server/handlers/captcha.js +203 -0
- package/dist/lib/server/handlers/captcha.js.map +1 -0
- package/dist/lib/server/handlers/disputes.d.ts +1 -7
- package/dist/lib/server/handlers/disputes.d.ts.map +1 -1
- package/dist/lib/server/handlers/disputes.js.map +1 -1
- package/dist/lib/server/handlers/identity.d.ts +1 -7
- package/dist/lib/server/handlers/identity.d.ts.map +1 -1
- package/dist/lib/server/handlers/identity.js +67 -11
- package/dist/lib/server/handlers/identity.js.map +1 -1
- package/dist/lib/server/handlers/index.d.ts +1 -0
- package/dist/lib/server/handlers/index.d.ts.map +1 -1
- package/dist/lib/server/handlers/index.js +2 -0
- package/dist/lib/server/handlers/index.js.map +1 -1
- package/dist/lib/server/handlers/message.d.ts +1 -7
- package/dist/lib/server/handlers/message.d.ts.map +1 -1
- package/dist/lib/server/handlers/message.js +9 -2
- package/dist/lib/server/handlers/message.js.map +1 -1
- package/dist/lib/server/handlers/nick.d.ts +1 -8
- package/dist/lib/server/handlers/nick.d.ts.map +1 -1
- package/dist/lib/server/handlers/nick.js.map +1 -1
- package/dist/lib/server/handlers/presence.d.ts +1 -7
- package/dist/lib/server/handlers/presence.d.ts.map +1 -1
- package/dist/lib/server/handlers/presence.js.map +1 -1
- package/dist/lib/server/handlers/proposal.d.ts +1 -7
- package/dist/lib/server/handlers/proposal.d.ts.map +1 -1
- package/dist/lib/server/handlers/proposal.js +1 -0
- package/dist/lib/server/handlers/proposal.js.map +1 -1
- package/dist/lib/server/handlers/skills.d.ts +1 -7
- package/dist/lib/server/handlers/skills.d.ts.map +1 -1
- package/dist/lib/server/handlers/skills.js +36 -11
- package/dist/lib/server/handlers/skills.js.map +1 -1
- package/dist/lib/server.d.ts +35 -1
- package/dist/lib/server.d.ts.map +1 -1
- package/dist/lib/server.js +152 -13
- package/dist/lib/server.js.map +1 -1
- package/dist/lib/types.d.ts +34 -4
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +8 -0
- package/dist/lib/types.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Doctor — startup health check for agentchat agents
|
|
3
|
+
*
|
|
4
|
+
* Scans for:
|
|
5
|
+
* 1. Exposed secrets in environment variables (API keys, tokens, passwords)
|
|
6
|
+
* 2. Missing admin public key (insecure mode — no directive verification)
|
|
7
|
+
* 3. Identity file permission issues
|
|
8
|
+
*
|
|
9
|
+
* Warn-only by default. Does not block startup.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
// Patterns that suggest a secret value in env var names
|
|
13
|
+
const SECRET_NAME_PATTERNS = [
|
|
14
|
+
/KEY/i,
|
|
15
|
+
/TOKEN/i,
|
|
16
|
+
/SECRET/i,
|
|
17
|
+
/PASSWORD/i,
|
|
18
|
+
/CREDENTIAL/i,
|
|
19
|
+
/AUTH/i,
|
|
20
|
+
/PRIVATE/i,
|
|
21
|
+
];
|
|
22
|
+
// Env vars that are known-safe despite matching patterns
|
|
23
|
+
const SAFE_ENV_NAMES = new Set([
|
|
24
|
+
'COLORTERM',
|
|
25
|
+
'GPG_AGENT_INFO',
|
|
26
|
+
'SSH_AUTH_SOCK',
|
|
27
|
+
'TERM_SESSION_ID',
|
|
28
|
+
'KEYCHAIN_PATH',
|
|
29
|
+
'AUTH_TYPE', // often just "none" or "basic"
|
|
30
|
+
'TOKEN_ENDPOINT', // URL, not a token
|
|
31
|
+
'PUBLIC_KEY', // public keys aren't secrets
|
|
32
|
+
'ADMIN_PUBKEY', // this is what we want to be set
|
|
33
|
+
]);
|
|
34
|
+
// Prefixes for values that look like real secrets
|
|
35
|
+
const SECRET_VALUE_PREFIXES = [
|
|
36
|
+
'sk-', // OpenAI, Anthropic
|
|
37
|
+
'sk-ant-', // Anthropic
|
|
38
|
+
'ghp_', // GitHub PAT
|
|
39
|
+
'gho_', // GitHub OAuth
|
|
40
|
+
'ghs_', // GitHub App
|
|
41
|
+
'ghu_', // GitHub user-to-server
|
|
42
|
+
'github_pat_', // GitHub fine-grained PAT
|
|
43
|
+
'xoxb-', // Slack bot
|
|
44
|
+
'xoxp-', // Slack user
|
|
45
|
+
'xoxs-', // Slack legacy
|
|
46
|
+
'Bearer ', // Bearer tokens
|
|
47
|
+
'Basic ', // Basic auth
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Redact a secret value for safe display
|
|
51
|
+
* Shows first 6 chars + asterisks, or just asterisks if too short
|
|
52
|
+
*/
|
|
53
|
+
function redactValue(value) {
|
|
54
|
+
if (value.length <= 8) {
|
|
55
|
+
return '***';
|
|
56
|
+
}
|
|
57
|
+
return value.substring(0, 6) + '***';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if an env var name looks like it holds a secret
|
|
61
|
+
*/
|
|
62
|
+
function looksLikeSecretName(name) {
|
|
63
|
+
if (SAFE_ENV_NAMES.has(name))
|
|
64
|
+
return false;
|
|
65
|
+
return SECRET_NAME_PATTERNS.some(pattern => pattern.test(name));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if a value looks like an actual secret (not just "true" or a path)
|
|
69
|
+
*/
|
|
70
|
+
function looksLikeSecretValue(value) {
|
|
71
|
+
// Short values are probably flags, not secrets
|
|
72
|
+
if (value.length < 10)
|
|
73
|
+
return false;
|
|
74
|
+
// Check for known secret prefixes
|
|
75
|
+
if (SECRET_VALUE_PREFIXES.some(prefix => value.startsWith(prefix))) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// High entropy strings (base64-ish, hex-ish) longer than 20 chars
|
|
79
|
+
if (value.length >= 20) {
|
|
80
|
+
const alphanumCount = (value.match(/[a-zA-Z0-9]/g) || []).length;
|
|
81
|
+
const ratio = alphanumCount / value.length;
|
|
82
|
+
// Mostly alphanumeric + some special chars = likely a key/token
|
|
83
|
+
if (ratio > 0.8)
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Scan environment variables for exposed secrets
|
|
90
|
+
*/
|
|
91
|
+
export function scanEnvSecrets() {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
94
|
+
if (!value)
|
|
95
|
+
continue;
|
|
96
|
+
if (looksLikeSecretName(name) && looksLikeSecretValue(value)) {
|
|
97
|
+
warnings.push({
|
|
98
|
+
name,
|
|
99
|
+
redacted: redactValue(value),
|
|
100
|
+
reason: 'Potential secret exposed in environment',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return warnings;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if admin public key is configured
|
|
108
|
+
* Looks for ADMIN_PUBKEY env var or admin_pubkey in skill file
|
|
109
|
+
*/
|
|
110
|
+
export function checkAdminPubkey(skillFilePath) {
|
|
111
|
+
// Check env var first
|
|
112
|
+
if (process.env.ADMIN_PUBKEY) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// Check skill file if path provided
|
|
116
|
+
if (skillFilePath) {
|
|
117
|
+
try {
|
|
118
|
+
const content = fs.readFileSync(skillFilePath, 'utf-8');
|
|
119
|
+
if (content.includes('admin_pubkey')) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// File doesn't exist or unreadable — no admin key
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check identity file permissions (should be 0600 — owner read/write only)
|
|
131
|
+
*/
|
|
132
|
+
export function checkIdentityPermissions(identityPath) {
|
|
133
|
+
const issues = [];
|
|
134
|
+
if (!identityPath)
|
|
135
|
+
return issues;
|
|
136
|
+
try {
|
|
137
|
+
const stat = fs.statSync(identityPath);
|
|
138
|
+
const mode = stat.mode & 0o777;
|
|
139
|
+
if (mode & 0o077) {
|
|
140
|
+
issues.push(`Identity file ${identityPath} has permissions ${mode.toString(8)} — ` +
|
|
141
|
+
`should be 600 (owner read/write only). Other users can read your private key.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// File doesn't exist yet — not an issue
|
|
146
|
+
}
|
|
147
|
+
return issues;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Run all environment health checks
|
|
151
|
+
*/
|
|
152
|
+
export function runEnvDoctor(options) {
|
|
153
|
+
const envWarnings = scanEnvSecrets();
|
|
154
|
+
const hasAdminKey = checkAdminPubkey(options?.skillFilePath);
|
|
155
|
+
const identityIssues = checkIdentityPermissions(options?.identityPath);
|
|
156
|
+
return {
|
|
157
|
+
warnings: envWarnings,
|
|
158
|
+
insecureMode: !hasAdminKey,
|
|
159
|
+
identityIssues,
|
|
160
|
+
clean: envWarnings.length === 0 && hasAdminKey && identityIssues.length === 0,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Print the envDoctor report to stderr
|
|
165
|
+
* Returns true if any warnings were printed
|
|
166
|
+
*/
|
|
167
|
+
export function printEnvDoctorReport(options) {
|
|
168
|
+
const result = runEnvDoctor(options);
|
|
169
|
+
if (result.clean)
|
|
170
|
+
return false;
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push('='.repeat(60));
|
|
174
|
+
lines.push(' ENVIRONMENT HEALTH CHECK');
|
|
175
|
+
lines.push('='.repeat(60));
|
|
176
|
+
// Insecure mode warning
|
|
177
|
+
if (result.insecureMode) {
|
|
178
|
+
lines.push('');
|
|
179
|
+
lines.push('\u26A0\uFE0F WARNING: No admin public key configured.');
|
|
180
|
+
lines.push('\u26A0\uFE0F Running in INSECURE mode — any user can issue');
|
|
181
|
+
lines.push('\u26A0\uFE0F privileged commands without verification.');
|
|
182
|
+
lines.push('\u26A0\uFE0F Set ADMIN_PUBKEY or add admin_pubkey to your skill file.');
|
|
183
|
+
}
|
|
184
|
+
// Exposed secrets
|
|
185
|
+
if (result.warnings.length > 0) {
|
|
186
|
+
lines.push('');
|
|
187
|
+
lines.push('\u26A0\uFE0F Potential secrets found in environment:');
|
|
188
|
+
for (const w of result.warnings) {
|
|
189
|
+
lines.push(` ${w.name} = ${w.redacted}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push(' These should be managed by agentauth proxy,');
|
|
193
|
+
lines.push(' not passed directly as env vars.');
|
|
194
|
+
}
|
|
195
|
+
// Identity file permissions
|
|
196
|
+
if (result.identityIssues.length > 0) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push('\u26A0\uFE0F Identity file issues:');
|
|
199
|
+
for (const issue of result.identityIssues) {
|
|
200
|
+
lines.push(` ${issue}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
lines.push('');
|
|
204
|
+
lines.push('='.repeat(60));
|
|
205
|
+
lines.push('');
|
|
206
|
+
console.error(lines.join('\n'));
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=env-doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-doctor.js","sourceRoot":"","sources":["../../lib/env-doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HNSW - Hierarchical Navigable Small World
|
|
3
|
+
* Pure TypeScript, zero deps, base64 binary serialization.
|
|
4
|
+
*
|
|
5
|
+
* Based on: Malkov & Yashunin, "Efficient and robust approximate nearest
|
|
6
|
+
* neighbor search using Hierarchical Navigable Small World graphs" (2018)
|
|
7
|
+
*/
|
|
8
|
+
export interface HNSWConfig {
|
|
9
|
+
/** Vector dimensionality */
|
|
10
|
+
dim: number;
|
|
11
|
+
/** Max connections per node per layer (default: 16) */
|
|
12
|
+
M: number;
|
|
13
|
+
/** Max connections for layer 0 (default: 2*M) */
|
|
14
|
+
M0: number;
|
|
15
|
+
/** Size of dynamic candidate list during construction (default: 200) */
|
|
16
|
+
efConstruction: number;
|
|
17
|
+
/** Normalization factor for level generation: 1/ln(M) */
|
|
18
|
+
mL: number;
|
|
19
|
+
}
|
|
20
|
+
export interface HNSWNode {
|
|
21
|
+
id: number;
|
|
22
|
+
vector: Float32Array;
|
|
23
|
+
level: number;
|
|
24
|
+
/** neighbors[layer] = array of neighbor node ids */
|
|
25
|
+
neighbors: number[][];
|
|
26
|
+
/** Optional metadata attached to the node */
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface SearchResult {
|
|
30
|
+
id: number;
|
|
31
|
+
distance: number;
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/** Cosine distance = 1 - cosine_similarity. Range [0, 2]. */
|
|
35
|
+
export declare function cosineDistance(a: Float32Array, b: Float32Array): number;
|
|
36
|
+
/** Euclidean (L2) distance squared — skip sqrt for perf since we only compare. */
|
|
37
|
+
export declare function euclideanDistance(a: Float32Array, b: Float32Array): number;
|
|
38
|
+
export type DistanceFunction = (a: Float32Array, b: Float32Array) => number;
|
|
39
|
+
export declare class HNSW {
|
|
40
|
+
private config;
|
|
41
|
+
private nodes;
|
|
42
|
+
private entryPointId;
|
|
43
|
+
private maxLevel;
|
|
44
|
+
private nextId;
|
|
45
|
+
private distanceFn;
|
|
46
|
+
/** Optional callback fired after every insert — use for write-through persistence. */
|
|
47
|
+
onInsert?: (index: HNSW) => void;
|
|
48
|
+
constructor(dim: number, opts?: Partial<Pick<HNSWConfig, 'M' | 'efConstruction'>> & {
|
|
49
|
+
distance?: DistanceFunction;
|
|
50
|
+
});
|
|
51
|
+
get size(): number;
|
|
52
|
+
get dimensions(): number;
|
|
53
|
+
private randomLevel;
|
|
54
|
+
/** Greedy search on a single layer. Returns ef closest nodes. */
|
|
55
|
+
private searchLayer;
|
|
56
|
+
private selectNeighbors;
|
|
57
|
+
insert(vector: number[] | Float32Array, metadata?: Record<string, unknown>): number;
|
|
58
|
+
search(query: number[] | Float32Array, k?: number, efSearch?: number): SearchResult[];
|
|
59
|
+
getNode(id: number): HNSWNode | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Binary layout:
|
|
62
|
+
* [Header: 24 bytes]
|
|
63
|
+
* magic (4 bytes): "HNSW"
|
|
64
|
+
* version (4 bytes): 1
|
|
65
|
+
* dim (4 bytes)
|
|
66
|
+
* nodeCount (4 bytes)
|
|
67
|
+
* entryPointId (4 bytes, signed)
|
|
68
|
+
* maxLevel (4 bytes, signed)
|
|
69
|
+
*
|
|
70
|
+
* [Config: 16 bytes]
|
|
71
|
+
* M (4 bytes)
|
|
72
|
+
* M0 (4 bytes)
|
|
73
|
+
* efConstruction (4 bytes)
|
|
74
|
+
* nextId (4 bytes)
|
|
75
|
+
*
|
|
76
|
+
* [Nodes: variable]
|
|
77
|
+
* For each node:
|
|
78
|
+
* id (4 bytes)
|
|
79
|
+
* level (4 bytes)
|
|
80
|
+
* metadataLength (4 bytes) — byte length of JSON metadata string
|
|
81
|
+
* metadata (metadataLength bytes) — UTF-8 JSON
|
|
82
|
+
* vector (dim * 4 bytes) — float32
|
|
83
|
+
* For each layer 0..level:
|
|
84
|
+
* neighborCount (4 bytes)
|
|
85
|
+
* neighborIds (neighborCount * 4 bytes)
|
|
86
|
+
*/
|
|
87
|
+
serialize(): string;
|
|
88
|
+
static deserialize(base64: string, distance?: DistanceFunction): HNSW;
|
|
89
|
+
}
|
|
90
|
+
export default HNSW;
|
|
91
|
+
//# sourceMappingURL=hnsw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hnsw.d.ts","sourceRoot":"","sources":["../../lib/hnsw.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,CAAC,EAAE,MAAM,CAAC;IACV,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAID,6DAA6D;AAC7D,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAUvE;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAO1E;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,KAAK,MAAM,CAAC;AA6G5E,qBAAa,IAAI;IACf,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAmB;IAErC,sFAAsF;IAC/E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;gBAGtC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,GAAG,gBAAgB,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAA;KAAO;IAahG,IAAI,IAAI,IAAI,MAAM,CAA4B;IAC9C,IAAI,UAAU,IAAI,MAAM,CAA4B;IAIpD,OAAO,CAAC,WAAW;IAQnB,iEAAiE;IACjE,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,eAAe;IAQvB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IA0EnF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC,GAAE,MAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IA4BzF,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAMzC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,SAAS,IAAI,MAAM;IAsEnB,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI;CAoEtE;AA+BD,eAAe,IAAI,CAAC"}
|