@thenewlabs/entangle-serve 2.1.0

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.
@@ -0,0 +1,10 @@
1
+ import { type CapabilityInfo } from './capability.js';
2
+ interface AgentOptions {
3
+ serverUrl: string;
4
+ outputMode?: string;
5
+ password?: string;
6
+ pinnedCapability?: CapabilityInfo;
7
+ }
8
+ export declare function startAgent(options: AgentOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAExE,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,cAAc,CAAC;CACnC;AAcD,wBAAsB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrE"}
package/dist/agent.js ADDED
@@ -0,0 +1,167 @@
1
+ import WebSocket from 'ws';
2
+ import { randomBytes } from 'crypto';
3
+ import { getConfig, OutputHandler, parseOutputMode } from '@thenewlabs/entangle-utils';
4
+ import { hashPassword } from '@thenewlabs/entangle-crypto';
5
+ import { handleInvokerConnection } from './session.js';
6
+ import { createCapability } from './capability.js';
7
+ export async function startAgent(options) {
8
+ const output = new OutputHandler({ mode: parseOutputMode(options.outputMode || process.env.OUTPUT_MODE || 'text') });
9
+ const config = getConfig();
10
+ const state = {
11
+ capabilities: new Map(),
12
+ serverUrl: options.serverUrl,
13
+ output,
14
+ ...(options.outputMode && { outputMode: options.outputMode }),
15
+ ...(options.password && { password: options.password }),
16
+ };
17
+ output.info('Starting agent');
18
+ // Hash password if provided (Argon2id with a random salt).
19
+ if (state.password) {
20
+ state.passwordHash = await hashPassword(state.password);
21
+ output.info('Password protection enabled');
22
+ }
23
+ // Exactly one capability is served: a pinned one supplied by the caller, or a
24
+ // fresh ephemeral one minted in memory (never written to disk).
25
+ let cap;
26
+ if (options.pinnedCapability) {
27
+ cap = options.pinnedCapability;
28
+ state.pinned = true;
29
+ }
30
+ else {
31
+ cap = await createCapability({
32
+ singleRun: false,
33
+ ...(state.outputMode && { outputMode: state.outputMode }),
34
+ });
35
+ }
36
+ state.capabilities.set(cap.capId, cap);
37
+ await connectToServer(state, options.serverUrl);
38
+ setInterval(() => {
39
+ sendHeartbeat(state);
40
+ }, config.agentHeartbeatMs || 15000);
41
+ process.on('SIGINT', () => {
42
+ state.output.info('Shutting down');
43
+ state.ws?.close();
44
+ process.exit(0);
45
+ });
46
+ }
47
+ async function connectToServer(state, serverUrl) {
48
+ const wsUrl = serverUrl.replace(/^http/, 'ws') + '/agent/register';
49
+ state.output.info(`Connecting to server: ${wsUrl}`);
50
+ const ws = new WebSocket(wsUrl);
51
+ state.ws = ws;
52
+ ws.on('open', () => {
53
+ state.output.info('Connected to server');
54
+ const hello = {
55
+ type: 'CLIENT_HELLO',
56
+ machineId: getMachineId(),
57
+ // Shared secret gating agent registration (RELAY_AGENT_TOKEN). Undefined
58
+ // when the relay does not require one.
59
+ token: getConfig().agentToken,
60
+ };
61
+ ws.send(JSON.stringify(hello));
62
+ });
63
+ // Store active relay sessions
64
+ const relaySessions = new Map();
65
+ ws.on('message', async (data) => {
66
+ try {
67
+ const msg = JSON.parse(data.toString());
68
+ if (msg.type === 'ASSIGN') {
69
+ state.agentId = msg.agentId;
70
+ state.output.info(`Agent registered: ${msg.agentId}`);
71
+ // Display the single served capability (pinned or ephemeral).
72
+ displayCapability(state);
73
+ // Announce all capabilities
74
+ for (const capId of state.capabilities.keys()) {
75
+ announceCapability(state, capId);
76
+ }
77
+ }
78
+ else if (msg.type === 'INVOKER_CONNECT') {
79
+ const { capId, socketId } = msg;
80
+ const cap = state.capabilities.get(capId);
81
+ if (!cap) {
82
+ state.output.warn(`Unknown capability: ${capId}`);
83
+ return;
84
+ }
85
+ state.output.info(`Invoker connected: socketId=${socketId}, capId=${capId}`);
86
+ // Register synchronously (no await) so the session exists before this
87
+ // invoker's AUTH1 arrives on the next relay message.
88
+ const session = handleInvokerConnection(state.ws, socketId, cap, state.passwordHash);
89
+ relaySessions.set(socketId, session);
90
+ }
91
+ else if (msg.type === 'RELAY_MSG') {
92
+ // Handle forwarded frame from invoker
93
+ const session = relaySessions.get(msg.socketId);
94
+ if (session && session.handleFrame) {
95
+ const frameData = Buffer.from(msg.frame, 'base64');
96
+ session.handleFrame(frameData);
97
+ }
98
+ }
99
+ else if (msg.type === 'INVOKER_DISCONNECT') {
100
+ // Clean up relay session
101
+ const session = relaySessions.get(msg.socketId);
102
+ if (session && session.cleanup) {
103
+ session.cleanup();
104
+ }
105
+ relaySessions.delete(msg.socketId);
106
+ }
107
+ }
108
+ catch (error) {
109
+ // If it's not JSON, it might be a legacy binary frame - ignore it
110
+ if (data instanceof Buffer) {
111
+ return;
112
+ }
113
+ state.output.error('Failed to handle message', error instanceof Error ? error.message : String(error));
114
+ }
115
+ });
116
+ ws.on('error', (error) => {
117
+ state.output.error('WebSocket error', error instanceof Error ? error.message : String(error));
118
+ });
119
+ ws.on('close', () => {
120
+ state.output.info('Disconnected from server');
121
+ setTimeout(() => connectToServer(state, serverUrl), 5000);
122
+ });
123
+ }
124
+ function displayCapability(state) {
125
+ const config = getConfig();
126
+ const relayUrl = config.relayUrl || state.serverUrl || config.publicOrigin || 'https://suncoder.dev';
127
+ // There is exactly one capability. Print its full URL block including the
128
+ // secret S: this capability is ephemeral (or explicitly pinned by the
129
+ // operator) and the secret is intentionally shown here so it can be copied
130
+ // and handed to an invoker — without it the capability is unusable.
131
+ const cap = state.capabilities.values().next().value;
132
+ if (!cap)
133
+ return;
134
+ const link = `${relayUrl}/cap/${cap.capId}#S=${cap.S}`;
135
+ state.output.info('');
136
+ state.output.info('=====================================');
137
+ state.output.info(state.pinned ? 'Using pinned capability' : 'Ephemeral capability created (not stored)');
138
+ state.output.info(`capId: ${cap.capId}`);
139
+ state.output.info(`S: ${cap.S}`);
140
+ state.output.info('');
141
+ state.output.info(`Web URL: ${link}`);
142
+ state.output.info('=====================================');
143
+ state.output.info('');
144
+ }
145
+ function announceCapability(state, capId) {
146
+ if (!state.ws)
147
+ return;
148
+ const announce = {
149
+ type: 'ANNOUNCE_CAP',
150
+ capId,
151
+ };
152
+ state.ws.send(JSON.stringify(announce));
153
+ state.output.info(`Capability announced: ${capId}`);
154
+ }
155
+ function sendHeartbeat(state) {
156
+ if (!state.ws || state.ws.readyState !== WebSocket.OPEN)
157
+ return;
158
+ const heartbeat = {
159
+ type: 'HEARTBEAT',
160
+ timestamp: Date.now(),
161
+ };
162
+ state.ws.send(JSON.stringify(heartbeat));
163
+ }
164
+ function getMachineId() {
165
+ return randomBytes(16).toString('hex');
166
+ }
167
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type Policy } from '@thenewlabs/entangle-protocol';
2
+ export interface CapabilityInfo {
3
+ capId: string;
4
+ S: string;
5
+ policy: Policy;
6
+ }
7
+ export declare function createCapability(options: {
8
+ singleRun?: boolean;
9
+ outputMode?: string;
10
+ }): Promise<CapabilityInfo>;
11
+ /**
12
+ * Parse a capability URL into a CapabilityInfo. Accepts either a full URL of
13
+ * the form `https://host[:port]/cap/<capId>#S=<secret>` or the compact form
14
+ * `<capId>#S=<secret>`. For a full URL, `relayOrigin` is set to the URL origin
15
+ * so the caller can point the agent at the same relay that minted the
16
+ * capability.
17
+ */
18
+ export declare function parseCapabilityUrl(input: string): Promise<CapabilityInfo & {
19
+ relayOrigin?: string;
20
+ }>;
21
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,cAAc,CAAC,CAgB1B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,cAAc,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwDpD"}
@@ -0,0 +1,75 @@
1
+ import { generateCapId, generateSecret, initCrypto, extractSaltFromCapId } from '@thenewlabs/entangle-crypto';
2
+ export async function createCapability(options) {
3
+ await initCrypto();
4
+ const { capId } = generateCapId();
5
+ const S = generateSecret();
6
+ const policy = {
7
+ singleRun: options.singleRun ?? false,
8
+ maxStreams: 1, // Default to single stream for backward compatibility
9
+ };
10
+ return {
11
+ capId,
12
+ S,
13
+ policy,
14
+ };
15
+ }
16
+ /**
17
+ * Parse a capability URL into a CapabilityInfo. Accepts either a full URL of
18
+ * the form `https://host[:port]/cap/<capId>#S=<secret>` or the compact form
19
+ * `<capId>#S=<secret>`. For a full URL, `relayOrigin` is set to the URL origin
20
+ * so the caller can point the agent at the same relay that minted the
21
+ * capability.
22
+ */
23
+ export async function parseCapabilityUrl(input) {
24
+ await initCrypto();
25
+ let capId;
26
+ let fragment;
27
+ let relayOrigin;
28
+ const hashIndex = input.indexOf('#');
29
+ const beforeHash = hashIndex >= 0 ? input.slice(0, hashIndex) : input;
30
+ fragment = hashIndex >= 0 ? input.slice(hashIndex + 1) : undefined;
31
+ if (/^https?:\/\//i.test(beforeHash)) {
32
+ // Full URL form.
33
+ try {
34
+ const url = new URL(input);
35
+ relayOrigin = url.origin;
36
+ const match = url.pathname.match(/\/cap\/([^/]+)$/);
37
+ if (match) {
38
+ capId = decodeURIComponent(match[1]);
39
+ }
40
+ }
41
+ catch {
42
+ // Fall through to the "expected ..." error below.
43
+ }
44
+ }
45
+ else {
46
+ // Compact form: everything before the '#' is the capId.
47
+ if (beforeHash) {
48
+ capId = beforeHash;
49
+ }
50
+ }
51
+ let secret;
52
+ if (fragment) {
53
+ const params = new URLSearchParams(fragment);
54
+ secret = params.get('S') ?? undefined;
55
+ }
56
+ if (!capId || !secret) {
57
+ throw new Error('Invalid capability URL: expected .../cap/<capId>#S=<secret>');
58
+ }
59
+ try {
60
+ extractSaltFromCapId(capId);
61
+ }
62
+ catch {
63
+ throw new Error('Invalid capability URL: malformed capId');
64
+ }
65
+ if (typeof secret !== 'string' || secret.length < 8) {
66
+ throw new Error('Invalid capability URL: missing or malformed secret');
67
+ }
68
+ return {
69
+ capId,
70
+ S: secret,
71
+ policy: { singleRun: false, maxStreams: 1 },
72
+ ...(relayOrigin !== undefined && { relayOrigin }),
73
+ };
74
+ }
75
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAS9G,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAGtC;IACC,MAAM,UAAU,EAAE,CAAC;IAEnB,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,EAAE,CAAC;IAClC,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;IAE3B,MAAM,MAAM,GAAW;QACrB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK;QACrC,UAAU,EAAE,CAAC,EAAE,sDAAsD;KACtE,CAAC;IAEF,OAAO;QACL,KAAK;QACL,CAAC;QACD,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa;IAEb,MAAM,UAAU,EAAE,CAAC;IAEnB,IAAI,KAAyB,CAAC;IAC9B,IAAI,QAA4B,CAAC;IACjC,IAAI,WAA+B,CAAC;IAEpC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtE,QAAQ,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEnE,IAAI,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACrC,iBAAiB;QACjB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;YAC3B,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC;YACzB,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACpD,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;QACpD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,wDAAwD;QACxD,IAAI,UAAU,EAAE,CAAC;YACf,KAAK,GAAG,UAAU,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,MAA0B,CAAC;IAC/B,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IACxC,CAAC;IAED,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,CAAC;QACH,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,OAAO;QACL,KAAK;QACL,CAAC,EAAE,MAAM;QACT,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE;QAC3C,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { getConfig, getVersionInfo, OutputHandler, parseOutputMode } from '@thenewlabs/entangle-utils';
4
+ import { startAgent } from './agent.js';
5
+ import { createCapability, parseCapabilityUrl } from './capability.js';
6
+ const program = new Command();
7
+ program
8
+ .name('entangle-serve')
9
+ .description('Entangle secure agent for exposing CLI tools')
10
+ .version(getVersionInfo())
11
+ .option('--output-mode <mode>', 'Output mode: text or stream-json', 'text');
12
+ program
13
+ .command('start')
14
+ .description('Start the agent and register with server')
15
+ .option('--server <url>', 'Server URL')
16
+ .option('--password <password>', 'Optional password for agent authentication')
17
+ .option('--capability <url>', 'Serve a specific capability URL (https://relay/cap/<capId>#S=<secret>) instead of minting a fresh ephemeral one; its host is also used as the relay server')
18
+ .action(async (options) => {
19
+ try {
20
+ // Propagate output mode to all loggers in this process
21
+ process.env.OUTPUT_MODE = program.opts().outputMode;
22
+ const outputMode = parseOutputMode(program.opts().outputMode);
23
+ const output = new OutputHandler({ mode: outputMode });
24
+ output.version('Entangle Agent', getVersionInfo());
25
+ const config = getConfig();
26
+ const capUrl = options.capability || process.env.ENTANGLE_CAPABILITY;
27
+ const pinned = capUrl ? await parseCapabilityUrl(capUrl) : undefined;
28
+ // Explicit --server wins, else the capability URL's origin, else config/default.
29
+ const serverUrl = options.server || (pinned && pinned.relayOrigin) || config.relayUrl || 'http://localhost:8080';
30
+ await startAgent({
31
+ serverUrl,
32
+ outputMode: program.opts().outputMode,
33
+ password: options.password || process.env.AGENT_PASSWORD,
34
+ ...(pinned && { pinnedCapability: pinned }),
35
+ });
36
+ }
37
+ catch (error) {
38
+ const outputMode = parseOutputMode(program.opts().outputMode);
39
+ const output = new OutputHandler({ mode: outputMode });
40
+ output.error('Failed to start agent', error instanceof Error ? error.message : String(error));
41
+ process.exit(1);
42
+ }
43
+ });
44
+ program
45
+ .command('create-cap')
46
+ .description('Create a new capability')
47
+ .option('--single-run', 'Allow only one run per session (default: multiple runs allowed)')
48
+ .action(async (options) => {
49
+ try {
50
+ process.env.OUTPUT_MODE = program.opts().outputMode;
51
+ const outputMode = parseOutputMode(program.opts().outputMode);
52
+ const output = new OutputHandler({ mode: outputMode });
53
+ const cap = await createCapability({
54
+ singleRun: options.singleRun,
55
+ outputMode: program.opts().outputMode,
56
+ });
57
+ output.info('\nCapability created:');
58
+ output.info(`capId: ${cap.capId}`);
59
+ output.info(`S: ${cap.S}`);
60
+ const config = getConfig();
61
+ const relayUrl = config.relayUrl || config.publicOrigin || 'https://suncoder.dev';
62
+ const link = `${relayUrl}/cap/${cap.capId}#S=${cap.S}`;
63
+ output.info(`\nWeb URL: ${link}`);
64
+ }
65
+ catch (error) {
66
+ const outputMode = parseOutputMode(program.opts().outputMode);
67
+ const output = new OutputHandler({ mode: outputMode });
68
+ output.error('Failed to create capability', error instanceof Error ? error.message : String(error));
69
+ process.exit(1);
70
+ }
71
+ });
72
+ program.parse();
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import WebSocket from 'ws';
2
+ import { FrameType } from '@thenewlabs/entangle-protocol';
3
+ import { deriveKeys } from '@thenewlabs/entangle-crypto';
4
+ import { BidirectionalCounters, StreamCounters } from '@thenewlabs/entangle-utils';
5
+ import { StreamManager } from './stream-manager.js';
6
+ import type { CapabilityInfo } from './capability.js';
7
+ export interface MultiSession {
8
+ socketId: string;
9
+ ws: WebSocket;
10
+ cap: CapabilityInfo;
11
+ keys?: Awaited<ReturnType<typeof deriveKeys>>;
12
+ counters: BidirectionalCounters;
13
+ streamCounters: StreamCounters;
14
+ authenticated: boolean;
15
+ requiresPassword?: boolean;
16
+ passwordVerified?: boolean;
17
+ nonceB?: string;
18
+ nonceC?: string;
19
+ streamManager?: StreamManager;
20
+ terminated?: boolean;
21
+ }
22
+ export declare function handleMultiStreamFrame(session: MultiSession, frame: {
23
+ type: FrameType;
24
+ payload: Uint8Array;
25
+ }): Promise<void>;
26
+ export declare function cleanupMultiSession(session: MultiSession): void;
27
+ //# sourceMappingURL=multi-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-session.d.ts","sourceRoot":"","sources":["../src/multi-session.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EACL,SAAS,EAWV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,UAAU,EAKX,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGL,qBAAqB,EACrB,cAAc,EACf,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,SAAS,CAAC;IACd,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC;IAC9C,QAAQ,EAAE,qBAAqB,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IAEvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA+QD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GAC9C,OAAO,CAAC,IAAI,CAAC,CAsIf;AAGD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAI/D"}