prepia 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +312 -0
  3. package/bin/prepia.mjs +119 -0
  4. package/package.json +53 -0
  5. package/skill/SKILL.md +148 -0
  6. package/skill/config.json +29 -0
  7. package/src/analytics/dashboard.mjs +84 -0
  8. package/src/analytics/tracker.mjs +131 -0
  9. package/src/api/middleware.mjs +219 -0
  10. package/src/api/routes.mjs +142 -0
  11. package/src/api/server.mjs +150 -0
  12. package/src/cache/disk-store.mjs +199 -0
  13. package/src/cache/manager.mjs +142 -0
  14. package/src/cache/memory-store.mjs +205 -0
  15. package/src/chain/dag.mjs +209 -0
  16. package/src/chain/executor.mjs +103 -0
  17. package/src/chain/scheduler.mjs +89 -0
  18. package/src/client/adapters.mjs +483 -0
  19. package/src/client/connector.mjs +391 -0
  20. package/src/client/index.mjs +483 -0
  21. package/src/client/websocket.mjs +353 -0
  22. package/src/core/context-packager.mjs +169 -0
  23. package/src/core/engine.mjs +338 -0
  24. package/src/core/event-bus.mjs +84 -0
  25. package/src/core/prepimshot.mjs +120 -0
  26. package/src/core/task-decomposer.mjs +158 -0
  27. package/src/edge/lite.mjs +90 -0
  28. package/src/guard/checker.mjs +123 -0
  29. package/src/guard/fact-checker.mjs +105 -0
  30. package/src/guard/hallucination.mjs +108 -0
  31. package/src/index.mjs +67 -0
  32. package/src/models/local-model.mjs +171 -0
  33. package/src/models/provider.mjs +192 -0
  34. package/src/models/router.mjs +156 -0
  35. package/src/morph/optimizer.mjs +142 -0
  36. package/src/network/p2p.mjs +146 -0
  37. package/src/persona/detector.mjs +118 -0
  38. package/src/plugins/loader.mjs +120 -0
  39. package/src/plugins/registry.mjs +164 -0
  40. package/src/plugins/sandbox.mjs +79 -0
  41. package/src/rate/limiter.mjs +145 -0
  42. package/src/rate/shield.mjs +150 -0
  43. package/src/script/executor.mjs +164 -0
  44. package/src/script/parser.mjs +134 -0
  45. package/src/security/privacy.mjs +108 -0
  46. package/src/security/sanitizer.mjs +133 -0
  47. package/src/shadow/daemon.mjs +128 -0
  48. package/src/stream/handler.mjs +204 -0
  49. package/src/tools/calculator.mjs +312 -0
  50. package/src/tools/file-ops.mjs +138 -0
  51. package/src/tools/http-client.mjs +127 -0
  52. package/src/tools/orchestrator.mjs +205 -0
  53. package/src/tools/web-scraper.mjs +159 -0
  54. package/src/tools/web-search.mjs +129 -0
  55. package/src/vault/knowledge-base.mjs +207 -0
  56. package/src/vault/pattern-learner.mjs +192 -0
  57. package/workflows/analyze.json +32 -0
  58. package/workflows/automate.json +32 -0
  59. package/workflows/research.json +37 -0
  60. package/workflows/summarize.json +32 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Web search using Node 22 global fetch.
