@yamo/memory-mesh 2.2.0 → 2.3.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.
@@ -1,160 +0,0 @@
1
- /**
2
- * PatternMiner - Analyzes search results for patterns and insights
3
- */
4
-
5
- import crypto from "crypto";
6
-
7
- class PatternMiner {
8
- constructor(options = {}) {
9
- // @ts-ignore
10
- this.similarityThreshold = options.similarityThreshold || 0.8;
11
- // @ts-ignore
12
- this.minClusterSize = options.minClusterSize || 2;
13
- // @ts-ignore
14
- this.maxThemes = options.maxThemes || 10;
15
- // @ts-ignore
16
- this.minWordLength = options.minWordLength || 4;
17
- }
18
-
19
- async minePatterns(results, options = {}) {
20
- // @ts-ignore
21
- const { extractEntities = false, deduplicate = true } = options;
22
- let workingResults = [...results];
23
- if (deduplicate) workingResults = this.deduplicate(workingResults);
24
-
25
- const patterns = {
26
- originalCount: results.length,
27
- uniqueCount: workingResults.length,
28
- duplicatesRemoved: results.length - workingResults.length,
29
- clusters: [],
30
- themes: [],
31
- entities: [],
32
- summary: ''
33
- };
34
-
35
- // @ts-ignore
36
- patterns.clusters = this._clusterResults(workingResults);
37
- // @ts-ignore
38
- patterns.themes = this._extractThemes(workingResults);
39
-
40
- if (extractEntities) {
41
- // @ts-ignore
42
- patterns.entities = await this._extractEntities(workingResults);
43
- }
44
-
45
- patterns.summary = this._generateSummary(patterns);
46
- return patterns;
47
- }
48
-
49
- _clusterResults(results) {
50
- const clusters = [];
51
- const used = new Set();
52
- for (let i = 0; i < results.length; i++) {
53
- if (used.has(i)) continue;
54
- const cluster = { representative: results[i], members: [results[i]], similarityScore: 1.0 };
55
- for (let j = i + 1; j < results.length; j++) {
56
- if (used.has(j)) continue;
57
- const similarity = this._calculateSimilarity(results[i], results[j]);
58
- if (similarity >= this.similarityThreshold) {
59
- cluster.members.push(results[j]);
60
- used.add(j);
61
- cluster.similarityScore = Math.min(cluster.similarityScore, similarity);
62
- }
63
- }
64
- if (cluster.members.length >= this.minClusterSize || cluster.members.length === 1) {
65
- used.add(i);
66
- clusters.push(cluster);
67
- }
68
- }
69
- return clusters;
70
- }
71
-
72
- _calculateSimilarity(result1, result2) {
73
- let score = 0;
74
- let factors = 0;
75
- if (result1.score !== undefined && result2.score !== undefined) {
76
- const scoreDiff = Math.abs(result1.score - result2.score);
77
- const scoreSim = Math.max(0, 1 - scoreDiff);
78
- score += scoreSim * 0.5;
79
- factors += 0.5;
80
- }
81
- if (result1.metadata?.type && result2.metadata?.type) {
82
- const typeMatch = result1.metadata.type === result2.metadata.type ? 1 : 0;
83
- score += typeMatch * 0.25;
84
- factors += 0.25;
85
- }
86
- const contentSim = this._contentOverlap(result1.content, result2.content);
87
- score += contentSim * 0.1;
88
- factors += 0.1;
89
- return factors > 0 ? score / factors : 0;
90
- }
91
-
92
- _contentOverlap(content1, content2) {
93
- const words1 = new Set(content1.toLowerCase().split(/\s+/).filter(w => w.length > this.minWordLength));
94
- const words2 = new Set(content2.toLowerCase().split(/\s+/).filter(w => w.length > this.minWordLength));
95
- if (words1.size === 0 || words2.size === 0) return 0;
96
- const intersection = new Set([...words1].filter(x => words2.has(x)));
97
- const union = new Set([...words1, ...words2]);
98
- return intersection.size / union.size;
99
- }
100
-
101
- _extractThemes(results) {
102
- const themeMap = new Map();
103
- const stopWords = new Set(['the', 'this', 'that', 'with', 'from', 'have', 'been', 'were', 'they', 'their', 'what', 'when', 'where', 'which', 'will', 'your', 'about', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'could', 'would', 'should', 'each', 'only', 'being', 'other', 'some', 'such', 'them', 'these', 'those', 'over', 'also']);
104
- results.forEach(result => {
105
- const words = result.content.toLowerCase().split(/\s+/).map(w => w.replace(/[^a-z]/g, '')).filter(w => w.length >= this.minWordLength && !stopWords.has(w));
106
- words.forEach(word => {
107
- themeMap.set(word, (themeMap.get(word) || 0) + 1);
108
- });
109
- });
110
- return Array.from(themeMap.entries()).sort((a, b) => b[1] - a[1]).slice(0, this.maxThemes).map(([word, count]) => ({
111
- word, count, frequency: count / results.length
112
- }));
113
- }
114
-
115
- async _extractEntities(results) {
116
- const entities = [];
117
- results.forEach(result => {
118
- const emailMatches = result.content.match(/\b[\w.-]+@[\w.-]+\.\w+\b/g);
119
- if (emailMatches) emailMatches.forEach(e => entities.push({ type: 'email', value: e }));
120
- });
121
- const uniqueEntities = [];
122
- const seen = new Set();
123
- entities.forEach(e => {
124
- const key = `${e.type}:${e.value}`;
125
- if (!seen.has(key)) {
126
- seen.add(key);
127
- uniqueEntities.push(e);
128
- }
129
- });
130
- return uniqueEntities;
131
- }
132
-
133
- _generateSummary(patterns) {
134
- return `Original results: ${patterns.originalCount}, Unique results: ${patterns.uniqueCount}`;
135
- }
136
-
137
- deduplicate(results) {
138
- const seen = new Set();
139
- const unique = [];
140
- results.forEach(result => {
141
- const hash = crypto.createHash('md5').update(result.content).digest('hex');
142
- if (!seen.has(hash)) {
143
- seen.add(hash);
144
- unique.push(result);
145
- }
146
- });
147
- return unique;
148
- }
149
-
150
- getStats() {
151
- return {
152
- similarityThreshold: this.similarityThreshold,
153
- minClusterSize: this.minClusterSize,
154
- maxThemes: this.maxThemes,
155
- minWordLength: this.minWordLength
156
- };
157
- }
158
- }
159
-
160
- export default PatternMiner;
@@ -1,84 +0,0 @@
1
- /**
2
- * Secure Error Handling Utilities
3
- * Sanitizes error messages to prevent API key leakage
4
- */
5
-
6
- /**
7
- * Sanitize error messages by redacting sensitive information
8
- * @param {string} message - Error message to sanitize
9
- * @returns {string} Sanitized error message
10
- */
11
- export function sanitizeErrorMessage(message) {
12
- if (typeof message !== 'string') {
13
- return '[Non-string error message]';
14
- }
15
-
16
- // Redact common sensitive patterns
17
- return message
18
- // Redact Bearer tokens
19
- .replace(/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi, 'Bearer [REDACTED]')
20
- // Redact OpenAI API keys (sk- followed by 32+ chars)
21
- .replace(/sk-[A-Za-z0-9]{32,}/g, 'sk-[REDACTED]')
22
- // Redact generic API keys (20+ alphanumeric chars after api_key)
23
- .replace(/api_key["\s:]+[A-Za-z0-9]{20,}/gi, 'api_key: [REDACTED]')
24
- // Redact environment variable patterns that might contain secrets
25
- .replace(/(OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_API_KEY)[="'\s]+[A-Za-z0-9\-_]+/gi, '$1=[REDACTED]')
26
- // Redact Authorization headers
27
- .replace(/Authorization:\s*[^"\r\n]+/gi, 'Authorization: [REDACTED]')
28
- // Redact potential JWT tokens (header.payload.signature pattern)
29
- .replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, '[JWT_REDACTED]');
30
- }
31
-
32
- /**
33
- * Sanitize error object for logging
34
- * @param {Error|Object} error - Error object to sanitize
35
- * @returns {Object} Sanitized error object safe for logging
36
- */
37
- export function sanitizeErrorForLogging(error) {
38
- if (!error || typeof error !== 'object') {
39
- return { message: '[Invalid error object]' };
40
- }
41
-
42
- const sanitized = {
43
- name: error.name || 'Error',
44
- message: sanitizeErrorMessage(error.message || 'Unknown error')
45
- };
46
-
47
- // Only include stack in development
48
- if (process.env.NODE_ENV === 'development' && error.stack) {
49
- sanitized.stack = error.stack;
50
- }
51
-
52
- // Include code if present (non-sensitive)
53
- if (error.code) {
54
- sanitized.code = error.code;
55
- }
56
-
57
- // Include timestamp
58
- sanitized.timestamp = new Date().toISOString();
59
-
60
- return sanitized;
61
- }
62
-
63
- /**
64
- * Wrap a function to catch and sanitize errors
65
- * @param {Function} fn - Function to wrap
66
- * @param {string} context - Context description for error logging
67
- * @returns {Function} Wrapped function with error sanitization
68
- */
69
- export function withSanitizedErrors(fn, context = 'operation') {
70
- return async (...args) => {
71
- try {
72
- return await fn(...args);
73
- } catch (error) {
74
- const sanitizedError = sanitizeErrorForLogging(error);
75
- throw {
76
- success: false,
77
- error: {
78
- ...sanitizedError,
79
- context
80
- }
81
- };
82
- }
83
- };
84
- }
@@ -1,85 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
-
4
- /**
5
- * Handoff Package Validator
6
- * Verifies that a handoff package is complete and valid.
7
- */
8
- class HandoffValidator {
9
- constructor(packagePath) {
10
- this.packagePath = packagePath;
11
- this.requiredFiles = [
12
- 'context.md',
13
- 'memory.json',
14
- 'metadata.json'
15
- ];
16
- }
17
-
18
- validate() {
19
- /** @type {{valid: boolean, errors: string[], warnings: string[], summary: string}} */
20
- const result = {
21
- valid: true,
22
- errors: [],
23
- warnings: [],
24
- summary: ''
25
- };
26
-
27
- if (!fs.existsSync(this.packagePath)) {
28
- result.valid = false;
29
- result.errors.push(`Package path not found: ${this.packagePath}`);
30
- return result;
31
- }
32
-
33
- // Check required files
34
- for (const file of this.requiredFiles) {
35
- const filePath = path.join(this.packagePath, file);
36
- if (!fs.existsSync(filePath)) {
37
- result.valid = false;
38
- result.errors.push(`Missing required file: ${file}`);
39
- }
40
- }
41
-
42
- // Validate metadata
43
- const metadataPath = path.join(this.packagePath, 'metadata.json');
44
- if (fs.existsSync(metadataPath)) {
45
- try {
46
- const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
47
- if (!metadata.timestamp) result.warnings.push('Metadata missing timestamp');
48
- if (!metadata.source) result.warnings.push('Metadata missing source');
49
- } catch (e) {
50
- result.valid = false;
51
- result.errors.push('Invalid metadata.json format');
52
- }
53
- }
54
-
55
- result.summary = result.valid
56
- ? `✅ Package valid with ${result.warnings.length} warnings`
57
- : `❌ Package invalid with ${result.errors.length} errors`;
58
-
59
- return result;
60
- }
61
- }
62
-
63
- // CLI usage
64
- if (process.argv[1] === import.meta.url) {
65
- const pkgPath = process.argv[2] || '.';
66
- const validator = new HandoffValidator(pkgPath);
67
- const result = validator.validate();
68
-
69
- if (!result.valid) {
70
- console.log(result.summary);
71
- console.log('\n❌ Errors found:');
72
- result.errors.forEach(err => console.log(` - ${err}`));
73
- process.exit(1);
74
- } else {
75
- console.log(result.summary);
76
- console.log('\n✅ Handoff package is valid!');
77
-
78
- if (result.warnings.length > 0) {
79
- console.log('\n⚠️ Warnings:');
80
- result.warnings.forEach(warn => console.log(` - ${warn}`));
81
- }
82
- }
83
- }
84
-
85
- export default HandoffValidator;
@@ -1,190 +0,0 @@
1
- /**
2
- * Spinner and Progress Indicators
3
- */
4
-
5
- import { fileURLToPath } from 'url';
6
-
7
- class Spinner {
8
- constructor(text = 'Loading...', options = {}) {
9
- this.text = text;
10
- // @ts-ignore
11
- this.frames = options.frames || ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12
- // @ts-ignore
13
- this.interval = options.interval || 80;
14
- // @ts-ignore
15
- this.stream = options.stream || process.stderr;
16
- // @ts-ignore
17
- this.color = options.color || '\x1b[36m';
18
- this.frameIndex = 0;
19
- this.timer = null;
20
- this.isSpinning = false;
21
- }
22
-
23
- start() {
24
- if (this.isSpinning) return this;
25
- this.isSpinning = true;
26
- this.frameIndex = 0;
27
- this.stream.write('\x1B[?25l');
28
- this.timer = setInterval(() => {
29
- const frame = this.frames[this.frameIndex];
30
- this.stream.write(`\r${this.color}${frame}\x1b[0m ${this.text}`);
31
- this.frameIndex = (this.frameIndex + 1) % this.frames.length;
32
- }, this.interval);
33
- return this;
34
- }
35
-
36
- update(text) {
37
- this.text = text;
38
- return this;
39
- }
40
-
41
- succeed(text) {
42
- return this.stop('\x1b[32m✔\x1b[0m', text);
43
- }
44
-
45
- fail(text) {
46
- return this.stop('\x1b[31m✖\x1b[0m', text);
47
- }
48
-
49
- warn(text) {
50
- return this.stop('\x1b[33m⚠\x1b[0m', text);
51
- }
52
-
53
- info(text) {
54
- return this.stop('\x1b[36mℹ\x1b[0m', text);
55
- }
56
-
57
- /**
58
- * @param {string|null} symbol
59
- * @param {string|null} text
60
- */
61
- stop(symbol = null, text = null) {
62
- if (!this.isSpinning) return this;
63
- if (this.timer) {
64
- // @ts-ignore
65
- clearInterval(this.timer);
66
- }
67
- this.isSpinning = false;
68
- this.stream.write('\r\x1b[K');
69
- this.stream.write('\x1B[?25h');
70
- if (symbol && text) {
71
- this.stream.write(`${symbol} ${text}\n`);
72
- } else if (text) {
73
- this.stream.write(`${text}\n`);
74
- }
75
- return this;
76
- }
77
-
78
- clear() {
79
- if (!this.isSpinning) return this;
80
- if (this.timer) {
81
- // @ts-ignore
82
- clearInterval(this.timer);
83
- }
84
- this.isSpinning = false;
85
- this.stream.write('\r\x1b[K');
86
- this.stream.write('\x1B[?25h');
87
- return this;
88
- }
89
- }
90
-
91
- class ProgressBar {
92
- constructor(total, options = {}) {
93
- this.total = total;
94
- this.current = 0;
95
- // @ts-ignore
96
- this.width = options.width || 40;
97
- // @ts-ignore
98
- this.stream = options.stream || process.stderr;
99
- // @ts-ignore
100
- this.format = options.format || ':bar :percent :current/:total';
101
- // @ts-ignore
102
- this.complete = options.complete || '█';
103
- // @ts-ignore
104
- this.incomplete = options.incomplete || '░';
105
- // @ts-ignore
106
- this.renderThrottle = options.renderThrottle || 16;
107
- this.lastRender = 0;
108
- }
109
-
110
- tick(amount = 1) {
111
- this.current = Math.min(this.current + amount, this.total);
112
- const now = Date.now();
113
- if (now - this.lastRender < this.renderThrottle && this.current < this.total) {
114
- return this;
115
- }
116
- this.lastRender = now;
117
- this.render();
118
- return this;
119
- }
120
-
121
- render() {
122
- const percent = Math.floor((this.current / this.total) * 100);
123
- const completeLength = Math.floor(this.width * (this.current / this.total));
124
- const incompleteLength = this.width - completeLength;
125
- const bar = this.complete.repeat(completeLength) + this.incomplete.repeat(incompleteLength);
126
- // @ts-ignore
127
- let output = this.format.replace(':bar', bar).replace(':percent', `${percent}%`).replace(':current', String(this.current)).replace(':total', String(this.total));
128
- this.stream.write(`\r${output}`);
129
- if (this.current >= this.total) {
130
- this.stream.write('\n');
131
- }
132
- return this;
133
- }
134
-
135
- update(current) {
136
- this.current = Math.min(current, this.total);
137
- this.render();
138
- return this;
139
- }
140
- }
141
-
142
- class MultiSpinner {
143
- constructor() {
144
- this.spinners = new Map();
145
- this.stream = process.stderr;
146
- }
147
-
148
- add(key, text, status = 'pending') {
149
- this.spinners.set(key, { text, status, startTime: Date.now() });
150
- this.render();
151
- return this;
152
- }
153
-
154
- update(key, status, text = null) {
155
- const spinner = this.spinners.get(key);
156
- if (spinner) {
157
- spinner.status = status;
158
- if (text) spinner.text = text;
159
- this.render();
160
- }
161
- return this;
162
- }
163
-
164
- succeed(key, text) { return this.update(key, 'success', text); }
165
- fail(key, text) { return this.update(key, 'fail', text); }
166
- warn(key, text) { return this.update(key, 'warn', text); }
167
-
168
- render() {
169
- this.stream.write('\r\x1b[K');
170
- const lines = [];
171
- for (const [key, spinner] of this.spinners) {
172
- // @ts-ignore
173
- const symbol = { pending: '\x1b[36m⋯\x1b[0m', success: '\x1b[32m✔\x1b[0m', fail: '\x1b[31m✖\x1b[0m', warn: '\x1b[33m⚠\x1b[0m' }[spinner.status];
174
- const duration = ((Date.now() - spinner.startTime) / 1000).toFixed(1);
175
- lines.push(`${symbol} ${spinner.text} (${duration}s)`);
176
- }
177
- this.stream.write(lines.join('\n'));
178
- if (lines.length > 1) {
179
- this.stream.write(`\x1b[${lines.length - 1}A`);
180
- }
181
- return this;
182
- }
183
-
184
- done() {
185
- this.stream.write('\n'.repeat(this.spinners.size));
186
- return this;
187
- }
188
- }
189
-
190
- export { Spinner, ProgressBar, MultiSpinner };
@@ -1,128 +0,0 @@
1
- import { fileURLToPath } from 'url';
2
- import https from "https";
3
-
4
- /**
5
- * Streaming LLM Client
6
- */
7
- class StreamingClient {
8
- constructor(config) {
9
- this.config = config;
10
- this.buffer = '';
11
- }
12
-
13
- stream(payload, onToken, onComplete, onError) {
14
- const url = new URL(this.config.endpoint);
15
-
16
- /** @type {any} */
17
- const options = {
18
- hostname: url.hostname,
19
- path: url.pathname,
20
- method: 'POST',
21
- headers: {
22
- 'Content-Type': 'application/json',
23
- 'Authorization': `Bearer ${this.config.apiKey}`,
24
- 'Accept': 'text/event-stream'
25
- },
26
- rejectUnauthorized: true,
27
- minVersion: 'TLSv1.2'
28
- };
29
-
30
- const req = https.request(options, (res) => {
31
- let fullResponse = '';
32
- res.on('data', (chunk) => {
33
- this.buffer += chunk.toString();
34
- const lines = this.buffer.split('\n');
35
- this.buffer = lines.pop() || '';
36
- for (const line of lines) {
37
- if (line.startsWith('data: ')) {
38
- const data = line.slice(6);
39
- if (data === '[DONE]') continue;
40
- try {
41
- const parsed = JSON.parse(data);
42
- const token = this.extractToken(parsed, this.config.schema);
43
- if (token) {
44
- fullResponse += token;
45
- onToken(token, parsed);
46
- }
47
- } catch (e) { /* ignore */ }
48
- }
49
- }
50
- });
51
- res.on('end', () => { if (onComplete) onComplete(fullResponse); });
52
- res.on('error', (e) => { if (onError) onError(e); });
53
- });
54
-
55
- req.on('error', (e) => { if (onError) onError(e); });
56
- req.setTimeout(60000, () => {
57
- req.destroy();
58
- if (onError) onError(new Error('Timeout'));
59
- });
60
- req.write(JSON.stringify({ ...payload, stream: true }));
61
- req.end();
62
- return req;
63
- }
64
-
65
- extractToken(data, schema) {
66
- switch (schema) {
67
- case 'openai': return data.choices?.[0]?.delta?.content || '';
68
- default: return data.choices?.[0]?.delta?.content || data.delta?.text || '';
69
- }
70
- }
71
-
72
- async request(payload) {
73
- return new Promise((resolve, reject) => {
74
- const url = new URL(this.config.endpoint);
75
-
76
- /** @type {any} */
77
- const options = {
78
- hostname: url.hostname,
79
- path: url.pathname,
80
- method: 'POST',
81
- headers: {
82
- 'Content-Type': 'application/json',
83
- 'Authorization': `Bearer ${this.config.apiKey}`
84
- },
85
- rejectUnauthorized: true,
86
- minVersion: 'TLSv1.2'
87
- };
88
- const req = https.request(options, (res) => {
89
- let data = '';
90
- res.on('data', (chunk) => data += chunk);
91
- res.on('end', () => {
92
- try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
93
- });
94
- });
95
- req.on('error', reject);
96
- req.write(JSON.stringify(payload));
97
- req.end();
98
- });
99
- }
100
- }
101
-
102
- class StreamingLLM {
103
- constructor(config) {
104
- this.client = new StreamingClient(config);
105
- this.config = config;
106
- }
107
-
108
- async streamToConsole(payload, options = {}) {
109
- // @ts-ignore
110
- const prefix = options.prefix || '';
111
- return new Promise((resolve, reject) => {
112
- let tokens = [];
113
- this.client.stream(payload, (token) => {
114
- process.stdout.write(token);
115
- // @ts-ignore
116
- tokens.push(token);
117
- }, (full) => {
118
- process.stdout.write('\n');
119
- resolve({ content: full, tokens: tokens.length });
120
- }, (e) => {
121
- process.stdout.write('\n');
122
- reject(e);
123
- });
124
- });
125
- }
126
- }
127
-
128
- export { StreamingClient, StreamingLLM };
@@ -1,41 +0,0 @@
1
- metadata:
2
- name;Scrubber;
3
- version;1.0.0;
4
- description;A utility skill that sanitizes text or files using S-MORA Layer 0 Scrubber principles (removing PII, boilerplate, and normalizing structure).;
5
- author;Soverane Labs;
6
- license;MIT;
7
- tags;utility;sanitization;cleaning;security;
8
- capabilities;scrub_text;scrub_file;
9
- parameters:
10
- content:
11
- type;string;
12
- required;false;
13
- description;Raw text content to scrub.;
14
- file_path:
15
- type;string;
16
- required;false;
17
- description;Path to a file to scrub.;
18
- environment:
19
- requires_filesystem;true;
20
- dependencies:
21
- required:
22
- - node >=18.0.0;
23
- - tools/scrubber.js;
24
- ---
25
- agent: ScrubbingAgent;
26
- intent: sanitize_input_content;
27
- context:
28
- raw_text;provided_by_user.content;
29
- target_file;provided_by_user.file_path;
30
- scrubber_tool;tools/scrubber.js;
31
- constraints:
32
- - if_file_provided;use_tool_scrub_file_mode;
33
- - if_text_provided;use_tool_scrub_mode;
34
- - if_both;prefer_file;
35
- - remove_boilerplate_and_pii;
36
- - normalize_structure;
37
- - return_cleaned_json_result;
38
- priority: high;
39
- output: scrubbed_result.json;
40
- log: content_scrubbed;original_length;cleaned_length;
41
- handoff: End;