@fronx/use-claude-code 0.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.
Files changed (58) hide show
  1. package/README.md +273 -0
  2. package/dist/client/connection.d.ts +101 -0
  3. package/dist/client/connection.d.ts.map +1 -0
  4. package/dist/client/connection.js +379 -0
  5. package/dist/client/connection.js.map +1 -0
  6. package/dist/client/event-emitter.d.ts +32 -0
  7. package/dist/client/event-emitter.d.ts.map +1 -0
  8. package/dist/client/event-emitter.js +72 -0
  9. package/dist/client/event-emitter.js.map +1 -0
  10. package/dist/client/index.d.ts +30 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +29 -0
  13. package/dist/client/index.js.map +1 -0
  14. package/dist/client/state-machine.d.ts +61 -0
  15. package/dist/client/state-machine.d.ts.map +1 -0
  16. package/dist/client/state-machine.js +292 -0
  17. package/dist/client/state-machine.js.map +1 -0
  18. package/dist/client/stream-processor.d.ts +46 -0
  19. package/dist/client/stream-processor.d.ts.map +1 -0
  20. package/dist/client/stream-processor.js +173 -0
  21. package/dist/client/stream-processor.js.map +1 -0
  22. package/dist/react/hook.d.ts +55 -0
  23. package/dist/react/hook.d.ts.map +1 -0
  24. package/dist/react/hook.js +132 -0
  25. package/dist/react/hook.js.map +1 -0
  26. package/dist/react/index.d.ts +49 -0
  27. package/dist/react/index.d.ts.map +1 -0
  28. package/dist/react/index.js +48 -0
  29. package/dist/react/index.js.map +1 -0
  30. package/dist/server/claude-spawn.d.ts +52 -0
  31. package/dist/server/claude-spawn.d.ts.map +1 -0
  32. package/dist/server/claude-spawn.js +147 -0
  33. package/dist/server/claude-spawn.js.map +1 -0
  34. package/dist/server/conversation.d.ts +113 -0
  35. package/dist/server/conversation.d.ts.map +1 -0
  36. package/dist/server/conversation.js +520 -0
  37. package/dist/server/conversation.js.map +1 -0
  38. package/dist/server/index.d.ts +35 -0
  39. package/dist/server/index.d.ts.map +1 -0
  40. package/dist/server/index.js +39 -0
  41. package/dist/server/index.js.map +1 -0
  42. package/dist/server/sse-emitter.d.ts +42 -0
  43. package/dist/server/sse-emitter.d.ts.map +1 -0
  44. package/dist/server/sse-emitter.js +56 -0
  45. package/dist/server/sse-emitter.js.map +1 -0
  46. package/dist/shared/index.d.ts +3 -0
  47. package/dist/shared/index.d.ts.map +1 -0
  48. package/dist/shared/index.js +3 -0
  49. package/dist/shared/index.js.map +1 -0
  50. package/dist/shared/tool-utils.d.ts +25 -0
  51. package/dist/shared/tool-utils.d.ts.map +1 -0
  52. package/dist/shared/tool-utils.js +49 -0
  53. package/dist/shared/tool-utils.js.map +1 -0
  54. package/dist/shared/types.d.ts +82 -0
  55. package/dist/shared/types.d.ts.map +1 -0
  56. package/dist/shared/types.js +5 -0
  57. package/dist/shared/types.js.map +1 -0
  58. package/package.json +40 -0
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Chat state machine - handles message accumulation and streaming state correctly.
3
+ *
4
+ * Key insight: when Claude runs tools, the `result` event only contains
5
+ * the FINAL portion of the response. But `assistant` events stream the
6
+ * FULL content. We must use the accumulated streaming content, not the
7
+ * result payload.
8
+ *
9
+ * This module is pure - no React, no DOM, no side effects.
10
+ */
11
+ let messageCounter = 0;
12
+ function generateMessageId(prefix) {
13
+ return `${prefix}-${Date.now()}-${++messageCounter}`;
14
+ }
15
+ /**
16
+ * Create the initial state for a new connection.
17
+ */
18
+ export function createInitialState() {
19
+ return {
20
+ messages: [],
21
+ status: 'checking',
22
+ isStreaming: false,
23
+ streamingSegments: [],
24
+ hasPersistedSession: false,
25
+ lastError: null,
26
+ currentSessionId: null,
27
+ };
28
+ }
29
+ /**
30
+ * Create state for when starting a new conversation.
31
+ * Optionally includes a user message if provided.
32
+ */
33
+ export function createStartingState(state, userMessage) {
34
+ const messages = [];
35
+ if (userMessage) {
36
+ messages.push({
37
+ id: 'user-start',
38
+ role: 'user',
39
+ content: userMessage,
40
+ timestamp: new Date(),
41
+ });
42
+ }
43
+ messages.push({
44
+ id: 'system-starting',
45
+ role: 'system',
46
+ content: 'Starting session...',
47
+ timestamp: new Date(),
48
+ });
49
+ return {
50
+ ...state,
51
+ messages,
52
+ status: 'connecting',
53
+ isStreaming: true,
54
+ streamingSegments: [],
55
+ lastError: null,
56
+ };
57
+ }
58
+ /**
59
+ * Process an SSE event and return the new state.
60
+ * Pure function - no side effects.
61
+ *
62
+ * @param state - Current state
63
+ * @param event - SSE event to process
64
+ * @param needNewTextSegment - Whether next text should start a new segment (after tool use)
65
+ * @returns Tuple of [newState, needNewTextSegment]
66
+ */
67
+ export function processEvent(state, event, needNewTextSegment) {
68
+ // Filter stale events by session ID
69
+ if ('sessionId' in event && event.sessionId && state.currentSessionId) {
70
+ if (event.sessionId !== state.currentSessionId) {
71
+ return [state, needNewTextSegment];
72
+ }
73
+ }
74
+ switch (event.type) {
75
+ case 'init': {
76
+ return [
77
+ {
78
+ ...state,
79
+ currentSessionId: event.sessionId,
80
+ status: 'connected',
81
+ },
82
+ needNewTextSegment,
83
+ ];
84
+ }
85
+ case 'history': {
86
+ // Only apply history if we don't have messages (reconnection case)
87
+ if (state.messages.length > 0) {
88
+ return [
89
+ {
90
+ ...state,
91
+ currentSessionId: event.sessionId,
92
+ },
93
+ needNewTextSegment,
94
+ ];
95
+ }
96
+ return [
97
+ {
98
+ ...state,
99
+ currentSessionId: event.sessionId,
100
+ messages: event.messages.map(m => ({
101
+ ...m,
102
+ timestamp: m.timestamp ? new Date(m.timestamp) : undefined,
103
+ })),
104
+ },
105
+ needNewTextSegment,
106
+ ];
107
+ }
108
+ case 'assistant': {
109
+ const segments = [...state.streamingSegments];
110
+ if (needNewTextSegment ||
111
+ segments.length === 0 ||
112
+ segments[segments.length - 1].type !== 'text') {
113
+ // Start new text segment
114
+ segments.push({ type: 'text', content: event.text });
115
+ }
116
+ else {
117
+ // Append to existing text segment
118
+ const lastSegment = segments[segments.length - 1];
119
+ if (lastSegment.type === 'text') {
120
+ segments[segments.length - 1] = {
121
+ type: 'text',
122
+ content: lastSegment.content + event.text,
123
+ };
124
+ }
125
+ }
126
+ return [
127
+ {
128
+ ...state,
129
+ isStreaming: true,
130
+ streamingSegments: segments,
131
+ },
132
+ false, // needNewTextSegment = false after text
133
+ ];
134
+ }
135
+ case 'tool': {
136
+ const segments = [...state.streamingSegments];
137
+ segments.push({
138
+ type: 'tool',
139
+ name: event.name,
140
+ param: event.param,
141
+ });
142
+ return [
143
+ {
144
+ ...state,
145
+ isStreaming: true,
146
+ streamingSegments: segments,
147
+ },
148
+ true, // needNewTextSegment = true after tool
149
+ ];
150
+ }
151
+ case 'result': {
152
+ // CRITICAL: Use accumulated streaming content, NOT event.result!
153
+ // When Claude runs tools, event.result only has the final portion,
154
+ // but streamingSegments has the full response.
155
+ const textContent = state.streamingSegments
156
+ .filter((s) => s.type === 'text')
157
+ .map(s => s.content)
158
+ .join('\n\n');
159
+ const content = textContent || event.result;
160
+ // Remove the "Starting session..." system message and add assistant response
161
+ const messages = [
162
+ ...state.messages.filter(m => m.id !== 'system-starting'),
163
+ {
164
+ id: generateMessageId('assistant'),
165
+ role: 'assistant',
166
+ content,
167
+ timestamp: new Date(),
168
+ },
169
+ ];
170
+ return [
171
+ {
172
+ ...state,
173
+ messages,
174
+ isStreaming: false,
175
+ streamingSegments: [],
176
+ },
177
+ true, // needNewTextSegment = true after result
178
+ ];
179
+ }
180
+ case 'user': {
181
+ // Usually we add user messages optimistically, so skip echoes.
182
+ // But if this is from history replay, it would be handled in 'history' event.
183
+ return [state, needNewTextSegment];
184
+ }
185
+ case 'error': {
186
+ return [
187
+ {
188
+ ...state,
189
+ lastError: event.error,
190
+ isStreaming: false,
191
+ },
192
+ needNewTextSegment,
193
+ ];
194
+ }
195
+ case 'closed': {
196
+ return [
197
+ {
198
+ ...state,
199
+ status: 'disconnected',
200
+ isStreaming: false,
201
+ },
202
+ needNewTextSegment,
203
+ ];
204
+ }
205
+ case 'cleared': {
206
+ return [
207
+ {
208
+ ...createInitialState(),
209
+ status: 'disconnected',
210
+ },
211
+ true,
212
+ ];
213
+ }
214
+ case 'stopped': {
215
+ // Convert any accumulated segments to a message (partial response)
216
+ let messages = state.messages;
217
+ if (state.streamingSegments.length > 0) {
218
+ const textContent = state.streamingSegments
219
+ .filter((s) => s.type === 'text')
220
+ .map(s => s.content)
221
+ .join('\n\n');
222
+ if (textContent) {
223
+ messages = [
224
+ ...messages,
225
+ {
226
+ id: generateMessageId('assistant'),
227
+ role: 'assistant',
228
+ content: textContent + '\n\n*(stopped)*',
229
+ timestamp: new Date(),
230
+ },
231
+ ];
232
+ }
233
+ }
234
+ return [
235
+ {
236
+ ...state,
237
+ messages,
238
+ isStreaming: false,
239
+ streamingSegments: [],
240
+ },
241
+ true, // needNewTextSegment = true after stopped
242
+ ];
243
+ }
244
+ default:
245
+ return [state, needNewTextSegment];
246
+ }
247
+ }
248
+ /**
249
+ * Add a user message to the state (for optimistic updates).
250
+ */
251
+ export function addUserMessage(state, content) {
252
+ return {
253
+ ...state,
254
+ messages: [
255
+ ...state.messages,
256
+ {
257
+ id: generateMessageId('user'),
258
+ role: 'user',
259
+ content,
260
+ timestamp: new Date(),
261
+ },
262
+ ],
263
+ };
264
+ }
265
+ /**
266
+ * Update status.
267
+ */
268
+ export function setStatus(state, status) {
269
+ return { ...state, status };
270
+ }
271
+ /**
272
+ * Set persisted session flag.
273
+ */
274
+ export function setHasPersistedSession(state, hasPersistedSession) {
275
+ return { ...state, hasPersistedSession };
276
+ }
277
+ /**
278
+ * Clear session ID (used before clear() to reject stale events).
279
+ */
280
+ export function clearSessionId(state) {
281
+ return { ...state, currentSessionId: null };
282
+ }
283
+ /**
284
+ * Full state reset for clear operations.
285
+ */
286
+ export function resetState() {
287
+ return {
288
+ ...createInitialState(),
289
+ status: 'disconnected',
290
+ };
291
+ }
292
+ //# sourceMappingURL=state-machine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.js","sourceRoot":"","sources":["../../src/client/state-machine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * StreamProcessor - Unified SSE parsing for both POST response body and EventSource.
3
+ *
4
+ * This abstraction eliminates duplicated event handling between:
5
+ * - start()/resume() POST requests with SSE response body
6
+ * - EventSource connection for ongoing updates
7
+ */
8
+ import type { SSEEvent } from '../shared/types.js';
9
+ export type StreamEventHandler = (event: SSEEvent) => void;
10
+ /**
11
+ * Parses SSE-formatted data and emits typed events.
12
+ */
13
+ export declare class StreamProcessor {
14
+ private onEvent;
15
+ private buffer;
16
+ private currentEventType;
17
+ constructor(onEvent: StreamEventHandler);
18
+ /**
19
+ * Process a chunk of SSE data from a fetch response body.
20
+ * Handles buffering for partial lines.
21
+ */
22
+ processChunk(chunk: string): void;
23
+ /**
24
+ * Process an EventSource message event.
25
+ * The event type comes from the event property, data from the data property.
26
+ */
27
+ processMessageEvent(event: MessageEvent, eventType?: string): void;
28
+ /**
29
+ * Flush any remaining data in the buffer.
30
+ * Call this when the stream ends.
31
+ */
32
+ flush(): void;
33
+ /**
34
+ * Reset the processor state.
35
+ */
36
+ reset(): void;
37
+ private processLine;
38
+ /**
39
+ * Infer event type from data structure when not explicitly provided.
40
+ * Used for POST response streams that may not include event: lines.
41
+ */
42
+ private inferEventType;
43
+ private emitEvent;
44
+ private createEvent;
45
+ }
46
+ //# sourceMappingURL=stream-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-processor.d.ts","sourceRoot":"","sources":["../../src/client/stream-processor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAE3D;;GAEG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,gBAAgB,CAAuB;gBAE3B,OAAO,EAAE,kBAAkB;IAE/C;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWjC;;;OAGG;IACH,mBAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAclE;;;OAGG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,WAAW;IAoBnB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,WAAW;CAoEpB"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * StreamProcessor - Unified SSE parsing for both POST response body and EventSource.
3
+ *
4
+ * This abstraction eliminates duplicated event handling between:
5
+ * - start()/resume() POST requests with SSE response body
6
+ * - EventSource connection for ongoing updates
7
+ */
8
+ /**
9
+ * Parses SSE-formatted data and emits typed events.
10
+ */
11
+ export class StreamProcessor {
12
+ onEvent;
13
+ buffer = '';
14
+ currentEventType = null;
15
+ constructor(onEvent) {
16
+ this.onEvent = onEvent;
17
+ }
18
+ /**
19
+ * Process a chunk of SSE data from a fetch response body.
20
+ * Handles buffering for partial lines.
21
+ */
22
+ processChunk(chunk) {
23
+ this.buffer += chunk;
24
+ const lines = this.buffer.split('\n');
25
+ // Keep the last incomplete line in the buffer
26
+ this.buffer = lines.pop() || '';
27
+ for (const line of lines) {
28
+ this.processLine(line);
29
+ }
30
+ }
31
+ /**
32
+ * Process an EventSource message event.
33
+ * The event type comes from the event property, data from the data property.
34
+ */
35
+ processMessageEvent(event, eventType) {
36
+ const type = eventType || 'message';
37
+ const data = event.data;
38
+ if (!data)
39
+ return;
40
+ try {
41
+ const parsed = JSON.parse(data);
42
+ this.emitEvent(type, parsed);
43
+ }
44
+ catch {
45
+ // Ignore parse errors
46
+ }
47
+ }
48
+ /**
49
+ * Flush any remaining data in the buffer.
50
+ * Call this when the stream ends.
51
+ */
52
+ flush() {
53
+ if (this.buffer.trim()) {
54
+ this.processLine(this.buffer);
55
+ this.buffer = '';
56
+ }
57
+ }
58
+ /**
59
+ * Reset the processor state.
60
+ */
61
+ reset() {
62
+ this.buffer = '';
63
+ this.currentEventType = null;
64
+ }
65
+ processLine(line) {
66
+ // SSE format: "event: type\ndata: json\n\n"
67
+ if (line.startsWith('event: ')) {
68
+ this.currentEventType = line.slice(7).trim();
69
+ }
70
+ else if (line.startsWith('data: ')) {
71
+ const data = line.slice(6);
72
+ try {
73
+ const parsed = JSON.parse(data);
74
+ const type = this.currentEventType || this.inferEventType(parsed);
75
+ this.emitEvent(type, parsed);
76
+ this.currentEventType = null;
77
+ }
78
+ catch {
79
+ // Ignore parse errors
80
+ }
81
+ }
82
+ else if (line === '') {
83
+ // Empty line signals end of event - reset event type
84
+ this.currentEventType = null;
85
+ }
86
+ }
87
+ /**
88
+ * Infer event type from data structure when not explicitly provided.
89
+ * Used for POST response streams that may not include event: lines.
90
+ */
91
+ inferEventType(data) {
92
+ if ('messages' in data && 'sessionId' in data)
93
+ return 'history';
94
+ if ('text' in data)
95
+ return 'assistant';
96
+ if ('name' in data && !('result' in data))
97
+ return 'tool';
98
+ if ('result' in data)
99
+ return 'result';
100
+ if ('content' in data && !('role' in data))
101
+ return 'user';
102
+ if ('error' in data)
103
+ return 'error';
104
+ if ('sessionId' in data && Object.keys(data).length <= 3)
105
+ return 'init';
106
+ return 'unknown';
107
+ }
108
+ emitEvent(type, data) {
109
+ const event = this.createEvent(type, data);
110
+ if (event) {
111
+ this.onEvent(event);
112
+ }
113
+ }
114
+ createEvent(type, data) {
115
+ switch (type) {
116
+ case 'init':
117
+ return {
118
+ type: 'init',
119
+ sessionId: data.sessionId,
120
+ resumed: data.resumed,
121
+ prewarmed: data.prewarmed,
122
+ };
123
+ case 'history':
124
+ return {
125
+ type: 'history',
126
+ messages: data.messages,
127
+ sessionId: data.sessionId,
128
+ };
129
+ case 'assistant':
130
+ return {
131
+ type: 'assistant',
132
+ text: data.text,
133
+ sessionId: data.sessionId,
134
+ };
135
+ case 'tool':
136
+ return {
137
+ type: 'tool',
138
+ name: data.name,
139
+ param: data.param,
140
+ sessionId: data.sessionId,
141
+ };
142
+ case 'result':
143
+ return {
144
+ type: 'result',
145
+ result: data.result,
146
+ sessionId: data.sessionId,
147
+ };
148
+ case 'user':
149
+ return {
150
+ type: 'user',
151
+ content: data.content,
152
+ sessionId: data.sessionId,
153
+ };
154
+ case 'error':
155
+ return {
156
+ type: 'error',
157
+ error: data.error,
158
+ };
159
+ case 'closed':
160
+ return {
161
+ type: 'closed',
162
+ code: data.code,
163
+ };
164
+ case 'cleared':
165
+ return { type: 'cleared' };
166
+ case 'stopped':
167
+ return { type: 'stopped' };
168
+ default:
169
+ return null;
170
+ }
171
+ }
172
+ }
173
+ //# sourceMappingURL=stream-processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-processor.js","sourceRoot":"","sources":["../../src/client/stream-processor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * React hook for Claude Code conversation management.
3
+ *
4
+ * Thin wrapper over ClaudeConnection that converts events to React state updates.
5
+ */
6
+ import type { Message, StreamSegment, ClaudeError, ReconnectConfig } from '../shared/types.js';
7
+ import type { ConnectionStatus } from '../client/state-machine.js';
8
+ export interface UseClaudeConversationOptions {
9
+ baseUrl: string;
10
+ conversationId: string;
11
+ autoConnect?: boolean;
12
+ reconnect?: ReconnectConfig;
13
+ onResult?: () => void;
14
+ }
15
+ export interface UseClaudeConversationReturn {
16
+ messages: Message[];
17
+ status: ConnectionStatus;
18
+ isStreaming: boolean;
19
+ streamingSegments: StreamSegment[];
20
+ hasPersistedSession: boolean;
21
+ lastError: ClaudeError | null;
22
+ start: (initialMessage?: string) => Promise<void>;
23
+ send: (message: string) => Promise<void>;
24
+ stop: () => Promise<void>;
25
+ clear: () => Promise<void>;
26
+ resume: () => Promise<void>;
27
+ prewarm: () => Promise<void>;
28
+ }
29
+ /**
30
+ * Hook for managing a Claude conversation in React.
31
+ *
32
+ * @example
33
+ * function Chat({ projectId }) {
34
+ * const {
35
+ * messages,
36
+ * status,
37
+ * isStreaming,
38
+ * streamingSegments,
39
+ * hasPersistedSession,
40
+ * start,
41
+ * send,
42
+ * stop,
43
+ * clear,
44
+ * resume,
45
+ * } = useClaudeConversation({
46
+ * baseUrl: '/api/claude',
47
+ * conversationId: projectId,
48
+ * onResult: () => console.log('Response complete'),
49
+ * });
50
+ *
51
+ * // Render messages, input, etc.
52
+ * }
53
+ */
54
+ export declare function useClaudeConversation(options: UseClaudeConversationOptions): UseClaudeConversationReturn;
55
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/react/hook.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAE1C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,aAAa,EAAE,CAAC;IACnC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAG9B,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAuH7B"}