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,391 @@
1
+ /**
2
+ * Prepia Universal Connector
3
+ * Auto-detects and connects any AI agent to Prepia
4
+ * Supports: HTTP REST, WebSocket, CLI, Webhook, stdio
5
+ */
6
+
7
+ import { EventEmitter } from 'events';
8
+ import http from 'http';
9
+ import { PrepiClient, prepiConnect } from './index.mjs';
10
+ import { WebSocketServer } from './websocket.mjs';
11
+ import { createAdapter, listAdapters } from './adapters.mjs';
12
+
13
+ /**
14
+ * @typedef {Object} ConnectorConfig
15
+ * @property {number} [port=4710] - Server port
16
+ * @property {string} [host='0.0.0.0'] - Bind host
17
+ * @property {string} [apiKey] - Required API key (null = no auth)
18
+ * @property {boolean} [cors=true] - Enable CORS
19
+ * @property {number} [maxConnections=100] - Max concurrent WebSocket connections
20
+ * @property {boolean} [websocket=true] - Enable WebSocket support
21
+ * @property {Object} [rateLimit] - Per-client rate limiting
22
+ * @property {number} [rateLimit.windowMs=60000] - Rate limit window
23
+ * @property {number} [rateLimit.maxRequests=100] - Max requests per window
24
+ */
25
+
26
+ /**
27
+ * Prepia Universal Connector Server
28
+ * Accepts connections from ANY AI agent via REST, WebSocket, or CLI
29
+ */
30
+ export class PrepiaConnector extends EventEmitter {
31
+ /**
32
+ * @param {ConnectorConfig} config
33
+ * @param {import('../core/engine.mjs').PrepiaEngine} engine - Prepia engine instance
34
+ */
35
+ constructor(config = {}, engine = null) {
36
+ super();
37
+ this.config = {
38
+ port: config.port != null ? config.port : 4710,
39
+ host: config.host || '0.0.0.0',
40
+ apiKey: config.apiKey || null,
41
+ cors: config.cors !== false,
42
+ maxConnections: config.maxConnections || 100,
43
+ websocket: config.websocket !== false,
44
+ rateLimit: config.rateLimit || { windowMs: 60000, maxRequests: 100 },
45
+ };
46
+ this.engine = engine;
47
+ this.server = null;
48
+ this.wsServer = null;
49
+ /** @type {Map<string, { count: number, resetAt: number }>} */
50
+ this.rateLimits = new Map();
51
+ this.running = false;
52
+ }
53
+
54
+ /**
55
+ * Start the connector server
56
+ * @returns {Promise<void>}
57
+ */
58
+ async start() {
59
+ if (this.running) return;
60
+
61
+ this.server = http.createServer((req, res) => this._handleRequest(req, res));
62
+
63
+ if (this.config.websocket) {
64
+ this.wsServer = new WebSocketServer();
65
+ this.server.on('upgrade', (req, socket, head) => {
66
+ if (this.wsServer.clients.size >= this.config.maxConnections) {
67
+ socket.destroy();
68
+ return;
69
+ }
70
+ if (!this._authenticate(req)) {
71
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
72
+ socket.destroy();
73
+ return;
74
+ }
75
+ this.wsServer.handleUpgrade(req, socket, head);
76
+ });
77
+
78
+ this.wsServer.on('task:submit', async (conn, data, msg) => {
79
+ try {
80
+ conn.send({ event: 'task:started', taskId: msg.taskId, timestamp: Date.now() });
81
+ const result = await this._executeTask(data, (phase) => {
82
+ conn.send({ event: 'task:progress', taskId: msg.taskId, data: phase });
83
+ });
84
+ conn.send({ event: 'task:complete', taskId: msg.taskId, data: result });
85
+ } catch (err) {
86
+ conn.send({ event: 'task:error', taskId: msg.taskId, data: { error: err.message } });
87
+ }
88
+ });
89
+ }
90
+
91
+ return new Promise((resolve) => {
92
+ this.server.listen(this.config.port, this.config.host, () => {
93
+ this.running = true;
94
+ this.emit('started', { host: this.config.host, port: this.config.port });
95
+ resolve();
96
+ });
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Stop the connector server
102
+ * @returns {Promise<void>}
103
+ */
104
+ async stop() {
105
+ if (!this.running) return;
106
+ if (this.wsServer) this.wsServer.closeAll();
107
+ return new Promise((resolve) => {
108
+ this.server.close(() => {
109
+ this.running = false;
110
+ this.emit('stopped');
111
+ resolve();
112
+ });
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Handle incoming HTTP request
118
+ * @param {http.IncomingMessage} req
119
+ * @param {http.ServerResponse} res
120
+ */
121
+ async _handleRequest(req, res) {
122
+ // CORS
123
+ if (this.config.cors) {
124
+ res.setHeader('Access-Control-Allow-Origin', '*');
125
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
126
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Prepia-Agent-Id, X-Prepia-Agent-Type');
127
+ if (req.method === 'OPTIONS') {
128
+ res.writeHead(204);
129
+ res.end();
130
+ return;
131
+ }
132
+ }
133
+
134
+ // Auth
135
+ if (!this._authenticate(req)) {
136
+ this._json(res, 401, { error: 'Unauthorized', message: 'Invalid or missing API key' });
137
+ return;
138
+ }
139
+
140
+ // Rate limit
141
+ const clientId = this._getClientId(req);
142
+ if (!this._checkRateLimit(clientId)) {
143
+ this._json(res, 429, { error: 'Rate limited', message: 'Too many requests', retryAfter: this.config.rateLimit.windowMs / 1000 });
144
+ return;
145
+ }
146
+
147
+ const url = new URL(req.url, `http://${req.headers.host}`);
148
+ const path = url.pathname;
149
+ const method = req.method;
150
+
151
+ try {
152
+ // Parse body for POST/PUT
153
+ let body = null;
154
+ if (method === 'POST' || method === 'PUT') {
155
+ body = await this._parseBody(req);
156
+ }
157
+
158
+ // Route request
159
+ const result = await this._route(method, path, body, req, url.searchParams);
160
+ this._json(res, result.status || 200, result.data || result);
161
+ } catch (err) {
162
+ const status = err.status || 500;
163
+ this._json(res, status, { error: err.message, code: err.code });
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Route request to appropriate handler
169
+ */
170
+ async _route(method, path, body, req, params) {
171
+ // Health
172
+ if (path === '/api/health' && method === 'GET') {
173
+ return {
174
+ data: {
175
+ status: 'ok',
176
+ version: '1.0.0',
177
+ uptime: process.uptime(),
178
+ connections: this.wsServer?.clientCount || 0,
179
+ adapters: listAdapters(),
180
+ features: {
181
+ websocket: this.config.websocket,
182
+ caching: true,
183
+ analytics: true,
184
+ plugins: true,
185
+ },
186
+ },
187
+ };
188
+ }
189
+
190
+ // Task submission
191
+ if (path === '/api/task' && method === 'POST') {
192
+ if (!body?.description) {
193
+ throw Object.assign(new Error('Missing required field: description'), { status: 400 });
194
+ }
195
+ const onProgress = (phase) => this.emit('task:progress', phase);
196
+ const result = await this._executeTask(body, onProgress);
197
+ return { data: result };
198
+ }
199
+
200
+ // Analytics
201
+ if (path === '/api/analytics' && method === 'GET') {
202
+ if (this.engine?.analytics) {
203
+ return { data: this.engine.analytics.getReport() };
204
+ }
205
+ return { data: { message: 'Analytics not available' } };
206
+ }
207
+
208
+ // Cache
209
+ if (path === '/api/cache' && method === 'DELETE') {
210
+ if (this.engine?.cache) {
211
+ this.engine.cache.clear();
212
+ return { data: { message: 'Cache cleared' } };
213
+ }
214
+ return { data: { message: 'Cache not available' } };
215
+ }
216
+
217
+ if (path.startsWith('/api/cache/') && method === 'DELETE') {
218
+ const key = decodeURIComponent(path.slice('/api/cache/'.length));
219
+ if (this.engine?.cache) {
220
+ this.engine.cache.delete(key);
221
+ return { data: { message: `Cache key '${key}' deleted` } };
222
+ }
223
+ return { data: { message: 'Cache not available' } };
224
+ }
225
+
226
+ // Plugins
227
+ if (path === '/api/plugins' && method === 'GET') {
228
+ if (this.engine?.plugins) {
229
+ return { data: this.engine.plugins.list() };
230
+ }
231
+ return { data: [] };
232
+ }
233
+
234
+ // Config
235
+ if (path === '/api/config' && method === 'GET') {
236
+ return { data: this.engine?.getConfig?.() || {} };
237
+ }
238
+ if (path === '/api/config' && method === 'POST') {
239
+ if (this.engine?.updateConfig) {
240
+ this.engine.updateConfig(body);
241
+ return { data: { message: 'Config updated' } };
242
+ }
243
+ return { data: { message: 'Config update not available' } };
244
+ }
245
+
246
+ // Script execution
247
+ if (path === '/api/script' && method === 'POST') {
248
+ if (!body?.script) {
249
+ throw Object.assign(new Error('Missing required field: script'), { status: 400 });
250
+ }
251
+ if (this.engine?.script) {
252
+ const result = await this.engine.script.execute(body.script);
253
+ return { data: result };
254
+ }
255
+ return { data: { message: 'Script engine not available' } };
256
+ }
257
+
258
+ // Knowledge base
259
+ if (path === '/api/knowledge' && method === 'GET') {
260
+ const query = params.get('q');
261
+ if (this.engine?.vault) {
262
+ const results = this.engine.vault.search(query || '');
263
+ return { data: results };
264
+ }
265
+ return { data: [] };
266
+ }
267
+ if (path === '/api/knowledge' && method === 'POST') {
268
+ if (this.engine?.vault && body?.key) {
269
+ this.engine.vault.store(body.key, body.value, body);
270
+ return { data: { message: 'Stored' } };
271
+ }
272
+ throw Object.assign(new Error('Missing key'), { status: 400 });
273
+ }
274
+
275
+ // Adapters info
276
+ if (path === '/api/adapters' && method === 'GET') {
277
+ return { data: { adapters: listAdapters() } };
278
+ }
279
+
280
+ // Python client generation
281
+ if (path === '/api/client/python' && method === 'GET') {
282
+ const { PythonBridgeAdapter } = await import('./adapters.mjs');
283
+ const adapter = new PythonBridgeAdapter({ host: this.config.host, port: this.config.port });
284
+ return { data: { code: adapter.generatePythonClient() } };
285
+ }
286
+
287
+ // OpenAPI spec
288
+ if (path === '/api/spec' && method === 'GET') {
289
+ const { RestAdapter } = await import('./adapters.mjs');
290
+ const adapter = new RestAdapter({ host: this.config.host, port: this.config.port });
291
+ return { data: adapter.getOpenAPISpec() };
292
+ }
293
+
294
+ throw Object.assign(new Error(`Not found: ${method} ${path}`), { status: 404 });
295
+ }
296
+
297
+ /**
298
+ * Execute a task through the engine
299
+ */
300
+ async _executeTask(taskData, onProgress) {
301
+ if (this.engine) {
302
+ return this.engine.process(taskData, onProgress);
303
+ }
304
+ // Fallback if no engine attached
305
+ return {
306
+ status: 'no_engine',
307
+ message: 'Prepia engine not attached. Connect an engine to process tasks.',
308
+ input: taskData,
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Authenticate request
314
+ */
315
+ _authenticate(req) {
316
+ if (!this.config.apiKey) return true;
317
+ const auth = req.headers.authorization;
318
+ if (!auth) return false;
319
+ const token = auth.replace(/^Bearer\s+/i, '');
320
+ return token === this.config.apiKey;
321
+ }
322
+
323
+ /**
324
+ * Get client identifier for rate limiting
325
+ */
326
+ _getClientId(req) {
327
+ return req.headers['x-prepia-agent-id'] || req.socket.remoteAddress || 'unknown';
328
+ }
329
+
330
+ /**
331
+ * Check rate limit for a client
332
+ */
333
+ _checkRateLimit(clientId) {
334
+ const now = Date.now();
335
+ const limit = this.rateLimits.get(clientId);
336
+
337
+ if (!limit || now > limit.resetAt) {
338
+ this.rateLimits.set(clientId, { count: 1, resetAt: now + this.config.rateLimit.windowMs });
339
+ return true;
340
+ }
341
+
342
+ if (limit.count >= this.config.rateLimit.maxRequests) {
343
+ return false;
344
+ }
345
+
346
+ limit.count++;
347
+ return true;
348
+ }
349
+
350
+ /**
351
+ * Parse JSON request body
352
+ */
353
+ _parseBody(req) {
354
+ return new Promise((resolve, reject) => {
355
+ const chunks = [];
356
+ req.on('data', (chunk) => chunks.push(chunk));
357
+ req.on('end', () => {
358
+ const raw = Buffer.concat(chunks).toString();
359
+ if (!raw) return resolve(null);
360
+ try {
361
+ resolve(JSON.parse(raw));
362
+ } catch {
363
+ reject(Object.assign(new Error('Invalid JSON body'), { status: 400 }));
364
+ }
365
+ });
366
+ req.on('error', reject);
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Send JSON response
372
+ */
373
+ _json(res, status, data) {
374
+ res.writeHead(status, { 'Content-Type': 'application/json' });
375
+ res.end(JSON.stringify(data));
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Create and start a Prepia connector
381
+ * @param {ConnectorConfig} config
382
+ * @param {import('../core/engine.mjs').PrepiaEngine} engine
383
+ * @returns {Promise<PrepiaConnector>}
384
+ */
385
+ export async function startConnector(config = {}, engine = null) {
386
+ const connector = new PrepiaConnector(config, engine);
387
+ await connector.start();
388
+ return connector;
389
+ }
390
+
391
+ export default PrepiaConnector;