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,84 @@
1
+ /**
2
+ * @fileoverview Cost/efficiency reporting dashboard.
3
+ * @module analytics/dashboard
4
+ */
5
+
6
+ /**
7
+ * Generate a formatted report from tracker metrics.
8
+ * @param {Object} metrics - From Tracker.getMetrics()
9
+ * @param {Object} [savings] - From Tracker.estimateSavings()
10
+ * @returns {string}
11
+ */
12
+ export function generateReport(metrics, savings = {}) {
13
+ const uptimeStr = formatDuration(metrics.uptime || 0);
14
+ const efficiencyPct = ((metrics.efficiency || 0) * 100).toFixed(1);
15
+
16
+ return `
17
+ ╔══════════════════════════════════════════════════════════╗
18
+ ║ PREPIA ANALYTICS ║
19
+ ╠══════════════════════════════════════════════════════════╣
20
+ ║ ║
21
+ ║ Uptime: ${uptimeStr.padEnd(38)}║
22
+ ║ Tasks Processed: ${String(metrics.tasksProcessed).padEnd(38)}║
23
+ ║ ║
24
+ ║ ── LLM Usage ────────────────────────────────────────── ║
25
+ ║ LLM Calls Made: ${String(metrics.llmCallsMade).padEnd(38)}║
26
+ ║ LLM Calls Avoided:${String(metrics.llmCallsAvoided).padEnd(38)}║
27
+ ║ Efficiency: ${(efficiencyPct + '%').padEnd(38)}║
28
+ ║ ║
29
+ ║ ── Token Usage ──────────────────────────────────────── ║
30
+ ║ Tokens Used: ${String(metrics.tokensUsed).padEnd(38)}║
31
+ ║ Tokens Saved: ${String(metrics.tokensSaved).padEnd(38)}║
32
+ ║ ║
33
+ ║ ── Cache ────────────────────────────────────────────── ║
34
+ ║ Cache Hits: ${String(metrics.cacheHits).padEnd(38)}║
35
+ ║ Cache Misses: ${String(metrics.cacheMisses).padEnd(38)}║
36
+ ║ Hit Rate: ${formatHitRate(metrics.cacheHits, metrics.cacheMisses).padEnd(38)}║
37
+ ║ ║
38
+ ║ ── Performance ──────────────────────────────────────── ║
39
+ ║ Avg Duration: ${(metrics.avgDuration + 'ms').padEnd(38)}║
40
+ ║ Tasks/Minute: ${String(metrics.tasksPerMinute).padEnd(38)}║
41
+ ║ Errors: ${String(metrics.errors).padEnd(38)}║
42
+ ║ ║${savings.estimatedDollarsSaved !== undefined ? `
43
+ ║ ── Cost Savings ─────────────────────────────────────── ║
44
+ ║ Est. Saved: ${('$' + savings.estimatedDollarsSaved.toFixed(2)).padEnd(38)}║
45
+ ║ Est. Spent: ${('$' + (savings.estimatedDollarsSpent || 0).toFixed(2)).padEnd(38)}║` : ''}
46
+ ║ ║
47
+ ╚══════════════════════════════════════════════════════════╝`.trim();
48
+ }
49
+
50
+ /**
51
+ * Generate a compact summary line.
52
+ * @param {Object} metrics
53
+ * @returns {string}
54
+ */
55
+ export function generateSummary(metrics) {
56
+ const eff = ((metrics.efficiency || 0) * 100).toFixed(0);
57
+ return `Prepia: ${metrics.tasksProcessed} tasks | ${eff}% LLM avoided | ${metrics.tokensSaved} tokens saved | ${metrics.errors} errors`;
58
+ }
59
+
60
+ /**
61
+ * Format duration in ms to human readable.
62
+ * @param {number} ms
63
+ * @returns {string}
64
+ */
65
+ function formatDuration(ms) {
66
+ if (ms < 1000) return `${ms}ms`;
67
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
68
+ if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
69
+ return `${Math.floor(ms / 3600000)}h ${Math.floor((ms % 3600000) / 60000)}m`;
70
+ }
71
+
72
+ /**
73
+ * Format cache hit rate.
74
+ * @param {number} hits
75
+ * @param {number} misses
76
+ * @returns {string}
77
+ */
78
+ function formatHitRate(hits, misses) {
79
+ const total = hits + misses;
80
+ if (total === 0) return 'N/A';
81
+ return `${((hits / total) * 100).toFixed(1)}%`;
82
+ }
83
+
84
+ export default { generateReport, generateSummary };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Usage tracking and metrics.
3
+ * @module analytics/tracker
4
+ */
5
+
6
+ import { EventEmitter } from 'node:events';
7
+
8
+ export class Tracker extends EventEmitter {
9
+ constructor() {
10
+ super();
11
+ this._metrics = {
12
+ tasksProcessed: 0,
13
+ llmCallsMade: 0,
14
+ llmCallsAvoided: 0,
15
+ tokensUsed: 0,
16
+ tokensSaved: 0,
17
+ totalDuration: 0,
18
+ cacheHits: 0,
19
+ cacheMisses: 0,
20
+ errors: 0,
21
+ };
22
+ this._taskHistory = [];
23
+ this._maxHistory = 1000;
24
+ this._startTime = Date.now();
25
+ }
26
+
27
+ /**
28
+ * Record a completed task.
29
+ * @param {Object} task
30
+ * @param {string} task.id - Task ID
31
+ * @param {string} task.type - Task type
32
+ * @param {boolean} task.usedLLM - Whether LLM was called
33
+ * @param {number} [task.tokensUsed=0] - Tokens consumed
34
+ * @param {number} [task.tokensSaved=0] - Tokens saved by optimization
35
+ * @param {number} task.duration - Task duration in ms
36
+ * @param {boolean} [task.cached=false] - Whether result was cached
37
+ * @param {boolean} [task.success=true] - Whether task succeeded
38
+ */
39
+ record(task) {
40
+ this._metrics.tasksProcessed++;
41
+ if (task.usedLLM) {
42
+ this._metrics.llmCallsMade++;
43
+ this._metrics.tokensUsed += task.tokensUsed || 0;
44
+ } else {
45
+ this._metrics.llmCallsAvoided++;
46
+ }
47
+ this._metrics.tokensSaved += task.tokensSaved || 0;
48
+ this._metrics.totalDuration += task.duration || 0;
49
+ if (task.cached) this._metrics.cacheHits++;
50
+ else this._metrics.cacheMisses++;
51
+ if (!task.success) this._metrics.errors++;
52
+
53
+ this._taskHistory.push({
54
+ ...task,
55
+ timestamp: Date.now(),
56
+ });
57
+ if (this._taskHistory.length > this._maxHistory) {
58
+ this._taskHistory.shift();
59
+ }
60
+
61
+ this.emit('task:recorded', task);
62
+ }
63
+
64
+ /**
65
+ * Get current metrics.
66
+ * @returns {Object}
67
+ */
68
+ getMetrics() {
69
+ const uptime = Date.now() - this._startTime;
70
+ const efficiency = this._metrics.tasksProcessed > 0
71
+ ? this._metrics.llmCallsAvoided / this._metrics.tasksProcessed
72
+ : 0;
73
+ const avgDuration = this._metrics.tasksProcessed > 0
74
+ ? this._metrics.totalDuration / this._metrics.tasksProcessed
75
+ : 0;
76
+
77
+ return {
78
+ ...this._metrics,
79
+ efficiency: Math.round(efficiency * 100) / 100,
80
+ avgDuration: Math.round(avgDuration),
81
+ uptime,
82
+ tasksPerMinute: uptime > 0 ? (this._metrics.tasksProcessed / (uptime / 60000)).toFixed(2) : 0,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Get task history.
88
+ * @param {number} [limit=50]
89
+ * @returns {Object[]}
90
+ */
91
+ getHistory(limit = 50) {
92
+ return this._taskHistory.slice(-limit);
93
+ }
94
+
95
+ /**
96
+ * Estimate cost savings.
97
+ * @param {number} [costPerToken=0.00001] - Cost per token in dollars
98
+ * @returns {Object}
99
+ */
100
+ estimateSavings(costPerToken = 0.00001) {
101
+ const tokensSaved = this._metrics.tokensSaved;
102
+ const callsAvoided = this._metrics.llmCallsAvoided;
103
+ return {
104
+ tokensSaved,
105
+ callsAvoided,
106
+ estimatedDollarsSaved: Math.round(tokensSaved * costPerToken * 100) / 100,
107
+ estimatedDollarsSpent: Math.round(this._metrics.tokensUsed * costPerToken * 100) / 100,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Reset all metrics.
113
+ */
114
+ reset() {
115
+ this._metrics = {
116
+ tasksProcessed: 0,
117
+ llmCallsMade: 0,
118
+ llmCallsAvoided: 0,
119
+ tokensUsed: 0,
120
+ tokensSaved: 0,
121
+ totalDuration: 0,
122
+ cacheHits: 0,
123
+ cacheMisses: 0,
124
+ errors: 0,
125
+ };
126
+ this._taskHistory = [];
127
+ this._startTime = Date.now();
128
+ }
129
+ }
130
+
131
+ export default Tracker;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * @fileoverview API middleware - request parsing, error handling, rate limiting, auth.
3
+ * @module api/middleware
4
+ */
5
+
6
+ import { SlidingWindow } from '../rate/limiter.mjs';
7
+
8
+ /**
9
+ * Parse JSON request body.
10
+ * @param {import('node:http').IncomingMessage} req
11
+ * @returns {Promise<Object>}
12
+ */
13
+ export function parseBody(req) {
14
+ return new Promise((resolve, reject) => {
15
+ if (req.method === 'GET' || req.method === 'HEAD') {
16
+ resolve(null);
17
+ return;
18
+ }
19
+
20
+ let body = '';
21
+ req.on('data', chunk => {
22
+ body += chunk;
23
+ if (body.length > 1024 * 1024) { // 1MB limit
24
+ reject(new Error('Request body too large'));
25
+ req.destroy();
26
+ }
27
+ });
28
+ req.on('end', () => {
29
+ if (!body) {
30
+ resolve(null);
31
+ return;
32
+ }
33
+ try {
34
+ resolve(JSON.parse(body));
35
+ } catch {
36
+ reject(new Error('Invalid JSON body'));
37
+ }
38
+ });
39
+ req.on('error', reject);
40
+ });
41
+ }
42
+
43
+ /**
44
+ * CORS middleware.
45
+ * @param {import('node:http').IncomingMessage} req
46
+ * @param {import('node:http').ServerResponse} res
47
+ * @param {Object} [options]
48
+ */
49
+ export function cors(req, res, options = {}) {
50
+ const origin = options.origin || '*';
51
+ res.setHeader('Access-Control-Allow-Origin', origin);
52
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
53
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
54
+ res.setHeader('Access-Control-Max-Age', '86400');
55
+
56
+ if (req.method === 'OPTIONS') {
57
+ res.writeHead(204);
58
+ res.end();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Per-client rate limiting middleware.
64
+ */
65
+ export class RateLimitMiddleware {
66
+ /**
67
+ * @param {Object} [options]
68
+ * @param {number} [options.maxRequests=100] - Max requests per window
69
+ * @param {number} [options.windowMs=60000] - Window duration
70
+ */
71
+ constructor(options = {}) {
72
+ this._maxRequests = options.maxRequests ?? 100;
73
+ this._windowMs = options.windowMs ?? 60000;
74
+ /** @type {Map<string, SlidingWindow>} */
75
+ this._clients = new Map();
76
+ }
77
+
78
+ /**
79
+ * Check rate limit for a client.
80
+ * @param {string} clientId - Client identifier (IP or API key)
81
+ * @returns {{ allowed: boolean, remaining: number, retryAfter: number }}
82
+ */
83
+ check(clientId) {
84
+ if (!this._clients.has(clientId)) {
85
+ this._clients.set(clientId, new SlidingWindow({
86
+ maxRequests: this._maxRequests,
87
+ windowMs: this._windowMs,
88
+ }));
89
+ }
90
+
91
+ const window = this._clients.get(clientId);
92
+ const allowed = window.consume();
93
+ return {
94
+ allowed,
95
+ remaining: window.remaining,
96
+ retryAfter: allowed ? 0 : window.waitFor(),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Clean up old client entries.
102
+ */
103
+ cleanup() {
104
+ for (const [id, window] of this._clients) {
105
+ if (window.remaining === this._maxRequests) {
106
+ this._clients.delete(id);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Authentication middleware.
114
+ */
115
+ export class AuthMiddleware {
116
+ /**
117
+ * @param {Object} [options]
118
+ * @param {string[]} [options.apiKeys] - Valid API keys
119
+ * @param {boolean} [options.required=false] - Whether auth is required
120
+ */
121
+ constructor(options = {}) {
122
+ this._apiKeys = new Set(options.apiKeys || []);
123
+ this._required = options.required ?? false;
124
+ }
125
+
126
+ /**
127
+ * Authenticate a request.
128
+ * @param {import('node:http').IncomingMessage} req
129
+ * @returns {{ authenticated: boolean, clientId: string }}
130
+ */
131
+ authenticate(req) {
132
+ const apiKey = req.headers['x-api-key'] || this._extractBearerToken(req);
133
+
134
+ if (!apiKey) {
135
+ return { authenticated: !this._required, clientId: req.socket.remoteAddress || 'unknown' };
136
+ }
137
+
138
+ if (this._apiKeys.size === 0) {
139
+ return { authenticated: true, clientId: apiKey };
140
+ }
141
+
142
+ const valid = this._apiKeys.has(apiKey);
143
+ return {
144
+ authenticated: valid,
145
+ clientId: valid ? apiKey : req.socket.remoteAddress || 'unknown',
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Add an API key.
151
+ * @param {string} key
152
+ */
153
+ addKey(key) {
154
+ this._apiKeys.add(key);
155
+ }
156
+
157
+ /**
158
+ * Remove an API key.
159
+ * @param {string} key
160
+ */
161
+ removeKey(key) {
162
+ this._apiKeys.delete(key);
163
+ }
164
+
165
+ /**
166
+ * Extract Bearer token from Authorization header.
167
+ * @param {import('node:http').IncomingMessage} req
168
+ * @returns {string|null}
169
+ * @private
170
+ */
171
+ _extractBearerToken(req) {
172
+ const auth = req.headers.authorization;
173
+ if (auth?.startsWith('Bearer ')) {
174
+ return auth.substring(7);
175
+ }
176
+ return null;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Error handler middleware.
182
+ * @param {Error} err
183
+ * @param {import('node:http').ServerResponse} res
184
+ */
185
+ export function errorHandler(err, res) {
186
+ const status = err.statusCode || 500;
187
+ const message = err.message || 'Internal Server Error';
188
+
189
+ res.writeHead(status, { 'Content-Type': 'application/json' });
190
+ res.end(JSON.stringify({
191
+ error: {
192
+ message,
193
+ status,
194
+ timestamp: new Date().toISOString(),
195
+ },
196
+ }));
197
+ }
198
+
199
+ /**
200
+ * Request logger middleware.
201
+ * @param {import('node:http').IncomingMessage} req
202
+ * @param {import('node:http').ServerResponse} res
203
+ * @returns {Object} Logger with end() method
204
+ */
205
+ export function requestLogger(req) {
206
+ const start = Date.now();
207
+ const method = req.method;
208
+ const url = req.url;
209
+
210
+ return {
211
+ end(res) {
212
+ const duration = Date.now() - start;
213
+ const status = res.statusCode;
214
+ console.log(`${method} ${url} ${status} ${duration}ms`);
215
+ },
216
+ };
217
+ }
218
+
219
+ export default { parseBody, cors, RateLimitMiddleware, AuthMiddleware, errorHandler, requestLogger };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @fileoverview API route handlers.
3
+ * @module api/routes
4
+ */
5
+
6
+ /**
7
+ * Create route handlers for the API.
8
+ * @param {Object} engine - Prepia engine instance
9
+ * @returns {Object} Route handlers
10
+ */
11
+ export function createRoutes(engine) {
12
+ /**
13
+ * POST /task - Submit a task
14
+ */
15
+ async function handleTask(req, res, body) {
16
+ if (!body || !body.query) {
17
+ return sendError(res, 400, 'Missing "query" in request body');
18
+ }
19
+
20
+ try {
21
+ const result = await engine.process(body.query, {
22
+ mode: body.mode || 'shot',
23
+ workflow: body.workflow,
24
+ ...body.options,
25
+ });
26
+ sendJSON(res, 200, result);
27
+ } catch (err) {
28
+ sendError(res, 500, err.message);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * GET /status - Health check
34
+ */
35
+ function handleStatus(req, res) {
36
+ sendJSON(res, 200, {
37
+ status: 'ok',
38
+ version: '1.0.0',
39
+ uptime: process.uptime(),
40
+ timestamp: new Date().toISOString(),
41
+ });
42
+ }
43
+
44
+ /**
45
+ * GET /analytics - Usage stats
46
+ */
47
+ function handleAnalytics(req, res) {
48
+ try {
49
+ const metrics = engine.getAnalytics();
50
+ sendJSON(res, 200, metrics);
51
+ } catch (err) {
52
+ sendError(res, 500, err.message);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * POST /cache/clear - Clear cache
58
+ */
59
+ async function handleCacheClear(req, res) {
60
+ try {
61
+ await engine.clearCache();
62
+ sendJSON(res, 200, { message: 'Cache cleared' });
63
+ } catch (err) {
64
+ sendError(res, 500, err.message);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * GET /plugins - List plugins
70
+ */
71
+ function handlePlugins(req, res) {
72
+ try {
73
+ const plugins = engine.getPlugins();
74
+ sendJSON(res, 200, { plugins });
75
+ } catch (err) {
76
+ sendError(res, 500, err.message);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * POST /config - Update configuration
82
+ */
83
+ function handleConfig(req, res, body) {
84
+ if (!body) {
85
+ return sendError(res, 400, 'Missing request body');
86
+ }
87
+ try {
88
+ engine.updateConfig(body);
89
+ sendJSON(res, 200, { message: 'Configuration updated' });
90
+ } catch (err) {
91
+ sendError(res, 500, err.message);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Route a request to the appropriate handler.
97
+ * @param {import('node:http').IncomingMessage} req
98
+ * @param {import('node:http').ServerResponse} res
99
+ * @param {Object|null} body - Parsed request body
100
+ */
101
+ async function route(req, res, body) {
102
+ const url = new URL(req.url, `http://${req.headers.host}`);
103
+ const path = url.pathname;
104
+ const method = req.method;
105
+
106
+ // Match routes
107
+ if (path === '/task' && method === 'POST') return handleTask(req, res, body);
108
+ if (path === '/status' && method === 'GET') return handleStatus(req, res);
109
+ if (path === '/analytics' && method === 'GET') return handleAnalytics(req, res);
110
+ if (path === '/cache/clear' && method === 'POST') return handleCacheClear(req, res);
111
+ if (path === '/plugins' && method === 'GET') return handlePlugins(req, res);
112
+ if (path === '/config' && method === 'POST') return handleConfig(req, res);
113
+
114
+ // 404
115
+ sendError(res, 404, `Not found: ${method} ${path}`);
116
+ }
117
+
118
+ return { route, handleTask, handleStatus, handleAnalytics, handleCacheClear, handlePlugins, handleConfig };
119
+ }
120
+
121
+ /**
122
+ * Send a JSON response.
123
+ * @param {import('node:http').ServerResponse} res
124
+ * @param {number} status
125
+ * @param {*} data
126
+ */
127
+ function sendJSON(res, status, data) {
128
+ res.writeHead(status, { 'Content-Type': 'application/json' });
129
+ res.end(JSON.stringify(data, null, 2));
130
+ }
131
+
132
+ /**
133
+ * Send an error response.
134
+ * @param {import('node:http').ServerResponse} res
135
+ * @param {number} status
136
+ * @param {string} message
137
+ */
138
+ function sendError(res, status, message) {
139
+ sendJSON(res, status, { error: { message, status } });
140
+ }
141
+
142
+ export default { createRoutes };