lyzr-cortex-sdk 0.1.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/README.md ADDED
@@ -0,0 +1,445 @@
1
+ # Cortex SDK for TypeScript
2
+
3
+ Build tools that integrate with the Cortex Platform for bidirectional knowledge flow.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @cortex/sdk
9
+ # or
10
+ yarn add @cortex/sdk
11
+ # or
12
+ pnpm add @cortex/sdk
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### React Integration
18
+
19
+ ```tsx
20
+ import { CortexProvider, useCortex } from '@cortex/sdk';
21
+
22
+ // 1. Wrap your app with CortexProvider
23
+ function App() {
24
+ return (
25
+ <CortexProvider
26
+ apiUrl={process.env.REACT_APP_CORTEX_API_URL}
27
+ apiKey={process.env.REACT_APP_CORTEX_API_KEY}
28
+ >
29
+ <YourApp />
30
+ </CortexProvider>
31
+ );
32
+ }
33
+
34
+ // 2. Use the hook in your components
35
+ function MeetingCard({ meeting }) {
36
+ const { isEmbedded, openChat, showNotification, push } = useCortex();
37
+
38
+ const handleSave = async () => {
39
+ await push({
40
+ id: meeting.id,
41
+ type: 'meeting_transcript',
42
+ content: meeting.transcript,
43
+ name: meeting.title,
44
+ scope: 'team',
45
+ });
46
+ showNotification('Saved to Cortex', 'success');
47
+ };
48
+
49
+ return (
50
+ <div>
51
+ <h3>{meeting.title}</h3>
52
+ <button onClick={handleSave}>Save to Cortex</button>
53
+
54
+ {/* Only show when embedded in Cortex */}
55
+ {isEmbedded && (
56
+ <button onClick={() => openChat(`What action items from "${meeting.title}"?`)}>
57
+ Ask Cortex
58
+ </button>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ### Direct API Client (without React)
66
+
67
+ ```typescript
68
+ import { CortexClient } from '@cortex/sdk';
69
+
70
+ const client = new CortexClient({
71
+ apiUrl: 'https://api.cortex.ai',
72
+ apiKey: 'your-api-key',
73
+ toolId: 'your-tool',
74
+ });
75
+
76
+ // Push a document
77
+ await client.push({
78
+ id: 'doc_123',
79
+ type: 'meeting_transcript',
80
+ content: 'Full transcript...',
81
+ name: 'Product Sync',
82
+ scope: 'team',
83
+ });
84
+
85
+ // Query
86
+ const result = await client.query('What decisions were made about Q1?');
87
+ console.log(result.answer);
88
+ console.log(result.sources);
89
+ ```
90
+
91
+ ### PostMessage Bridge (Low-level)
92
+
93
+ ```typescript
94
+ import { CortexBridge, isEmbeddedInCortex } from '@cortex/sdk';
95
+
96
+ // Check if embedded
97
+ if (isEmbeddedInCortex()) {
98
+ const bridge = new CortexBridge();
99
+
100
+ // Wait for initialization
101
+ const user = await bridge.waitForReady();
102
+ console.log('Cortex user:', user?.email);
103
+
104
+ // Send messages
105
+ bridge.openChat('Hello from my tool!');
106
+ bridge.showNotification('Tool loaded', 'success');
107
+
108
+ // Listen for messages
109
+ bridge.on('CORTEX_THEME', (payload) => {
110
+ document.body.className = payload.theme;
111
+ });
112
+ }
113
+ ```
114
+
115
+ ## Frontend Integration Guide
116
+
117
+ Step-by-step guide for integrating Cortex into your React/TypeScript frontend.
118
+
119
+ ### 1. Create Cortex Provider
120
+
121
+ File: `frontend/lib/cortex.tsx`
122
+ ```tsx
123
+ 'use client';
124
+
125
+ import { createContext, useContext, useEffect, useState, useCallback, useMemo, ReactNode } from 'react';
126
+
127
+ interface CortexUser {
128
+ id: string;
129
+ email: string;
130
+ name?: string;
131
+ orgId: string;
132
+ teams: Array<{ id: string; name: string }>;
133
+ }
134
+
135
+ interface CortexContextValue {
136
+ user: CortexUser | null;
137
+ isEmbedded: boolean;
138
+ isReady: boolean;
139
+ theme: 'light' | 'dark';
140
+ openChat: (query?: string) => void;
141
+ }
142
+
143
+ const CortexContext = createContext<CortexContextValue | null>(null);
144
+
145
+ function isInIframe(): boolean {
146
+ if (typeof window === 'undefined') return false;
147
+ try {
148
+ return window.self !== window.top;
149
+ } catch {
150
+ return true;
151
+ }
152
+ }
153
+
154
+ export function CortexProvider({ children }: { children: ReactNode }) {
155
+ const [isEmbedded] = useState(() => isInIframe());
156
+ const [isReady, setIsReady] = useState(!isInIframe());
157
+ const [user, setUser] = useState<CortexUser | null>(null);
158
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
159
+
160
+ useEffect(() => {
161
+ if (!isEmbedded) return;
162
+
163
+ const handleMessage = (event: MessageEvent) => {
164
+ const message = event.data;
165
+ if (!message?.type) return;
166
+
167
+ if (message.type === 'CORTEX_INIT') {
168
+ setUser(message.payload?.user || null);
169
+ setTheme(message.payload?.theme || 'light');
170
+ setIsReady(true);
171
+ }
172
+
173
+ if (message.type === 'CORTEX_THEME') {
174
+ setTheme(message.payload?.theme || 'light');
175
+ }
176
+ };
177
+
178
+ window.addEventListener('message', handleMessage);
179
+ window.parent.postMessage({ type: 'TOOL_READY' }, '*');
180
+
181
+ const timeout = setTimeout(() => setIsReady(true), 5000);
182
+
183
+ return () => {
184
+ window.removeEventListener('message', handleMessage);
185
+ clearTimeout(timeout);
186
+ };
187
+ }, [isEmbedded]);
188
+
189
+ const openChat = useCallback((query?: string) => {
190
+ if (isEmbedded) {
191
+ window.parent.postMessage({ type: 'OPEN_CHAT', payload: { query } }, '*');
192
+ }
193
+ }, [isEmbedded]);
194
+
195
+ const value = useMemo(() => ({
196
+ user, isEmbedded, isReady, theme, openChat
197
+ }), [user, isEmbedded, isReady, theme, openChat]);
198
+
199
+ return (
200
+ <CortexContext.Provider value={value}>
201
+ {children}
202
+ </CortexContext.Provider>
203
+ );
204
+ }
205
+
206
+ export function useCortex(): CortexContextValue {
207
+ const context = useContext(CortexContext);
208
+ if (!context) {
209
+ return {
210
+ user: null,
211
+ isEmbedded: false,
212
+ isReady: true,
213
+ theme: 'light',
214
+ openChat: () => {},
215
+ };
216
+ }
217
+ return context;
218
+ }
219
+ ```
220
+
221
+ ### 2. Wrap App with Provider
222
+
223
+ File: `frontend/app/providers.tsx`
224
+ ```tsx
225
+ 'use client';
226
+
227
+ import { CortexProvider } from '@/lib/cortex';
228
+
229
+ export function Providers({ children }: { children: React.ReactNode }) {
230
+ return <CortexProvider>{children}</CortexProvider>;
231
+ }
232
+ ```
233
+
234
+ File: `frontend/app/layout.tsx`
235
+ ```tsx
236
+ import { Providers } from './providers';
237
+
238
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
239
+ return (
240
+ <html lang="en">
241
+ <body>
242
+ <Providers>{children}</Providers>
243
+ </body>
244
+ </html>
245
+ );
246
+ }
247
+ ```
248
+
249
+ ### 3. Use in Components
250
+
251
+ ```tsx
252
+ import { useCortex } from '@/lib/cortex';
253
+
254
+ function TranscriptCard({ meeting }) {
255
+ const { isEmbedded, openChat } = useCortex();
256
+
257
+ return (
258
+ <div>
259
+ <h3>{meeting.title}</h3>
260
+ <p>{meeting.transcript}</p>
261
+
262
+ {isEmbedded && (
263
+ <button onClick={() => openChat(`Summarize meeting: ${meeting.title}`)}>
264
+ Ask Cortex
265
+ </button>
266
+ )}
267
+ </div>
268
+ );
269
+ }
270
+ ```
271
+
272
+ ### 4. Embedded Mode Authentication
273
+
274
+ When your tool is embedded in Cortex, the frontend receives the user's OGI JWT via `CORTEX_INIT` postMessage. Always send **both** custom headers and the standard `Authorization` header to survive production reverse proxies:
275
+
276
+ ```typescript
277
+ // In your tool's frontend
278
+ const headers = {
279
+ 'X-Cortex-Embedded': 'true',
280
+ 'X-Cortex-User-Email': user.email,
281
+ 'Authorization': `Bearer ${ogiJwtToken}`, // Standard header — proxy-safe
282
+ };
283
+ ```
284
+
285
+ Your tool's backend should accept both modes:
286
+ 1. Custom `X-Cortex-Embedded` + `X-Cortex-User-Email` headers (fast path, works locally)
287
+ 2. `Authorization: Bearer {ogiJWT}` decoded with `CORTEX_JWT_PUBLIC_KEY` (works through all proxies)
288
+
289
+ > **Production Note:** Reverse proxies, CDNs, and load balancers may strip custom `X-Cortex-*` headers. Always send the OGI JWT as `Authorization: Bearer` alongside custom headers. The tool backend should accept both auth modes.
290
+
291
+ ---
292
+
293
+ ## API Reference
294
+
295
+ ### CortexProvider
296
+
297
+ React context provider for Cortex integration.
298
+
299
+ ```tsx
300
+ <CortexProvider
301
+ apiUrl="https://api.cortex.ai" // Cortex gateway URL
302
+ apiKey="your-api-key" // Tool's API key
303
+ toolId="your-tool" // Tool identifier (optional)
304
+ debug={false} // Enable debug logging (optional)
305
+ >
306
+ {children}
307
+ </CortexProvider>
308
+ ```
309
+
310
+ ### useCortex()
311
+
312
+ React hook returning Cortex context:
313
+
314
+ ```typescript
315
+ interface CortexContextValue {
316
+ user: CortexUser | null; // Current user (null if standalone)
317
+ isEmbedded: boolean; // Whether in Cortex iframe
318
+ isReady: boolean; // Whether SDK is initialized
319
+ theme: 'light' | 'dark'; // Current theme
320
+
321
+ push(doc): Promise<void>; // Push document to Knowledge Graph
322
+ query(question, options?): Promise<CortexQueryResult>; // Query
323
+ openChat(query?): void; // Open Cortex chat
324
+ showNotification(message, type?): void; // Show notification
325
+ }
326
+ ```
327
+
328
+ ### Additional Hooks
329
+
330
+ ```typescript
331
+ // More efficient if you only need specific values
332
+ const isEmbedded = useIsEmbedded();
333
+ const user = useCortexUser();
334
+ const theme = useCortexTheme();
335
+ ```
336
+
337
+ ### CortexClient
338
+
339
+ HTTP client for direct API calls:
340
+
341
+ ```typescript
342
+ const client = new CortexClient(config);
343
+
344
+ // Check if configured
345
+ client.isConfigured; // boolean
346
+
347
+ // Methods
348
+ await client.push(document);
349
+ await client.query(question, options);
350
+ await client.getUserContext(email);
351
+ ```
352
+
353
+ ### CortexBridge
354
+
355
+ Low-level postMessage bridge:
356
+
357
+ ```typescript
358
+ const bridge = new CortexBridge();
359
+
360
+ // Properties
361
+ bridge.isEmbedded; // boolean
362
+ bridge.isReady; // boolean
363
+ bridge.user; // CortexUser | null
364
+ bridge.theme; // 'light' | 'dark'
365
+
366
+ // Methods
367
+ await bridge.waitForReady();
368
+ bridge.openChat(query?);
369
+ bridge.showNotification(message, type);
370
+ bridge.navigate(path);
371
+ bridge.on(messageType, handler);
372
+ bridge.destroy();
373
+ ```
374
+
375
+ ## Types
376
+
377
+ ```typescript
378
+ interface CortexUser {
379
+ id: string;
380
+ email: string;
381
+ name?: string;
382
+ orgId: string;
383
+ orgName?: string;
384
+ teams: Array<{ id: string; name: string }>;
385
+ role: string;
386
+ permissions: string[];
387
+ }
388
+
389
+ interface CortexDocument {
390
+ id: string; // External ID
391
+ type: string; // Document type
392
+ content: string; // Text content
393
+ name?: string; // Display name
394
+ externalUrl?: string;
395
+ scope?: 'global' | 'team' | 'personal';
396
+ teamIds?: string[];
397
+ metadata?: Record<string, unknown>;
398
+ }
399
+
400
+ interface CortexQueryResult {
401
+ answer: string;
402
+ sources: CortexSource[];
403
+ query: string;
404
+ }
405
+ ```
406
+
407
+ ## PostMessage Protocol
408
+
409
+ When embedded in Cortex, the SDK uses postMessage for communication:
410
+
411
+ | Direction | Message Type | Payload |
412
+ |-----------|--------------|---------|
413
+ | Tool → Cortex | `TOOL_READY` | - |
414
+ | Cortex → Tool | `CORTEX_INIT` | `{ user, theme }` |
415
+ | Cortex → Tool | `CORTEX_THEME` | `{ theme }` |
416
+ | Tool → Cortex | `OPEN_CHAT` | `{ query? }` |
417
+ | Tool → Cortex | `SHOW_NOTIFICATION` | `{ message, type }` |
418
+ | Tool → Cortex | `NAVIGATE` | `{ path }` |
419
+
420
+ ## Graceful Degradation
421
+
422
+ The SDK works in both embedded and standalone modes:
423
+
424
+ ```tsx
425
+ function MyComponent() {
426
+ const { isEmbedded, push, openChat } = useCortex();
427
+
428
+ // Push always works (no-op if not configured)
429
+ await push({ id: '1', type: 'doc', content: '...' });
430
+
431
+ // openChat is a no-op if not embedded
432
+ openChat('Hello');
433
+
434
+ // Conditionally render embedded-only features
435
+ return (
436
+ <div>
437
+ {isEmbedded && <CortexOnlyFeature />}
438
+ </div>
439
+ );
440
+ }
441
+ ```
442
+
443
+ ## License
444
+
445
+ MIT