3
+ * @module tools/web-search
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} SearchResult
8
+ * @property {string} title - Result title
9
+ * @property {string} url - Result URL
10
+ * @property {string} snippet - Result snippet/description
11
+ * @property {string} source - Search engine used
12
+ */
13
+
14
+ /**
15
+ * Search the web using DuckDuckGo HTML (no API key needed).
16
+ * @param {string} query - Search query
17
+ * @param {Object} [options]
18
+ * @param {number} [options.maxResults=10] - Max results to return
19
+ * @param {string} [options.engine='duckduckgo'] - Search engine
20
+ * @returns {Promise<SearchResult[]>}
21
+ */
22
+ export async function search(query, options = {}) {
23
+ if (!query || typeof query !== 'string') {
24
+ throw new Error('Query must be a non-empty string');
25
+ }
26
+
27
+ const { maxResults = 10, engine = 'duckduckgo' } = options;
28
+
29
+ switch (engine) {
30
+ case 'duckduckgo':
31
+ return searchDuckDuckGo(query, maxResults);
32
+ case 'wikipedia':
33
+ return searchWikipedia(query, maxResults);
34
+ default:
35
+ return searchDuckDuckGo(query, maxResults);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Search using DuckDuckGo HTML version.
41
+ * @param {string} query
42
+ * @param {number} maxResults
43
+ * @returns {Promise<SearchResult[]>}
44
+ */
45
+ async function searchDuckDuckGo(query, maxResults) {
46
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
47
+ try {
48
+ const response = await fetch(url, {
49
+ headers: {
50
+ 'User-Agent': 'Mozilla/5.0 (compatible; Prepia/1.0)',
51
+ },
52
+ signal: AbortSignal.timeout(15000),
53
+ });
54
+ const html = await response.text();
55
+ return parseDuckDuckGoResults(html, maxResults);
56
+ } catch (err) {
57
+ throw new Error(`DuckDuckGo search failed: ${err.message}`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Parse DuckDuckGo HTML results.
63
+ * @param {string} html
64
+ * @param {number} maxResults
65
+ * @returns {SearchResult[]}
66
+ */
67
+ function parseDuckDuckGoResults(html, maxResults) {
68
+ const results = [];
69
+ // Match result blocks
70
+ const resultPattern = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gs;
71
+ const snippetPattern = /<a[^>]+class="result__snippet"[^>]*>(.*?)<\/a>/gs;
72
+
73
+ const links = [];
74
+ let match;
75
+
76
+ // Extract links
77
+ while ((match = resultPattern.exec(html)) !== null && links.length < maxResults) {
78
+ let url = match[1];
79
+ // DuckDuckGo uses redirect URLs
80
+ const uddgMatch = url.match(/uddg=([^&]+)/);
81
+ if (uddgMatch) url = decodeURIComponent(uddgMatch[1]);
82
+ const title = match[2].replace(/<[^>]*>/g, '').trim();
83
+ if (title && url) links.push({ title, url });
84
+ }
85
+
86
+ // Extract snippets
87
+ const snippets = [];
88
+ while ((match = snippetPattern.exec(html)) !== null) {
89
+ snippets.push(match[1].replace(/<[^>]*>/g, '').trim());
90
+ }
91
+
92
+ for (let i = 0; i < links.length && i < maxResults; i++) {
93
+ results.push({
94
+ title: links[i].title,
95
+ url: links[i].url,
96
+ snippet: snippets[i] || '',
97
+ source: 'duckduckgo',
98
+ });
99
+ }
100
+
101
+ return results;
102
+ }
103
+
104
+ /**
105
+ * Search Wikipedia API.
106
+ * @param {string} query
107
+ * @param {number} maxResults
108
+ * @returns {Promise<SearchResult[]>}
109
+ */
110
+ async function searchWikipedia(query, maxResults) {
111
+ const url = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&srlimit=${maxResults}&format=json`;
112
+ try {
113
+ const response = await fetch(url, {
114
+ headers: { 'User-Agent': 'Prepia/1.0' },
115
+ signal: AbortSignal.timeout(15000),
116
+ });
117
+ const data = await response.json();
118
+ return (data.query?.search || []).map(item => ({
119
+ title: item.title,
120
+ url: `https://en.wikipedia.org/wiki/${encodeURIComponent(item.title)}`,
121
+ snippet: item.snippet.replace(/<[^>]*>/g, '').trim(),
122
+ source: 'wikipedia',
123
+ }));
124
+ } catch (err) {
125
+ throw new Error(`Wikipedia search failed: ${err.message}`);
126
+ }
127
+ }
128
+
129
+ export default { search };
@@ -0,0 +1,207 @@
1
+ /**
2
+ * @fileoverview Persistent knowledge storage with relevance search.
3
+ * @module vault/knowledge-base
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+ import crypto from 'node:crypto';
9
+
10
+ export class KnowledgeBase {
11
+ /**
12
+ * @param {Object} [options]
13
+ * @param {string} [options.storagePath='.prepia/knowledge'] - Storage directory
14
+ * @param {number} [options.maxEntries=10000] - Max entries
15
+ * @param {number} [options.defaultTTL=86400000] - Default TTL (24h)
16
+ */
17
+ constructor(options = {}) {
18
+ this._storagePath = options.storagePath ?? '.prepia/knowledge';
19
+ this._maxEntries = options.maxEntries ?? 10000;
20
+ this._defaultTTL = options.defaultTTL ?? 24 * 60 * 60 * 1000;
21
+ /** @type {Map<string, Object>} */
22
+ this._entries = new Map();
23
+ this._initialized = false;
24
+ }
25
+
26
+ /**
27
+ * Initialize - load from disk if available.
28
+ */
29
+ async init() {
30
+ if (this._initialized) return;
31
+ try {
32
+ await fs.mkdir(this._storagePath, { recursive: true });
33
+ const indexFile = path.join(this._storagePath, 'index.json');
34
+ const data = await fs.readFile(indexFile, 'utf-8');
35
+ const entries = JSON.parse(data);
36
+ for (const entry of entries) {
37
+ this._entries.set(entry.key, entry);
38
+ }
39
+ } catch {
40
+ // No existing data or parse error
41
+ }
42
+ this._initialized = true;
43
+ }
44
+
45
+ /**
46
+ * Store knowledge.
47
+ * @param {string} key - Unique key
48
+ * @param {*} value - Value to store
49
+ * @param {Object} [options]
50
+ * @param {string[]} [options.tags] - Tags for categorization
51
+ * @param {string} [options.source] - Source of the knowledge
52
+ * @param {number} [options.ttl] - TTL in ms
53
+ * @param {number} [options.confidence=1] - Confidence score (0-1)
54
+ */
55
+ async store(key, value, options = {}) {
56
+ await this.init();
57
+ const now = Date.now();
58
+ this._entries.set(key, {
59
+ key,
60
+ value,
61
+ tags: options.tags || [],
62
+ source: options.source || '',
63
+ confidence: options.confidence ?? 1,
64
+ createdAt: now,
65
+ updatedAt: now,
66
+ expiresAt: now + (options.ttl ?? this._defaultTTL),
67
+ accessCount: 0,
68
+ lastAccessed: null,
69
+ });
70
+
71
+ // Evict if over limit
72
+ if (this._entries.size > this._maxEntries) {
73
+ const oldest = Array.from(this._entries.values())
74
+ .sort((a, b) => a.updatedAt - b.updatedAt)[0];
75
+ if (oldest) this._entries.delete(oldest.key);
76
+ }
77
+
78
+ await this._persist();
79
+ }
80
+
81
+ /**
82
+ * Retrieve knowledge by key.
83
+ * @param {string} key
84
+ * @returns {Promise<*>} The stored value or undefined
85
+ */
86
+ async get(key) {
87
+ await this.init();
88
+ const entry = this._entries.get(key);
89
+ if (!entry) return undefined;
90
+ if (Date.now() > entry.expiresAt) {
91
+ this._entries.delete(key);
92
+ return undefined;
93
+ }
94
+ entry.accessCount++;
95
+ entry.lastAccessed = Date.now();
96
+ return entry.value;
97
+ }
98
+
99
+ /**
100
+ * Search knowledge by relevance.
101
+ * @param {string} query - Search query
102
+ * @param {Object} [options]
103
+ * @param {string[]} [options.tags] - Filter by tags
104
+ * @param {number} [options.limit=10] - Max results
105
+ * @returns {Promise<Object[]>}
106
+ */
107
+ async search(query, options = {}) {
108
+ await this.init();
109
+ const { tags, limit = 10 } = options;
110
+ const now = Date.now();
111
+ const queryLower = query.toLowerCase();
112
+ const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2);
113
+
114
+ const scored = [];
115
+ for (const entry of this._entries.values()) {
116
+ if (now > entry.expiresAt) continue;
117
+ if (tags && !tags.some(t => entry.tags.includes(t))) continue;
118
+
119
+ let score = 0;
120
+ const valueStr = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value);
121
+ const valueLower = valueStr.toLowerCase();
122
+
123
+ // Exact match bonus
124
+ if (valueLower.includes(queryLower)) score += 10;
125
+
126
+ // Word match
127
+ for (const word of queryWords) {
128
+ if (valueLower.includes(word)) score += 2;
129
+ if (entry.key.toLowerCase().includes(word)) score += 3;
130
+ if (entry.tags.some(t => t.toLowerCase().includes(word))) score += 5;
131
+ }
132
+
133
+ // Confidence and recency boost
134
+ score *= entry.confidence;
135
+ score += (entry.accessCount || 0) * 0.1;
136
+
137
+ if (score > 0) {
138
+ scored.push({ ...entry, score });
139
+ }
140
+ }
141
+
142
+ scored.sort((a, b) => b.score - a.score);
143
+ return scored.slice(0, limit).map(({ score, ...rest }) => rest);
144
+ }
145
+
146
+ /**
147
+ * Delete knowledge by key.
148
+ * @param {string} key
149
+ * @returns {Promise<boolean>}
150
+ */
151
+ async delete(key) {
152
+ await this.init();
153
+ const deleted = this._entries.delete(key);
154
+ if (deleted) await this._persist();
155
+ return deleted;
156
+ }
157
+
158
+ /**
159
+ * Get the number of entries.
160
+ * @returns {Promise<number>}
161
+ */
162
+ async size() {
163
+ await this.init();
164
+ return this._entries.size;
165
+ }
166
+
167
+ /**
168
+ * Clear all knowledge.
169
+ */
170
+ async clear() {
171
+ this._entries.clear();
172
+ await this._persist();
173
+ }
174
+
175
+ /**
176
+ * Remove expired entries.
177
+ * @returns {Promise<number>}
178
+ */
179
+ async cleanup() {
180
+ await this.init();
181
+ const now = Date.now();
182
+ let removed = 0;
183
+ for (const [key, entry] of this._entries) {
184
+ if (now > entry.expiresAt) {
185
+ this._entries.delete(key);
186
+ removed++;
187
+ }
188
+ }
189
+ if (removed > 0) await this._persist();
190
+ return removed;
191
+ }
192
+
193
+ /**
194
+ * Persist to disk.
195
+ * @private
196
+ */
197
+ async _persist() {
198
+ try {
199
+ const indexFile = path.join(this._storagePath, 'index.json');
200
+ await fs.writeFile(indexFile, JSON.stringify(Array.from(this._entries.values()), null, 2));
201
+ } catch {
202
+ // Silently fail persistence
203
+ }
204
+ }
205
+ }
206
+
207
+ export default KnowledgeBase;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * @fileoverview Learns user query patterns over time.
3
+ * @module vault/pattern-learner
4
+ */
5
+
6
+ export class PatternLearner {
7
+ /**
8
+ * @param {Object} [options]
9
+ * @param {number} [options.maxPatterns=500] - Max patterns to track
10
+ * @param {number} [options.minOccurrences=2] - Min occurrences to be a pattern
11
+ */
12
+ constructor(options = {}) {
13
+ this._maxPatterns = options.maxPatterns ?? 500;
14
+ this._minOccurrences = options.minOccurrences ?? 2;
15
+ /** @type {Map<string, Object>} */
16
+ this._patterns = new Map();
17
+ /** @type {string[]} Recent query history */
18
+ this._history = [];
19
+ this._maxHistory = 100;
20
+ }
21
+
22
+ /**
23
+ * Record a query and its outcome.
24
+ * @param {string} query - User query
25
+ * @param {Object} outcome - { type, tools, success, duration }
26
+ */
27
+ record(query, outcome = {}) {
28
+ const normalized = this._normalize(query);
29
+ const existing = this._patterns.get(normalized);
30
+
31
+ if (existing) {
32
+ existing.count++;
33
+ existing.lastSeen = Date.now();
34
+ existing.successRate = ((existing.successRate * (existing.count - 1)) + (outcome.success ? 1 : 0)) / existing.count;
35
+ if (outcome.tools) {
36
+ for (const tool of outcome.tools) {
37
+ existing.tools.set(tool, (existing.tools.get(tool) || 0) + 1);
38
+ }
39
+ }
40
+ } else {
41
+ const tools = new Map();
42
+ if (outcome.tools) {
43
+ for (const tool of outcome.tools) {
44
+ tools.set(tool, 1);
45
+ }
46
+ }
47
+ this._patterns.set(normalized, {
48
+ pattern: normalized,
49
+ original: query,
50
+ count: 1,
51
+ firstSeen: Date.now(),
52
+ lastSeen: Date.now(),
53
+ successRate: outcome.success ? 1 : 0,
54
+ tools,
55
+ type: outcome.type || 'unknown',
56
+ });
57
+ }
58
+
59
+ this._history.push({ query, timestamp: Date.now(), ...outcome });
60
+ if (this._history.length > this._maxHistory) {
61
+ this._history.shift();
62
+ }
63
+
64
+ // Evict old patterns if over limit
65
+ if (this._patterns.size > this._maxPatterns) {
66
+ const oldest = Array.from(this._patterns.values())
67
+ .filter(p => p.count < this._minOccurrences)
68
+ .sort((a, b) => a.lastSeen - b.lastSeen)[0];
69
+ if (oldest) this._patterns.delete(oldest.pattern);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get patterns similar to a query.
75
+ * @param {string} query
76
+ * @param {number} [limit=5]
77
+ * @returns {Object[]}
78
+ */
79
+ findSimilar(query, limit = 5) {
80
+ const normalized = this._normalize(query);
81
+ const queryWords = normalized.split(/\s+/);
82
+
83
+ const scored = [];
84
+ for (const pattern of this._patterns.values()) {
85
+ if (pattern.count < this._minOccurrences) continue;
86
+ const patternWords = pattern.pattern.split(/\s+/);
87
+ let overlap = 0;
88
+ for (const word of queryWords) {
89
+ if (patternWords.includes(word)) overlap++;
90
+ }
91
+ const similarity = overlap / Math.max(queryWords.length, patternWords.length);
92
+ if (similarity > 0.2) {
93
+ scored.push({ ...pattern, similarity, tools: Object.fromEntries(pattern.tools || new Map()) });
94
+ }
95
+ }
96
+
97
+ scored.sort((a, b) => b.similarity - a.similarity || b.count - a.count);
98
+ return scored.slice(0, limit);
99
+ }
100
+
101
+ /**
102
+ * Predict likely next queries based on recent history.
103
+ * @param {number} [limit=3]
104
+ * @returns {string[]}
105
+ */
106
+ predictNext(limit = 3) {
107
+ if (this._history.length < 2) return [];
108
+
109
+ const recent = this._history.slice(-5).map(h => this._normalize(h.query));
110
+ const predictions = new Map();
111
+
112
+ for (const pattern of this._patterns.values()) {
113
+ if (pattern.count < this._minOccurrences) continue;
114
+ for (const recentQuery of recent) {
115
+ const similarity = this._similarity(pattern.pattern, recentQuery);
116
+ if (similarity > 0.3 && similarity < 1.0) {
117
+ predictions.set(pattern.pattern, (predictions.get(pattern.pattern) || 0) + similarity * pattern.count);
118
+ }
119
+ }
120
+ }
121
+
122
+ return Array.from(predictions.entries())
123
+ .sort((a, b) => b[1] - a[1])
124
+ .slice(0, limit)
125
+ .map(([pattern]) => pattern);
126
+ }
127
+
128
+ /**
129
+ * Get all learned patterns.
130
+ * @returns {Object[]}
131
+ */
132
+ getAllPatterns() {
133
+ return Array.from(this._patterns.values())
134
+ .filter(p => p.count >= this._minOccurrences)
135
+ .sort((a, b) => b.count - a.count)
136
+ .map(p => ({ ...p, tools: Object.fromEntries(p.tools || new Map()) }));
137
+ }
138
+
139
+ /**
140
+ * Get recent query history.
141
+ * @param {number} [limit=20]
142
+ * @returns {Object[]}
143
+ */
144
+ getHistory(limit = 20) {
145
+ return this._history.slice(-limit);
146
+ }
147
+
148
+ /**
149
+ * Get the number of tracked patterns.
150
+ * @returns {number}
151
+ */
152
+ get size() {
153
+ return this._patterns.size;
154
+ }
155
+
156
+ /**
157
+ * Clear all patterns and history.
158
+ */
159
+ clear() {
160
+ this._patterns.clear();
161
+ this._history = [];
162
+ }
163
+
164
+ /**
165
+ * Normalize a query for pattern matching.
166
+ * @param {string} query
167
+ * @returns {string}
168
+ * @private
169
+ */
170
+ _normalize(query) {
171
+ return query.toLowerCase().trim().replace(/[^\w\s]/g, '').replace(/\s+/g, ' ');
172
+ }
173
+
174
+ /**
175
+ * Calculate similarity between two strings.
176
+ * @param {string} a
177
+ * @param {string} b
178
+ * @returns {number} 0-1
179
+ * @private
180
+ */
181
+ _similarity(a, b) {
182
+ const wordsA = new Set(a.split(/\s+/));
183
+ const wordsB = new Set(b.split(/\s+/));
184
+ let intersection = 0;
185
+ for (const word of wordsA) {
186
+ if (wordsB.has(word)) intersection++;
187
+ }
188
+ return intersection / Math.max(wordsA.size, wordsB.size);
189
+ }
190
+ }
191
+
192
+ export default PatternLearner;
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "analyze",
3
+ "description": "Data gathering and analysis",
4
+ "mode": "shot",
5
+ "steps": [
6
+ {
7
+ "keyword": "SEARCH",
8
+ "description": "Gather relevant data",
9
+ "params": { "maxResults": 10 }
10
+ },
11
+ {
12
+ "keyword": "EXTRACT",
13
+ "description": "Extract numerical data and facts",
14
+ "params": { "extractNumbers": true }
15
+ },
16
+ {
17
+ "keyword": "FORMAT",
18
+ "description": "Structure data for analysis",
19
+ "params": { "format": "json" }
20
+ },
21
+ {
22
+ "keyword": "DELIVER",
23
+ "description": "Analyze and present findings",
24
+ "params": { "includeCharts": false, "includeInsights": true }
25
+ }
26
+ ],
27
+ "options": {
28
+ "enableCalculation": true,
29
+ "enableVisualization": false,
30
+ "outputFormat": "markdown"
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "automate",
3
+ "description": "Multi-step task automation",
4
+ "mode": "shot",
5
+ "steps": [
6
+ {
7
+ "keyword": "SEARCH",
8
+ "description": "Research automation approach",
9
+ "params": { "maxResults": 5 }
10
+ },
11
+ {
12
+ "keyword": "EXTRACT",
13
+ "description": "Extract relevant procedures and methods",
14
+ "params": { "maxLength": 20000 }
15
+ },
16
+ {
17
+ "keyword": "FORMAT",
18
+ "description": "Structure automation plan",
19
+ "params": { "format": "steps" }
20
+ },
21
+ {
22
+ "keyword": "DELIVER",
23
+ "description": "Generate automation instructions",
24
+ "params": { "stepByStep": true, "includeWarnings": true }
25
+ }
26
+ ],
27
+ "options": {
28
+ "enableExecution": false,
29
+ "dryRun": true,
30
+ "outputFormat": "markdown"
31
+ }
32
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "research",
3
+ "description": "Multi-source web research with fact-checking",
4
+ "mode": "shot",
5
+ "steps": [
6
+ {
7
+ "keyword": "SEARCH",
8
+ "description": "Search multiple sources for information",
9
+ "params": { "engines": ["duckduckgo", "wikipedia"], "maxResults": 5 }
10
+ },
11
+ {
12
+ "keyword": "EXTRACT",
13
+ "description": "Extract key content from search results",
14
+ "params": { "maxLength": 10000 }
15
+ },
16
+ {
17
+ "keyword": "FILTER",
18
+ "description": "Filter for relevance and recency",
19
+ "params": { "minRelevance": 0.3 }
20
+ },
21
+ {
22
+ "keyword": "FORMAT",
23
+ "description": "Format findings for LLM consumption",
24
+ "params": { "format": "structured" }
25
+ },
26
+ {
27
+ "keyword": "DELIVER",
28
+ "description": "Synthesize final answer with LLM",
29
+ "params": { "verify": true, "cite": true }
30
+ }
31
+ ],
32
+ "options": {
33
+ "enableFactCheck": true,
34
+ "maxTokens": 4000,
35
+ "outputFormat": "markdown"
36
+ }
37
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "summarize",
3
+ "description": "Content compression and summarization",
4
+ "mode": "shot",
5
+ "steps": [
6
+ {
7
+ "keyword": "EXTRACT",
8
+ "description": "Extract main content from source",
9
+ "params": { "stripHtml": true, "maxLength": 50000 }
10
+ },
11
+ {
12
+ "keyword": "FILTER",
13
+ "description": "Remove boilerplate and noise",
14
+ "params": { "removeAds": true, "removeNav": true }
15
+ },
16
+ {
17
+ "keyword": "FORMAT",
18
+ "description": "Prepare for summarization",
19
+ "params": { "format": "text" }
20
+ },
21
+ {
22
+ "keyword": "DELIVER",
23
+ "description": "Generate summary with LLM",
24
+ "params": { "style": "concise", "maxLength": 500 }
25
+ }
26
+ ],
27
+ "options": {
28
+ "compressionRatio": 0.2,
29
+ "preserveKeyPoints": true,
30
+ "outputFormat": "text"
31
+ }
32
+ }