openkbs 0.0.82 → 0.0.83

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkbs",
3
- "version": "0.0.82",
3
+ "version": "0.0.83",
4
4
  "description": "OpenKBS - Command Line Interface",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/actions.js CHANGED
@@ -307,6 +307,63 @@ async function loginAction() {
307
307
  });
308
308
  }
309
309
 
310
+ async function authAction(kbJWT) {
311
+ try {
312
+ // Decode JWT to validate format (without verification - that's done server-side)
313
+ const parts = kbJWT.split('.');
314
+ if (parts.length !== 3) {
315
+ return console.red('Invalid token format. Expected a JWT token.');
316
+ }
317
+
318
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
319
+
320
+ // Basic validation
321
+ if (!payload.kbUserId || !payload.myUserId) {
322
+ return console.red('Invalid token. Missing required fields (kbUserId, myUserId).');
323
+ }
324
+
325
+ if (!payload.serverURL) {
326
+ return console.red('Invalid token. Missing serverURL field. Token must be from a hosted studio.');
327
+ }
328
+
329
+ if (payload.kbUserId !== payload.myUserId) {
330
+ return console.red('Only KB owners can use this command. The token must be from the KB owner.');
331
+ }
332
+
333
+ console.log(`Server: ${payload.serverURL}`);
334
+
335
+ console.log('Exchanging kbJWT for clientJWT...');
336
+
337
+ // Call lambda-auth to exchange the token
338
+ const response = await fetch('https://auth.openkbs.com/exchangeKbToken', {
339
+ method: 'POST',
340
+ headers: { 'Content-Type': 'application/json' },
341
+ body: JSON.stringify({ token: kbJWT })
342
+ });
343
+
344
+ if (!response.ok) {
345
+ const error = await response.json();
346
+ return console.red(`Authentication failed: ${error.error || error.message || 'Unknown error'}`);
347
+ }
348
+
349
+ const data = await response.json();
350
+
351
+ if (!data.clientJWT) {
352
+ return console.red('Authentication failed: No clientJWT received.');
353
+ }
354
+
355
+ // Save the clientJWT
356
+ await fs.ensureDir(path.dirname(jwtPath));
357
+ await fs.writeFile(jwtPath, data.clientJWT, 'utf8');
358
+
359
+ console.green('Authentication successful! CLI is now authenticated.');
360
+ console.log('You can now use all OpenKBS CLI commands.');
361
+
362
+ } catch (error) {
363
+ console.red(`Authentication failed: ${error.message}`);
364
+ }
365
+ }
366
+
310
367
  async function pullAction(location = 'origin', targetFile) {
311
368
  try {
312
369
  // Remove './' prefix if present
@@ -2759,6 +2816,7 @@ module.exports = {
2759
2816
  signAction,
2760
2817
  serviceAction,
2761
2818
  loginAction,
2819
+ authAction,
2762
2820
  pullAction,
2763
2821
  pushAction,
2764
2822
  cloneAction,
package/src/index.js CHANGED
@@ -6,6 +6,7 @@ const {
6
6
  signAction,
7
7
  serviceAction,
8
8
  loginAction,
9
+ authAction,
9
10
  pullAction,
10
11
  pushAction,
11
12
  cloneAction,
@@ -51,6 +52,11 @@ program
51
52
  .description('Login to OpenKBS and store session locally.')
52
53
  .action(loginAction);
53
54
 
55
+ program
56
+ .command('auth <kbJWT>')
57
+ .description('Authenticate CLI using a kbJWT token (for container/CI environments)')
58
+ .action(authAction);
59
+
54
60
  program
55
61
  .command('create <app-name>')
56
62
  .description('Create a new KB application')
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.9"
2
+ "version": "0.1.16"
3
3
  }
@@ -157,25 +157,72 @@ const uploadHtmlContent = async (htmlContent, filename) => {
157
157
 
158
158
  ## Frontend File Upload (contentRender.js)
159
159
 
160
+ **IMPORTANT:** `openkbs` is passed as a **prop** to components - it's NOT global!
161
+
160
162
  ```javascript
161
- // Using openkbs.Files API in frontend
162
- const handleFileUpload = async (file, onProgress) => {
163
- try {
164
- await openkbs.Files.uploadFileAPI(file, 'files', onProgress);
165
- const publicUrl = `https://yourdomain.com/media/${file.name}`;
166
- return publicUrl;
167
- } catch (error) {
168
- console.error('Upload failed:', error);
169
- throw error;
170
- }
163
+ // File upload button component - receives openkbs as prop
164
+ const FileUploadButton = ({ openkbs, setSystemAlert }) => {
165
+ const [uploading, setUploading] = useState(false);
166
+ const [progress, setProgress] = useState(0);
167
+ const fileInputRef = useRef(null);
168
+
169
+ const handleUpload = async (e) => {
170
+ const file = e.target.files?.[0];
171
+ if (!file) return;
172
+
173
+ setUploading(true);
174
+ try {
175
+ // Upload file to storage with progress callback
176
+ await openkbs.Files.uploadFileAPI(file, 'files', (percent) => {
177
+ setProgress(percent);
178
+ });
179
+
180
+ setSystemAlert?.({ severity: 'success', message: `Uploaded ${file.name}` });
181
+ } catch (error) {
182
+ setSystemAlert?.({ severity: 'error', message: error.message });
183
+ } finally {
184
+ setUploading(false);
185
+ setProgress(0);
186
+ }
187
+ };
188
+
189
+ return (
190
+ <div>
191
+ <button onClick={() => fileInputRef.current?.click()} disabled={uploading}>
192
+ {uploading ? `Uploading ${progress}%` : 'Upload File'}
193
+ </button>
194
+ <input
195
+ ref={fileInputRef}
196
+ type="file"
197
+ onChange={handleUpload}
198
+ style={{ display: 'none' }}
199
+ />
200
+ </div>
201
+ );
202
+ };
203
+
204
+ // Header must pass openkbs to child components
205
+ const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
206
+ return (
207
+ <div>
208
+ <FileUploadButton openkbs={openkbs} setSystemAlert={setSystemAlert} />
209
+ </div>
210
+ );
171
211
  };
212
+ ```
172
213
 
173
- // List files
214
+ ### Other Files API Methods
215
+
216
+ ```javascript
217
+ // List files (inside a component that receives openkbs as prop)
174
218
  const files = await openkbs.Files.listFiles('files');
175
- // Returns: [{ Key: 'files/kbId/filename.jpg', Size: 12345 }]
219
+ // Returns: [{ Key: 'files/kbId/filename.jpg', Size: 12345, LastModified }]
176
220
 
177
221
  // Delete file
178
222
  await openkbs.Files.deleteRawKBFile('filename.jpg', 'files');
223
+
224
+ // Rename file
225
+ await openkbs.Files.renameFile('old.jpg', 'new.jpg', 'files');
179
226
  ```
180
227
 
181
228
  ## instructions.txt (LLM prompt)
@@ -364,43 +364,197 @@ await axios.put(presignedUrl, fileBuffer, {
364
364
 
365
365
  ## VectorDB (Semantic Search)
366
366
 
367
- ### items() - VectorDB Operations
367
+ ### items() - Create Items with Filterable Attributes
368
368
 
369
- Create and search items with embeddings for semantic search.
369
+ For filtering to work, items must be created with filterable attributes (keyword, integer, boolean, etc.).
370
370
 
371
371
  ```javascript
372
- // Create embeddings first
372
+ // 1. Create embeddings from content
373
373
  const { embeddings, totalTokens } = await openkbs.createEmbeddings(
374
- 'text to embed',
375
- 'text-embedding-3-large' // 3072 dimensions
374
+ 'Fix login bug - users cannot login with SSO',
375
+ 'text-embedding-3-large'
376
376
  );
377
377
 
378
- // Create item with embeddings
378
+ // 2. Create item with filterable attributes
379
379
  await openkbs.items({
380
380
  action: 'createItem',
381
- itemType: 'archive',
382
- itemId: 'archive_doc_123',
381
+ itemType: 'task',
382
+ itemId: `task_${Date.now()}`,
383
383
  attributes: [
384
384
  { attrType: 'itemId', attrName: 'itemId', encrypted: false },
385
- { attrType: 'body', attrName: 'body', encrypted: true }
385
+ { attrType: 'body', attrName: 'body', encrypted: true },
386
+ // Filterable attributes - map user-friendly names to generic types
387
+ { attrType: 'keyword1', attrName: 'category', encrypted: false },
388
+ { attrType: 'keyword2', attrName: 'status', encrypted: false },
389
+ { attrType: 'integer1', attrName: 'priority', encrypted: false },
390
+ { attrType: 'boolean1', attrName: 'isUrgent', encrypted: false }
386
391
  ],
387
- item: { body: await openkbs.encrypt(JSON.stringify(data)) },
392
+ item: {
393
+ body: await openkbs.encrypt(JSON.stringify({
394
+ title: 'Fix login bug',
395
+ description: 'Users cannot login with SSO'
396
+ })),
397
+ // Filterable field values
398
+ category: 'bug',
399
+ status: 'open',
400
+ priority: 1,
401
+ isUrgent: true
402
+ },
388
403
  totalTokens,
389
404
  embeddings: embeddings.slice(0, 3072),
390
405
  embeddingModel: 'text-embedding-3-large',
391
406
  embeddingDimension: 3072
392
407
  });
408
+ ```
409
+
410
+ ### Filterable Attribute Types
411
+
412
+ | attrType | S3 Vectors Type | Operators |
413
+ |----------|-----------------|-----------|
414
+ | keyword1-20 | String | $eq, $ne, $in, $nin |
415
+ | text1-20 | String | $eq, $ne, $in, $nin |
416
+ | integer1-20 | Number | $eq, $ne, $gt, $gte, $lt, $lte |
417
+ | float1-20 | Number | $eq, $ne, $gt, $gte, $lt, $lte |
418
+ | boolean1-9 | Boolean | $eq, $ne |
419
+ | date1-9 | Number | $gt, $gte, $lt, $lte |
420
+
421
+ ### Vector Metadata Limits
393
422
 
394
- // Semantic search
423
+ S3 Vectors has two types of metadata:
424
+ - **Filterable** (keyword1, integer1, etc.): 2KB limit - used for filtering in queries
425
+ - **Non-filterable** (itemData): 40KB limit - stored for retrieval only
426
+
427
+ By default, `itemData` is configured as non-filterable, so full item content (up to 40KB) is stored and returned in search results.
428
+
429
+ ### vectorMetadata - Optional Override (Advanced)
430
+
431
+ For items > 40KB, use `vectorMetadata` to control what gets stored in S3 Vectors. The full item is always stored in DynamoDB.
432
+
433
+ ```javascript
434
+ // Very large document (> 40KB) - store only essential fields in vector
435
+ await openkbs.items({
436
+ action: 'createItem',
437
+ itemType: 'document',
438
+ itemId: `doc_${Date.now()}`,
439
+ attributes: [...],
440
+ item: {
441
+ body: JSON.stringify({ fileName, fileUrl, hugeText }), // Full in DynamoDB
442
+ standard: 'IFS'
443
+ },
444
+ // Only these fields in S3 Vectors (for items > 40KB)
445
+ vectorMetadata: {
446
+ standard: 'IFS',
447
+ fileName: 'large-doc.pdf',
448
+ textPreview: hugeText.substring(0, 5000)
449
+ },
450
+ totalTokens,
451
+ embeddings,
452
+ embeddingModel: 'text-embedding-3-large',
453
+ embeddingDimension: 3072
454
+ });
455
+ ```
456
+
457
+ **Note**: For most use cases (items < 40KB), `vectorMetadata` is not needed.
458
+
459
+ ### searchVectorDBItems - Vector Search with Filter
460
+
461
+ ```javascript
462
+ // Simple search (no filter)
395
463
  const results = await openkbs.items({
396
464
  action: 'searchVectorDBItems',
397
- queryText: 'find similar documents',
398
- topK: 10,
399
- minScore: 0
465
+ queryText: 'login issues',
466
+ topK: 10
400
467
  });
401
468
 
402
- // Results: { items: [{ itemId, body, score }, ...] }
403
- // Note: body is encrypted, use openkbs.decrypt() to read
469
+ // Search with filter - use user-friendly field names
470
+ const filtered = await openkbs.items({
471
+ action: 'searchVectorDBItems',
472
+ queryText: 'login authentication issues',
473
+ topK: 20,
474
+ minScore: 70,
475
+ itemType: 'task', // Required for filter translation!
476
+ filter: {
477
+ category: 'bug',
478
+ status: 'open'
479
+ }
480
+ });
481
+
482
+ // Comparison operators
483
+ const highPriority = await openkbs.items({
484
+ action: 'searchVectorDBItems',
485
+ queryText: 'performance problems',
486
+ itemType: 'task',
487
+ filter: {
488
+ priority: { "$lte": 2 },
489
+ status: { "$ne": "done" }
490
+ }
491
+ });
492
+
493
+ // Complex filter with $or
494
+ const urgentOrHighPriority = await openkbs.items({
495
+ action: 'searchVectorDBItems',
496
+ queryText: 'critical issues',
497
+ itemType: 'task',
498
+ filter: {
499
+ "$or": [
500
+ { priority: 1 },
501
+ { isUrgent: true }
502
+ ]
503
+ }
504
+ });
505
+
506
+ // $in operator - match any value in array
507
+ const multipleCats = await openkbs.items({
508
+ action: 'searchVectorDBItems',
509
+ queryText: 'issues',
510
+ itemType: 'task',
511
+ filter: {
512
+ category: { "$in": ["bug", "hotfix"] },
513
+ status: { "$nin": ["done", "cancelled"] }
514
+ }
515
+ });
516
+ ```
517
+
518
+ ### Response Format
519
+
520
+ ```javascript
521
+ {
522
+ items: [
523
+ {
524
+ itemId: 'task_1705123456789',
525
+ body: 'encrypted...', // Use openkbs.decrypt()
526
+ category: 'bug', // Filterable fields returned
527
+ status: 'open',
528
+ priority: 1,
529
+ isUrgent: true,
530
+ score: 0.92, // Similarity score (0-1)
531
+ distance: 0.08,
532
+ totalTokens: 45
533
+ }
534
+ ]
535
+ }
536
+ ```
537
+
538
+ ### settings.json - itemTypes Configuration
539
+
540
+ Define attribute mappings in KB settings for automatic filter translation:
541
+
542
+ ```json
543
+ {
544
+ "itemTypes": {
545
+ "task": {
546
+ "attributes": [
547
+ { "attrName": "itemId", "attrType": "itemId", "encrypted": false },
548
+ { "attrName": "body", "attrType": "body", "encrypted": true },
549
+ { "attrName": "category", "attrType": "keyword1", "encrypted": false },
550
+ { "attrName": "status", "attrType": "keyword2", "encrypted": false },
551
+ { "attrName": "priority", "attrType": "integer1", "encrypted": false },
552
+ { "attrName": "isUrgent", "attrType": "boolean1", "encrypted": false }
553
+ ],
554
+ "embeddingTemplate": "${item.body.title} ${item.body.description}"
555
+ }
556
+ }
557
+ }
404
558
  ```
405
559
 
406
560
  ## Utilities
@@ -61,10 +61,10 @@ return JSON.stringify({ type: 'HIDDEN_MESSAGE' });
61
61
 
62
62
  ## Header Component
63
63
 
64
- Customize the chat header:
64
+ Customize the chat header. **Important:** Receive `openkbs` and `setSystemAlert` as props!
65
65
 
66
66
  ```javascript
67
- const Header = ({ setRenderSettings }) => {
67
+ const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
68
68
  useEffect(() => {
69
69
  setRenderSettings({
70
70
  disableShareButton: true,
@@ -72,10 +72,99 @@ const Header = ({ setRenderSettings }) => {
72
72
  });
73
73
  }, [setRenderSettings]);
74
74
 
75
+ // Now you can use openkbs.Files, openkbs.createItem, etc.
76
+ // Pass openkbs to child components that need it
77
+
75
78
  return <div>Custom Header</div>;
76
79
  };
77
80
  ```
78
81
 
82
+ ### Available Header Props
83
+
84
+ | Prop | Type | Description |
85
+ |------|------|-------------|
86
+ | `setRenderSettings` | function | Configure UI options |
87
+ | `openkbs` | object | SDK object with Files, createItem, etc. |
88
+ | `setSystemAlert` | function | Show alerts: `{ severity: 'success'/'error', message }` |
89
+ | `setBlockingLoading` | function | Show/hide loading overlay |
90
+
91
+ ### Render Settings Options
92
+
93
+ Configure UI behavior via `setRenderSettings()`:
94
+
95
+ ```javascript
96
+ setRenderSettings({
97
+ // UI toggles
98
+ disableShareButton: true,
99
+ disableBalanceView: true,
100
+ disableChatModelsSelect: true,
101
+ disableEmojiButton: true,
102
+ disableCodeExecuteButton: true,
103
+ disableSentLabel: true,
104
+ disableChatAvatar: true,
105
+ disableAutoscroll: true,
106
+ disableInitialScroll: true,
107
+ disableAutoFocusChatInput: true,
108
+ disableContextItems: true,
109
+ disableCopyButton: true,
110
+ disableTextToSpeechButton: true,
111
+ disablePushNotificationsButton: true,
112
+ disableMultichat: true,
113
+ disableImageUploadText: true,
114
+
115
+ // Layout
116
+ setMessageWidth: () => '85%',
117
+ chatContainerHeight: 600,
118
+ backgroundOpacity: 0.5,
119
+
120
+ // Behavior
121
+ inputLabelsQuickSend: true, // Quick send on label click
122
+ chatTimeoutMs: 30000, // Chat timeout in ms
123
+ onChatTimeout: () => {}, // Callback on timeout
124
+
125
+ // Message transform hook
126
+ onBeforeSendMessage: (message) => message,
127
+
128
+ // Custom components
129
+ sendIcon: () => <span>Send</span>,
130
+ formatMessageTime: (timestamp) => '...'
131
+ });
132
+ ```
133
+
134
+ ### onBeforeSendMessage Hook
135
+
136
+ Transform user messages before sending to the LLM. Useful for adding prefixes, filters, or modifying content.
137
+
138
+ ```javascript
139
+ const Header = ({ setRenderSettings, openkbs }) => {
140
+ const [filter, setFilter] = useState('all');
141
+
142
+ useEffect(() => {
143
+ setRenderSettings({
144
+ onBeforeSendMessage: (message) => {
145
+ // Add prefix based on filter selection
146
+ if (filter === 'all') return message;
147
+ return `[Category: ${filter}] ${message}`;
148
+ }
149
+ });
150
+ }, [setRenderSettings, filter]);
151
+
152
+ return (
153
+ <select value={filter} onChange={(e) => setFilter(e.target.value)}>
154
+ <option value="all">All</option>
155
+ <option value="tech">Tech</option>
156
+ <option value="sales">Sales</option>
157
+ </select>
158
+ );
159
+ };
160
+ ```
161
+
162
+ **Use cases:**
163
+ - Add category/filter prefixes to messages
164
+ - Inject context or metadata
165
+ - Transform or sanitize user input
166
+ - Add routing hints for the LLM
167
+
79
168
  ## Command Rendering Pattern
80
169
 
81
170
  Define commands with icons:
@@ -205,7 +294,21 @@ The `(fixed)` suffix indicates built-in libraries that don't need bundling.
205
294
 
206
295
  ## Frontend openkbs Object
207
296
 
208
- The `openkbs` object is available globally in frontend code.
297
+ **IMPORTANT:** The `openkbs` object is passed as a **prop** to Header and other components - it is NOT a global variable.
298
+
299
+ ```javascript
300
+ // CORRECT - receive openkbs as prop
301
+ const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
302
+ // Now you can use openkbs.Files, openkbs.createItem, etc.
303
+ };
304
+
305
+ // WRONG - openkbs is not global in frontend
306
+ const Header = ({ setRenderSettings }) => {
307
+ await openkbs.Files.listFiles(); // ERROR: openkbs is undefined
308
+ };
309
+ ```
310
+
311
+ Always destructure `openkbs` from props before using it.
209
312
 
210
313
  ### Item CRUD
211
314
 
package/version.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.82",
3
- "releaseDate": "2026-01-20",
4
- "releaseNotes": "OpenKBS CLI version 0.0.82"
2
+ "version": "0.0.83",
3
+ "releaseDate": "2026-01-26",
4
+ "releaseNotes": "OpenKBS CLI version 0.0.83"
5
5
  }