mindcache 1.0.1 → 2.0.0-alpha.1

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/lib/index.js DELETED
@@ -1,1030 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MindCache = exports.mindcache = void 0;
4
- /* eslint-disable @typescript-eslint/no-explicit-any */
5
- const zod_1 = require("zod");
6
- class MindCache {
7
- constructor() {
8
- this.stm = {};
9
- this.listeners = {};
10
- this.globalListeners = [];
11
- }
12
- // Helper method to encode file to base64
13
- encodeFileToBase64(file) {
14
- return new Promise((resolve, reject) => {
15
- // Check if we're in a browser environment
16
- if (typeof FileReader !== 'undefined') {
17
- const reader = new FileReader();
18
- reader.onload = () => {
19
- const result = reader.result;
20
- // Remove data URL prefix to get just the base64 data
21
- const base64Data = result.split(',')[1];
22
- resolve(base64Data);
23
- };
24
- reader.onerror = reject;
25
- reader.readAsDataURL(file);
26
- }
27
- else {
28
- // Node.js environment - reject with helpful error
29
- reject(new Error('FileReader not available in Node.js environment. Use set_base64() method instead.'));
30
- }
31
- });
32
- }
33
- // Helper method to create data URL from base64 and content type
34
- createDataUrl(base64Data, contentType) {
35
- return `data:${contentType};base64,${base64Data}`;
36
- }
37
- // Helper method to validate content type for different STM types
38
- validateContentType(type, contentType) {
39
- if (type === 'text' || type === 'json') {
40
- return true; // No content type validation needed for text/json
41
- }
42
- if (!contentType) {
43
- return false; // Files and images require content type
44
- }
45
- if (type === 'image') {
46
- return contentType.startsWith('image/');
47
- }
48
- if (type === 'file') {
49
- return true; // Any content type is valid for generic files
50
- }
51
- return false;
52
- }
53
- // Get a value from the STM (deprecated - use get_value instead)
54
- /** @deprecated Use get_value instead */
55
- get(key) {
56
- return this.get_value(key);
57
- }
58
- // Get a value from the STM with template processing if enabled
59
- get_value(key, _processingStack) {
60
- if (key === '$date') {
61
- const today = new Date();
62
- return today.toISOString().split('T')[0];
63
- }
64
- if (key === '$time') {
65
- const now = new Date();
66
- return now.toTimeString().split(' ')[0];
67
- }
68
- const entry = this.stm[key];
69
- if (!entry) {
70
- return undefined;
71
- }
72
- // If template is enabled, process the value through injectSTM
73
- if (entry.attributes.template) {
74
- // Prevent circular references
75
- const processingStack = _processingStack || new Set();
76
- if (processingStack.has(key)) {
77
- return entry.value; // Return raw value to break circular reference
78
- }
79
- processingStack.add(key);
80
- const result = this.injectSTM(entry.value, processingStack);
81
- processingStack.delete(key);
82
- return result;
83
- }
84
- return entry.value;
85
- }
86
- // Get attributes for a key
87
- get_attributes(key) {
88
- if (key === '$date' || key === '$time') {
89
- return {
90
- readonly: true,
91
- visible: true,
92
- hardcoded: true,
93
- template: false,
94
- type: 'text',
95
- tags: []
96
- };
97
- }
98
- const entry = this.stm[key];
99
- return entry ? entry.attributes : undefined;
100
- }
101
- // Set a value in the STM with default attributes
102
- set_value(key, value, attributes) {
103
- // Don't allow setting hardcoded system keys
104
- if (key === '$date' || key === '$time') {
105
- return;
106
- }
107
- const defaultAttributes = {
108
- readonly: false,
109
- visible: true,
110
- hardcoded: false,
111
- template: false,
112
- type: 'text',
113
- tags: []
114
- };
115
- // If key exists, preserve existing attributes unless explicitly overridden
116
- const existingEntry = this.stm[key];
117
- const baseAttributes = existingEntry ? existingEntry.attributes : defaultAttributes;
118
- const finalAttributes = attributes ? { ...baseAttributes, ...attributes } : baseAttributes;
119
- // If hardcoded is true, force readonly to true and template to false
120
- if (finalAttributes.hardcoded) {
121
- finalAttributes.readonly = true;
122
- finalAttributes.template = false;
123
- }
124
- this.stm[key] = {
125
- value,
126
- attributes: finalAttributes
127
- };
128
- if (this.listeners[key]) {
129
- this.listeners[key].forEach(listener => listener());
130
- }
131
- this.notifyGlobalListeners();
132
- }
133
- // Set attributes for an existing key
134
- set_attributes(key, attributes) {
135
- // Don't allow setting attributes for hardcoded system keys
136
- if (key === '$date' || key === '$time') {
137
- return false;
138
- }
139
- const entry = this.stm[key];
140
- if (!entry) {
141
- return false;
142
- }
143
- // Create a copy of attributes, excluding the hardcoded property to prevent modification
144
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
145
- const { hardcoded, ...allowedAttributes } = attributes;
146
- // Apply the allowed attributes
147
- entry.attributes = { ...entry.attributes, ...allowedAttributes };
148
- // If this is a hardcoded key, ensure readonly is always true and template is always false
149
- if (entry.attributes.hardcoded) {
150
- entry.attributes.readonly = true;
151
- entry.attributes.template = false;
152
- }
153
- this.notifyGlobalListeners();
154
- return true;
155
- }
156
- // Set a value in the STM (uses default attributes)
157
- set(key, value) {
158
- this.set_value(key, value);
159
- }
160
- // Set a file value in the STM with base64 encoding
161
- async set_file(key, file, attributes) {
162
- const base64Data = await this.encodeFileToBase64(file);
163
- const contentType = file.type;
164
- const fileAttributes = {
165
- type: contentType.startsWith('image/') ? 'image' : 'file',
166
- contentType,
167
- ...attributes
168
- };
169
- this.set_value(key, base64Data, fileAttributes);
170
- }
171
- // Set a base64 encoded value with content type
172
- set_base64(key, base64Data, contentType, type = 'file', attributes) {
173
- if (!this.validateContentType(type, contentType)) {
174
- throw new Error(`Invalid content type ${contentType} for type ${type}`);
175
- }
176
- const fileAttributes = {
177
- type,
178
- contentType,
179
- ...attributes
180
- };
181
- this.set_value(key, base64Data, fileAttributes);
182
- }
183
- // Convenience method to add an image to STM with proper attributes
184
- add_image(key, base64Data, contentType = 'image/jpeg', attributes) {
185
- if (!contentType.startsWith('image/')) {
186
- throw new Error(`Invalid image content type: ${contentType}. Must start with 'image/'`);
187
- }
188
- this.set_base64(key, base64Data, contentType, 'image', attributes);
189
- // Explicitly ensure the type is set to 'image' after setting the value
190
- this.set_attributes(key, {
191
- type: 'image',
192
- contentType: contentType
193
- });
194
- }
195
- // Get a value as data URL (for files/images)
196
- get_data_url(key) {
197
- const entry = this.stm[key];
198
- if (!entry || (entry.attributes.type !== 'image' && entry.attributes.type !== 'file')) {
199
- return undefined;
200
- }
201
- if (!entry.attributes.contentType) {
202
- return undefined;
203
- }
204
- return this.createDataUrl(entry.value, entry.attributes.contentType);
205
- }
206
- // Get raw base64 data
207
- get_base64(key) {
208
- const entry = this.stm[key];
209
- if (!entry || (entry.attributes.type !== 'image' && entry.attributes.type !== 'file')) {
210
- return undefined;
211
- }
212
- return entry.value;
213
- }
214
- // Check if a key exists in the STM
215
- has(key) {
216
- if (key === '$date' || key === '$time') {
217
- return true;
218
- }
219
- return key in this.stm;
220
- }
221
- // Delete a key-value pair from the STM
222
- delete(key) {
223
- if (key === '$date' || key === '$time') {
224
- return false; // Can't delete hardcoded system keys
225
- }
226
- if (!(key in this.stm)) {
227
- return false;
228
- }
229
- const deleted = delete this.stm[key];
230
- if (deleted) {
231
- this.notifyGlobalListeners();
232
- if (this.listeners[key]) {
233
- this.listeners[key].forEach(listener => listener());
234
- }
235
- }
236
- return deleted;
237
- }
238
- // Clear the entire STM
239
- clear() {
240
- // Clear the STM
241
- this.stm = {};
242
- this.notifyGlobalListeners();
243
- }
244
- // Get all keys in the STM
245
- keys() {
246
- return [...Object.keys(this.stm), '$date', '$time'];
247
- }
248
- // Get all values in the STM
249
- values() {
250
- const now = new Date();
251
- const stmValues = Object.values(this.stm).map(entry => entry.value);
252
- return [
253
- ...stmValues,
254
- now.toISOString().split('T')[0],
255
- now.toTimeString().split(' ')[0]
256
- ];
257
- }
258
- // Get all entries (key-value pairs) in the STM
259
- entries() {
260
- const now = new Date();
261
- const stmEntries = Object.entries(this.stm).map(([key, entry]) => [key, entry.value]);
262
- return [
263
- ...stmEntries,
264
- ['$date', now.toISOString().split('T')[0]],
265
- ['$time', now.toTimeString().split(' ')[0]]
266
- ];
267
- }
268
- // Get the size of the STM
269
- size() {
270
- return Object.keys(this.stm).length + 2; // +2 for $date and $time
271
- }
272
- // Get a copy of the entire STM object (returns values only for backward compatibility)
273
- getAll() {
274
- const now = new Date();
275
- const result = {};
276
- // Add regular STM values
277
- Object.entries(this.stm).forEach(([key, entry]) => {
278
- result[key] = entry.value;
279
- });
280
- // Add system values
281
- result['$date'] = now.toISOString().split('T')[0];
282
- result['$time'] = now.toTimeString().split(' ')[0];
283
- return result;
284
- }
285
- // Update the STM with multiple key-value pairs (uses default attributes)
286
- update(newValues) {
287
- Object.entries(newValues).forEach(([key, value]) => {
288
- if (key !== '$date' && key !== '$time') {
289
- // Set value without triggering individual notifications
290
- const defaultAttributes = {
291
- readonly: false,
292
- visible: true,
293
- hardcoded: false,
294
- template: false,
295
- type: 'text',
296
- tags: []
297
- };
298
- this.stm[key] = {
299
- value,
300
- attributes: defaultAttributes
301
- };
302
- // Trigger key-specific listeners
303
- if (this.listeners[key]) {
304
- this.listeners[key].forEach(listener => listener());
305
- }
306
- }
307
- });
308
- // Trigger global listeners only once at the end
309
- this.notifyGlobalListeners();
310
- }
311
- // Subscribe to changes for a specific key
312
- subscribe(key, listener) {
313
- if (!this.listeners[key]) {
314
- this.listeners[key] = [];
315
- }
316
- this.listeners[key].push(listener);
317
- }
318
- // Unsubscribe from changes for a specific key
319
- unsubscribe(key, listener) {
320
- if (this.listeners[key]) {
321
- this.listeners[key] = this.listeners[key].filter(l => l !== listener);
322
- }
323
- }
324
- // Subscribe to changes for all STM keys
325
- subscribeToAll(listener) {
326
- this.globalListeners.push(listener);
327
- }
328
- // Unsubscribe from all STM changes
329
- unsubscribeFromAll(listener) {
330
- this.globalListeners = this.globalListeners.filter(l => l !== listener);
331
- }
332
- // Helper method to notify all global listeners
333
- notifyGlobalListeners() {
334
- this.globalListeners.forEach(listener => listener());
335
- }
336
- // Replace placeholders in a string with STM values (only uses visible keys for public injectSTM calls)
337
- // Note: Image and file type placeholders are NOT replaced - they remain as {{key}} for tools to process
338
- injectSTM(template, _processingStack) {
339
- // Handle null/undefined templates
340
- if (template === null || template === undefined) {
341
- return String(template);
342
- }
343
- // Convert to string if not already
344
- const templateStr = String(template);
345
- // find all the keys in the template using double brackets
346
- const keys = templateStr.match(/\{\{([$\w]+)\}\}/g);
347
- if (!keys) {
348
- return templateStr;
349
- }
350
- // Extract the actual key names without the double curly braces
351
- const cleanKeys = keys.map(key => key.replace(/[{}]/g, ''));
352
- // Build inputValues with the clean keys
353
- const inputValues = cleanKeys.reduce((acc, key) => {
354
- // Always allow system keys
355
- if (key === '$date' || key === '$time') {
356
- return {
357
- ...acc,
358
- [key]: this.get_value(key, _processingStack)
359
- };
360
- }
361
- // If this is internal processing (from template), allow all keys
362
- // If this is external call, only allow visible keys
363
- const attributes = this.get_attributes(key);
364
- if (_processingStack || (attributes && attributes.visible)) {
365
- // Skip replacement for image and file types - keep the placeholder
366
- if (attributes && (attributes.type === 'image' || attributes.type === 'file')) {
367
- return acc; // Don't add to inputValues, placeholder will remain
368
- }
369
- return {
370
- ...acc,
371
- [key]: this.get_value(key, _processingStack)
372
- };
373
- }
374
- // If key doesn't exist or is not visible (for external calls), don't include it
375
- return acc;
376
- }, {});
377
- // Replace the placeholders with actual values using double brackets
378
- // Image/file placeholders will remain as {{key}} since they're not in inputValues
379
- return templateStr.replace(/\{\{([$\w]+)\}\}/g, (match, key) => {
380
- // If value is in inputValues, use it
381
- if (inputValues[key] !== undefined) {
382
- return inputValues[key];
383
- }
384
- // Check if this is an image/file placeholder that should be preserved
385
- const attributes = this.get_attributes(key);
386
- if (attributes && (attributes.type === 'image' || attributes.type === 'file')) {
387
- return match; // Keep the placeholder for images/files
388
- }
389
- // For missing or invisible keys, replace with empty string (original behavior)
390
- return '';
391
- });
392
- }
393
- // Get a formatted string of all visible STM key-value pairs
394
- getSTM() {
395
- const now = new Date();
396
- const entries = [];
397
- // Add visible regular STM entries
398
- Object.entries(this.stm).forEach(([key, entry]) => {
399
- if (entry.attributes.visible) {
400
- // Use get_value to handle template processing
401
- entries.push([key, this.get_value(key)]);
402
- }
403
- });
404
- // Add system keys (always visible)
405
- entries.push(['$date', now.toISOString().split('T')[0]]);
406
- entries.push(['$time', now.toTimeString().split(' ')[0]]);
407
- return entries
408
- .map(([key, value]) => `${key}: ${value}`)
409
- .join(', ');
410
- }
411
- // Get STM as a proper object (alias for getAll for clarity)
412
- getSTMObject() {
413
- return this.getAll();
414
- }
415
- // Get STM data formatted for API calls (multipart form data style)
416
- getSTMForAPI() {
417
- const now = new Date();
418
- const apiData = [];
419
- // Add visible regular STM entries
420
- Object.entries(this.stm).forEach(([key, entry]) => {
421
- if (entry.attributes.visible) {
422
- const processedValue = entry.attributes.template ? this.get_value(key) : entry.value;
423
- apiData.push({
424
- key,
425
- value: processedValue,
426
- type: entry.attributes.type,
427
- contentType: entry.attributes.contentType
428
- });
429
- }
430
- });
431
- // Add system keys (always visible)
432
- apiData.push({
433
- key: '$date',
434
- value: now.toISOString().split('T')[0],
435
- type: 'text'
436
- });
437
- apiData.push({
438
- key: '$time',
439
- value: now.toTimeString().split(' ')[0],
440
- type: 'text'
441
- });
442
- return apiData;
443
- }
444
- // Get visible images formatted for AI SDK UIMessage file parts
445
- getVisibleImages() {
446
- const imageParts = [];
447
- Object.entries(this.stm).forEach(([key, entry]) => {
448
- if (entry.attributes.visible && entry.attributes.type === 'image' && entry.attributes.contentType) {
449
- // Create data URL from base64 data
450
- const dataUrl = this.createDataUrl(entry.value, entry.attributes.contentType);
451
- imageParts.push({
452
- type: 'file',
453
- mediaType: entry.attributes.contentType,
454
- url: dataUrl,
455
- filename: key // Use the STM key as filename
456
- });
457
- }
458
- });
459
- return imageParts;
460
- }
461
- // Serialize STM to JSON string (complete state)
462
- toJSON() {
463
- return JSON.stringify(this.serialize());
464
- }
465
- // Deserialize from JSON string and update STM (complete state)
466
- fromJSON(jsonString) {
467
- try {
468
- const data = JSON.parse(jsonString);
469
- this.deserialize(data);
470
- }
471
- catch (error) {
472
- // eslint-disable-next-line no-console
473
- console.error('MindCache: Failed to deserialize JSON:', error);
474
- }
475
- }
476
- // Serialize complete state (values + attributes, excluding hardcoded keys)
477
- serialize() {
478
- const result = {};
479
- Object.entries(this.stm).forEach(([key, entry]) => {
480
- // Only serialize non-hardcoded entries
481
- if (!entry.attributes.hardcoded) {
482
- result[key] = {
483
- value: entry.value,
484
- attributes: { ...entry.attributes }
485
- };
486
- }
487
- });
488
- return result;
489
- }
490
- // Deserialize complete state (values + attributes)
491
- deserialize(data) {
492
- if (typeof data === 'object' && data !== null) {
493
- // Clear existing STM (preserves hardcoded keys via clear())
494
- this.clear();
495
- // Restore entries with their complete state
496
- Object.entries(data).forEach(([key, entry]) => {
497
- if (entry && typeof entry === 'object' && 'value' in entry && 'attributes' in entry) {
498
- this.stm[key] = {
499
- value: entry.value,
500
- attributes: {
501
- ...entry.attributes,
502
- tags: entry.attributes.tags || [] // Ensure tags array exists
503
- }
504
- };
505
- }
506
- });
507
- this.notifyGlobalListeners();
508
- }
509
- }
510
- // Generate system prompt from all visible STM keys
511
- get_system_prompt() {
512
- const now = new Date();
513
- const promptLines = [];
514
- // Add visible regular STM entries
515
- Object.entries(this.stm).forEach(([key, entry]) => {
516
- if (entry.attributes.visible) {
517
- // Skip images and large files in system prompt to save context
518
- if (entry.attributes.type === 'image') {
519
- promptLines.push(`image ${key} available`);
520
- return;
521
- }
522
- if (entry.attributes.type === 'file') {
523
- if (entry.attributes.readonly) {
524
- promptLines.push(`${key}: [${entry.attributes.type.toUpperCase()}] - ${entry.attributes.contentType || 'unknown format'}`);
525
- }
526
- else {
527
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
528
- promptLines.push(`${key}: [${entry.attributes.type.toUpperCase()}] - ${entry.attributes.contentType || 'unknown format'}. You can update this ${entry.attributes.type} using the write_${sanitizedKey} tool.`);
529
- }
530
- return;
531
- }
532
- const value = this.get_value(key);
533
- const formattedValue = typeof value === 'object' && value !== null
534
- ? JSON.stringify(value)
535
- : String(value);
536
- if (entry.attributes.readonly) {
537
- // Readonly keys: just show the key-value pair
538
- promptLines.push(`${key}: ${formattedValue}`);
539
- }
540
- else {
541
- // Writable keys: show value and mention the tool (with sanitized tool name)
542
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
543
- const toolInstruction = `You can rewrite "${key}" by using the write_${sanitizedKey} tool. ` +
544
- 'This tool DOES NOT append — start your response with the old value ' +
545
- `(${formattedValue})`;
546
- promptLines.push(`${key}: ${formattedValue}. ${toolInstruction}`);
547
- }
548
- }
549
- });
550
- // Add system keys (always visible and readonly)
551
- promptLines.push(`$date: ${now.toISOString().split('T')[0]}`);
552
- promptLines.push(`$time: ${now.toTimeString().split(' ')[0]}`);
553
- return promptLines.join('\n');
554
- }
555
- // Helper method to find original key from sanitized tool name
556
- findKeyFromToolName(toolName) {
557
- if (!toolName.startsWith('write_')) {
558
- return undefined;
559
- }
560
- const sanitizedKey = toolName.replace('write_', '');
561
- // Find the original key by checking all keys and their sanitized versions
562
- const allKeys = Object.keys(this.stm);
563
- return allKeys.find(k => k.replace(/[^a-zA-Z0-9_-]/g, '_') === sanitizedKey);
564
- }
565
- // Generate tools for Vercel AI SDK to write STM values (excludes readonly keys)
566
- get_aisdk_tools() {
567
- const tools = {};
568
- // Get all current keys (excluding built-in $date and $time and readonly keys)
569
- const writableKeys = Object.entries(this.stm)
570
- .filter(([, entry]) => !entry.attributes.readonly)
571
- .map(([key]) => key);
572
- // Create a write tool for each writable key
573
- writableKeys.forEach(key => {
574
- // Sanitize tool name to match OpenAI's pattern: ^[a-zA-Z0-9_-]+$
575
- const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
576
- const toolName = `write_${sanitizedKey}`;
577
- const entry = this.stm[key];
578
- const keyType = entry?.attributes.type || 'text';
579
- // Create appropriate schema based on the key's type
580
- let inputSchema;
581
- let description = `Write a value to the STM key: ${key}`;
582
- if (keyType === 'image' || keyType === 'file') {
583
- description += ' (expects base64 encoded data)';
584
- inputSchema = zod_1.z.object({
585
- value: zod_1.z.string().describe(`Base64 encoded data for ${key}`),
586
- contentType: zod_1.z.string().optional().describe(`MIME type for the ${keyType}`)
587
- });
588
- }
589
- else if (keyType === 'json') {
590
- description += ' (expects JSON string)';
591
- inputSchema = zod_1.z.object({
592
- value: zod_1.z.string().describe(`JSON string value for ${key}`)
593
- });
594
- }
595
- else {
596
- inputSchema = zod_1.z.object({
597
- value: zod_1.z.string().describe(`The text value to write to ${key}`)
598
- });
599
- }
600
- tools[toolName] = {
601
- description,
602
- inputSchema,
603
- execute: async (input) => {
604
- // Handle different types appropriately
605
- if (keyType === 'image' || keyType === 'file') {
606
- if (input.contentType) {
607
- this.set_base64(key, input.value, input.contentType, keyType);
608
- }
609
- else {
610
- // Use existing content type if available
611
- const existingContentType = entry?.attributes.contentType;
612
- if (existingContentType) {
613
- this.set_base64(key, input.value, existingContentType, keyType);
614
- }
615
- else {
616
- throw new Error(`Content type required for ${keyType} data`);
617
- }
618
- }
619
- }
620
- else {
621
- // For text and json, use regular set_value
622
- this.set_value(key, input.value);
623
- }
624
- // Create specialized success message based on type
625
- let resultMessage;
626
- if (keyType === 'image') {
627
- resultMessage = `Successfully saved image to ${key}`;
628
- }
629
- else if (keyType === 'file') {
630
- resultMessage = `Successfully saved file to ${key}`;
631
- }
632
- else if (keyType === 'json') {
633
- resultMessage = `Successfully saved JSON data to ${key}`;
634
- }
635
- else {
636
- // For text type, include the actual value
637
- resultMessage = `Successfully wrote "${input.value}" to ${key}`;
638
- }
639
- return {
640
- result: resultMessage,
641
- key: key,
642
- value: input.value,
643
- type: keyType,
644
- contentType: input.contentType,
645
- sanitizedKey: sanitizedKey
646
- };
647
- }
648
- };
649
- });
650
- // If no writable keys exist yet, return an empty object
651
- if (writableKeys.length === 0) {
652
- return {};
653
- }
654
- return tools;
655
- }
656
- // Public method for client-side tool execution
657
- executeToolCall(toolName, value) {
658
- const originalKey = this.findKeyFromToolName(toolName);
659
- if (!originalKey) {
660
- return null;
661
- }
662
- // Check if key is readonly
663
- const entry = this.stm[originalKey];
664
- if (entry && entry.attributes.readonly) {
665
- return null;
666
- }
667
- this.set_value(originalKey, value);
668
- return {
669
- result: `Successfully wrote "${value}" to ${originalKey}`,
670
- key: originalKey,
671
- value: value
672
- };
673
- }
674
- // Add a tag to a key
675
- addTag(key, tag) {
676
- // Don't allow tagging hardcoded system keys
677
- if (key === '$date' || key === '$time') {
678
- return false;
679
- }
680
- const entry = this.stm[key];
681
- if (!entry) {
682
- return false;
683
- }
684
- // Initialize tags array if it doesn't exist
685
- if (!entry.attributes.tags) {
686
- entry.attributes.tags = [];
687
- }
688
- // Add tag if it doesn't already exist
689
- if (!entry.attributes.tags.includes(tag)) {
690
- entry.attributes.tags.push(tag);
691
- this.notifyGlobalListeners();
692
- return true;
693
- }
694
- return false; // Tag already exists
695
- }
696
- // Remove a tag from a key
697
- removeTag(key, tag) {
698
- // Don't allow modifying hardcoded system keys
699
- if (key === '$date' || key === '$time') {
700
- return false;
701
- }
702
- const entry = this.stm[key];
703
- if (!entry || !entry.attributes.tags) {
704
- return false;
705
- }
706
- const tagIndex = entry.attributes.tags.indexOf(tag);
707
- if (tagIndex > -1) {
708
- entry.attributes.tags.splice(tagIndex, 1);
709
- this.notifyGlobalListeners();
710
- return true;
711
- }
712
- return false; // Tag not found
713
- }
714
- // Get all tags for a key
715
- getTags(key) {
716
- if (key === '$date' || key === '$time') {
717
- return []; // System keys have no tags
718
- }
719
- const entry = this.stm[key];
720
- return entry?.attributes.tags || [];
721
- }
722
- // Get all unique tags across all entries
723
- getAllTags() {
724
- const allTags = new Set();
725
- Object.values(this.stm).forEach(entry => {
726
- if (entry.attributes.tags) {
727
- entry.attributes.tags.forEach(tag => allTags.add(tag));
728
- }
729
- });
730
- return Array.from(allTags);
731
- }
732
- // Check if a key has a specific tag
733
- hasTag(key, tag) {
734
- if (key === '$date' || key === '$time') {
735
- return false; // System keys have no tags
736
- }
737
- const entry = this.stm[key];
738
- return entry?.attributes.tags?.includes(tag) || false;
739
- }
740
- // Get a formatted string of all entries with a specific tag (ignores visible attribute)
741
- getTagged(tag) {
742
- const entries = [];
743
- // Add regular STM entries that have the specified tag
744
- Object.entries(this.stm).forEach(([key, entry]) => {
745
- if (entry.attributes.tags?.includes(tag)) {
746
- // Use get_value to handle template processing
747
- entries.push([key, this.get_value(key)]);
748
- }
749
- });
750
- return entries
751
- .map(([key, value]) => `${key}: ${value}`)
752
- .join(', ');
753
- }
754
- // Export STM to Markdown format
755
- toMarkdown() {
756
- const now = new Date();
757
- const lines = [];
758
- const appendixEntries = [];
759
- let appendixCounter = 0;
760
- const appendixLabels = {};
761
- // Header
762
- lines.push('# MindCache STM Export');
763
- lines.push('');
764
- lines.push(`Export Date: ${now.toISOString().split('T')[0]}`);
765
- lines.push('');
766
- lines.push('---');
767
- lines.push('');
768
- lines.push('## STM Entries');
769
- lines.push('');
770
- // Process each entry
771
- Object.entries(this.stm).forEach(([key, entry]) => {
772
- // Skip hardcoded keys - they won't be serialized
773
- if (entry.attributes.hardcoded) {
774
- return;
775
- }
776
- lines.push(`### ${key}`);
777
- // Bug fix #1: Ensure type is always 'text' if undefined or the string "undefined"
778
- const entryType = (entry.attributes.type && entry.attributes.type !== 'undefined') ? entry.attributes.type : 'text';
779
- lines.push(`- **Type**: \`${entryType}\``);
780
- // Flatten attributes at the same level as Type
781
- lines.push(`- **Readonly**: \`${entry.attributes.readonly}\``);
782
- lines.push(`- **Visible**: \`${entry.attributes.visible}\``);
783
- lines.push(`- **Template**: \`${entry.attributes.template}\``);
784
- if (entry.attributes.tags && entry.attributes.tags.length > 0) {
785
- lines.push(`- **Tags**: \`${entry.attributes.tags.join('`, `')}\``);
786
- }
787
- // Handle content type for files/images
788
- if (entry.attributes.contentType) {
789
- lines.push(`- **Content Type**: \`${entry.attributes.contentType}\``);
790
- }
791
- // Handle value based on type - always use code blocks for text
792
- if (entryType === 'image' || entryType === 'file') {
793
- // Create appendix reference
794
- const label = String.fromCharCode(65 + appendixCounter); // A, B, C, etc.
795
- appendixLabels[key] = label;
796
- appendixCounter++;
797
- lines.push(`- **Value**: [See Appendix ${label}]`);
798
- // Store for appendix section
799
- appendixEntries.push({
800
- key,
801
- type: entryType,
802
- contentType: entry.attributes.contentType || 'application/octet-stream',
803
- base64: entry.value,
804
- label
805
- });
806
- }
807
- else if (entryType === 'json') {
808
- // Format JSON with proper indentation
809
- lines.push('- **Value**:');
810
- lines.push('```json');
811
- try {
812
- const jsonValue = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value, null, 2);
813
- lines.push(jsonValue);
814
- }
815
- catch {
816
- lines.push(String(entry.value));
817
- }
818
- lines.push('```');
819
- }
820
- else {
821
- // Text type - always use code blocks
822
- const valueStr = String(entry.value);
823
- lines.push('- **Value**:');
824
- lines.push('```');
825
- lines.push(valueStr);
826
- lines.push('```');
827
- }
828
- lines.push('');
829
- lines.push('---');
830
- lines.push('');
831
- });
832
- // Add appendix section if there are binary entries
833
- if (appendixEntries.length > 0) {
834
- lines.push('## Appendix: Binary Data');
835
- lines.push('');
836
- appendixEntries.forEach(({ key, contentType, base64, label }) => {
837
- lines.push(`### Appendix ${label}: ${key}`);
838
- lines.push(`**Type**: ${contentType}`);
839
- lines.push('');
840
- lines.push('```');
841
- lines.push(base64);
842
- lines.push('```');
843
- lines.push('');
844
- lines.push('---');
845
- lines.push('');
846
- });
847
- }
848
- lines.push('*End of MindCache Export*');
849
- return lines.join('\n');
850
- }
851
- // Import STM from Markdown format
852
- fromMarkdown(markdown) {
853
- const lines = markdown.split('\n');
854
- let currentSection = 'header';
855
- let currentKey = null;
856
- let currentEntry = null;
857
- let inCodeBlock = false;
858
- let codeBlockContent = [];
859
- let codeBlockType = null;
860
- const appendixData = {};
861
- let currentAppendixKey = null;
862
- const pendingEntries = {};
863
- // Clear existing STM first
864
- this.clear();
865
- for (let i = 0; i < lines.length; i++) {
866
- const line = lines[i];
867
- const trimmed = line.trim();
868
- // Detect sections
869
- if (trimmed === '## STM Entries') {
870
- currentSection = 'entries';
871
- continue;
872
- }
873
- if (trimmed === '## Appendix: Binary Data') {
874
- currentSection = 'appendix';
875
- continue;
876
- }
877
- // Handle code blocks
878
- if (trimmed === '```' || trimmed === '```json') {
879
- if (!inCodeBlock) {
880
- inCodeBlock = true;
881
- codeBlockContent = [];
882
- codeBlockType = currentSection === 'appendix' ? 'base64' : (trimmed === '```json' ? 'json' : 'value');
883
- }
884
- else {
885
- inCodeBlock = false;
886
- const content = codeBlockContent.join('\n');
887
- if (currentSection === 'appendix' && currentAppendixKey) {
888
- // Store appendix base64 data
889
- appendixData[currentAppendixKey].base64 = content;
890
- }
891
- else if (currentEntry && codeBlockType === 'json') {
892
- currentEntry.value = content;
893
- }
894
- else if (currentEntry && codeBlockType === 'value') {
895
- currentEntry.value = content;
896
- }
897
- codeBlockContent = [];
898
- codeBlockType = null;
899
- }
900
- continue;
901
- }
902
- if (inCodeBlock) {
903
- codeBlockContent.push(line);
904
- continue;
905
- }
906
- // Parse entries section
907
- if (currentSection === 'entries') {
908
- if (trimmed.startsWith('### ')) {
909
- // Save previous entry if exists
910
- if (currentKey && currentEntry && currentEntry.attributes) {
911
- pendingEntries[currentKey] = currentEntry;
912
- }
913
- // Start new entry
914
- currentKey = trimmed.substring(4);
915
- currentEntry = {
916
- value: undefined,
917
- attributes: {
918
- readonly: false,
919
- visible: true,
920
- hardcoded: false,
921
- template: false,
922
- type: 'text',
923
- tags: []
924
- }
925
- };
926
- }
927
- else if (trimmed.startsWith('- **Type**: `')) {
928
- const type = trimmed.match(/`([^`]+)`/)?.[1];
929
- // Don't store "undefined" string - treat it as missing and keep default 'text'
930
- if (currentEntry && type && type !== 'undefined') {
931
- currentEntry.attributes.type = type;
932
- }
933
- }
934
- else if (trimmed.startsWith('- **Readonly**: `')) {
935
- const value = trimmed.match(/`([^`]+)`/)?.[1] === 'true';
936
- if (currentEntry) {
937
- currentEntry.attributes.readonly = value;
938
- }
939
- }
940
- else if (trimmed.startsWith('- **Visible**: `')) {
941
- const value = trimmed.match(/`([^`]+)`/)?.[1] === 'true';
942
- if (currentEntry) {
943
- currentEntry.attributes.visible = value;
944
- }
945
- }
946
- else if (trimmed.startsWith('- **Template**: `')) {
947
- const value = trimmed.match(/`([^`]+)`/)?.[1] === 'true';
948
- if (currentEntry) {
949
- currentEntry.attributes.template = value;
950
- }
951
- }
952
- else if (trimmed.startsWith('- **Tags**: `')) {
953
- const tagsStr = trimmed.substring(13, trimmed.length - 1);
954
- if (currentEntry) {
955
- currentEntry.attributes.tags = tagsStr.split('`, `');
956
- }
957
- }
958
- else if (trimmed.startsWith('- **Content Type**: `')) {
959
- const contentType = trimmed.match(/`([^`]+)`/)?.[1];
960
- if (currentEntry && contentType) {
961
- currentEntry.attributes.contentType = contentType;
962
- }
963
- }
964
- else if (trimmed.startsWith('- **Value**: `')) {
965
- const value = trimmed.substring(14, trimmed.length - 1);
966
- if (currentEntry) {
967
- currentEntry.value = value;
968
- }
969
- }
970
- else if (trimmed.startsWith('- **Value**: [See Appendix ')) {
971
- const labelMatch = trimmed.match(/Appendix ([A-Z])\]/);
972
- if (currentEntry && labelMatch && currentKey) {
973
- currentEntry.appendixLabel = labelMatch[1];
974
- // Set a placeholder value so the entry is saved
975
- currentEntry.value = '';
976
- }
977
- }
978
- }
979
- // Parse appendix section
980
- if (currentSection === 'appendix') {
981
- if (trimmed.startsWith('### Appendix ')) {
982
- const match = trimmed.match(/### Appendix ([A-Z]): (.+)/);
983
- if (match) {
984
- const label = match[1];
985
- const key = match[2];
986
- currentAppendixKey = `${label}:${key}`;
987
- appendixData[currentAppendixKey] = { contentType: '', base64: '' };
988
- }
989
- }
990
- else if (trimmed.startsWith('**Type**: ')) {
991
- const contentType = trimmed.substring(10);
992
- if (currentAppendixKey) {
993
- appendixData[currentAppendixKey].contentType = contentType;
994
- }
995
- }
996
- }
997
- }
998
- // Save last entry
999
- if (currentKey && currentEntry && currentEntry.attributes) {
1000
- pendingEntries[currentKey] = currentEntry;
1001
- }
1002
- // Now combine entries with appendix data and populate STM
1003
- Object.entries(pendingEntries).forEach(([key, entry]) => {
1004
- const appendixLabel = entry.appendixLabel;
1005
- if (appendixLabel) {
1006
- // Find matching appendix data
1007
- const appendixKey = `${appendixLabel}:${key}`;
1008
- const appendixInfo = appendixData[appendixKey];
1009
- if (appendixInfo && appendixInfo.base64) {
1010
- entry.value = appendixInfo.base64;
1011
- if (!entry.attributes.contentType && appendixInfo.contentType) {
1012
- entry.attributes.contentType = appendixInfo.contentType;
1013
- }
1014
- }
1015
- }
1016
- // Set the entry in STM (value can be undefined for entries without value line)
1017
- if (entry.value !== undefined && entry.attributes) {
1018
- this.stm[key] = {
1019
- value: entry.value,
1020
- attributes: entry.attributes
1021
- };
1022
- }
1023
- });
1024
- this.notifyGlobalListeners();
1025
- }
1026
- }
1027
- exports.MindCache = MindCache;
1028
- // Create and export a single instance of MindCache
1029
- exports.mindcache = new MindCache();
1030
- //# sourceMappingURL=index.js.map