@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.
- package/README.md +1 -7
- package/bin/setup.js +1 -3
- package/lib/index.js +0 -12
- package/lib/memory/index.js +0 -2
- package/lib/search/index.js +1 -3
- package/lib/utils/index.js +1 -4
- package/package.json +1 -2
- package/skills/SKILL.md +550 -1
- package/bin/scrubber.js +0 -81
- package/lib/adapters/index.js +0 -3
- package/lib/memory/memory-context-manager.js +0 -388
- package/lib/memory/memory-translator.js +0 -130
- package/lib/memory/migrate-memory.js +0 -227
- package/lib/memory/migrate-to-v2.js +0 -120
- package/lib/memory/scorer.js +0 -85
- package/lib/memory/vector-memory.js +0 -364
- package/lib/privacy/audit-logger.js +0 -176
- package/lib/privacy/dlp-redactor.js +0 -72
- package/lib/privacy/index.js +0 -10
- package/lib/reporting/skill-report-generator.js +0 -283
- package/lib/search/filter.js +0 -275
- package/lib/search/hybrid.js +0 -137
- package/lib/search/pattern-miner.js +0 -160
- package/lib/utils/error-sanitizer.js +0 -84
- package/lib/utils/handoff-validator.js +0 -85
- package/lib/utils/spinner.js +0 -190
- package/lib/utils/streaming-client.js +0 -128
- package/skills/skill-scrubber.yamo +0 -41
- package/skills/skill-super.yamo +0 -548
|
@@ -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;
|
package/lib/utils/spinner.js
DELETED
|
@@ -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;
|