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,483 @@
1
+ /**
2
+ * Prepia Universal Client SDK
3
+ * Drop-in client for ANY AI agent to connect to Prepia
4
+ * Works with: OpenClaw, AutoGPT, CrewAI, LangChain, custom bots, anything
5
+ *
6
+ * Usage:
7
+ * import { PrepiClient } from 'prepia/client';
8
+ * const prepi = new PrepiClient({ host: 'localhost', port: 4710 });
9
+ * const result = await prepi.task('Research top 5 phones and make a table');
10
+ */
11
+
12
+ import { EventEmitter } from 'events';
13
+ import http from 'http';
14
+
15
+ /**
16
+ * Simple WebSocket client using net.Socket
17
+ * No external dependencies — connects via raw TCP + WebSocket handshake
18
+ */
19
+ class SimpleWebSocket extends EventEmitter {
20
+ constructor(url, options = {}) {
21
+ super();
22
+ this.url = url;
23
+ this.options = options;
24
+ this.socket = null;
25
+ this.connected = false;
26
+ this.buffer = Buffer.alloc(0);
27
+ }
28
+
29
+ connect() {
30
+ const { hostname, port, pathname } = this._parseUrl(this.url);
31
+ const net = require('net');
32
+ const crypto = require('crypto');
33
+
34
+ return new Promise((resolve, reject) => {
35
+ this.socket = net.createConnection(port, hostname, () => {
36
+ const key = crypto.randomBytes(16).toString('base64');
37
+ const headers = [
38
+ `GET ${pathname} HTTP/1.1`,
39
+ `Host: ${hostname}:${port}`,
40
+ 'Upgrade: websocket',
41
+ 'Connection: Upgrade',
42
+ `Sec-WebSocket-Key: ${key}`,
43
+ 'Sec-WebSocket-Version: 13',
44
+ '',
45
+ '',
46
+ ].join('\r\n');
47
+ this.socket.write(headers);
48
+ });
49
+
50
+ let handshakeDone = false;
51
+ this.socket.on('data', (chunk) => {
52
+ if (!handshakeDone) {
53
+ const str = chunk.toString();
54
+ if (str.includes('101 Switching Protocols')) {
55
+ handshakeDone = true;
56
+ this.connected = true;
57
+ this.emit('open');
58
+ resolve();
59
+ }
60
+ return;
61
+ }
62
+ this._handleFrame(chunk);
63
+ });
64
+
65
+ this.socket.on('close', () => {
66
+ this.connected = false;
67
+ this.emit('close');
68
+ });
69
+
70
+ this.socket.on('error', (err) => {
71
+ this.emit('error', err);
72
+ if (!this.connected) reject(err);
73
+ });
74
+ });
75
+ }
76
+
77
+ send(data) {
78
+ if (!this.connected || !this.socket) return;
79
+ const payload = typeof data === 'object' ? JSON.stringify(data) : String(data);
80
+ const mask = require('crypto').randomBytes(4);
81
+ const payloadBuf = Buffer.from(payload);
82
+ let header;
83
+ if (payloadBuf.length < 126) {
84
+ header = Buffer.alloc(2);
85
+ header[0] = 0x81;
86
+ header[1] = 0x80 | payloadBuf.length;
87
+ } else {
88
+ header = Buffer.alloc(4);
89
+ header[0] = 0x81;
90
+ header[1] = 0x80 | 126;
91
+ header.writeUInt16BE(payloadBuf.length, 2);
92
+ }
93
+ const masked = Buffer.alloc(payloadBuf.length);
94
+ for (let i = 0; i < payloadBuf.length; i++) masked[i] = payloadBuf[i] ^ mask[i % 4];
95
+ this.socket.write(Buffer.concat([header, mask, masked]));
96
+ }
97
+
98
+ close() {
99
+ if (this.socket) this.socket.end();
100
+ this.connected = false;
101
+ }
102
+
103
+ _handleFrame(chunk) {
104
+ // Simplified frame parsing for client
105
+ if (chunk.length < 2) return;
106
+ const opcode = chunk[0] & 0x0f;
107
+ if (opcode === 0x08) { this.close(); return; }
108
+ if (opcode === 0x09) { /* ping → send pong */ return; }
109
+ let offset = 2;
110
+ let len = chunk[1] & 0x7f;
111
+ if (len === 126) { len = chunk.readUInt16BE(2); offset = 4; }
112
+ const payload = chunk.slice(offset, offset + len).toString();
113
+ try { this.emit('message', JSON.parse(payload)); } catch { this.emit('message', payload); }
114
+ }
115
+
116
+ _parseUrl(url) {
117
+ const u = new URL(url);
118
+ return { hostname: u.hostname, port: parseInt(u.port) || 80, pathname: u.pathname || '/' };
119
+ }
120
+ }
121
+
122
+ /**
123
+ * @typedef {Object} PrepiClientOptions
124
+ * @property {string} [host='localhost'] - Prepia server host
125
+ * @property {number} [port=4710] - Prepia server port
126
+ * @property {string} [apiKey] - API key for authentication
127
+ * @property {string} [protocol='http'] - 'http' or 'https'
128
+ * @property {number} [timeout=300000] - Default task timeout in ms
129
+ * @property {number} [retries=3] - Default retry count
130
+ * @property {boolean} [stream=false] - Enable streaming by default
131
+ * @property {string} [agentId] - Identifier for the connecting agent
132
+ * @property {string} [agentType='generic'] - Agent framework type
133
+ */
134
+
135
+ export class PrepiClient extends EventEmitter {
136
+ /** @param {PrepiClientOptions} options */
137
+ constructor(options = {}) {
138
+ super();
139
+ this.host = options.host || 'localhost';
140
+ this.port = options.port || 4710;
141
+ this.apiKey = options.apiKey || null;
142
+ this.protocol = options.protocol || 'http';
143
+ this.timeout = options.timeout || 300000;
144
+ this.retries = options.retries || 3;
145
+ this.stream = options.stream || false;
146
+ this.agentId = options.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
147
+ this.agentType = options.agentType || 'generic';
148
+ this.baseUrl = `${this.protocol}://${this.host}:${this.port}`;
149
+ this.ws = null;
150
+ this.connected = false;
151
+ this._taskId = 0;
152
+ }
153
+
154
+ /**
155
+ * Get request headers
156
+ * @returns {Object}
157
+ */
158
+ _headers() {
159
+ const h = {
160
+ 'Content-Type': 'application/json',
161
+ 'X-Prepia-Agent-Id': this.agentId,
162
+ 'X-Prepia-Agent-Type': this.agentType,
163
+ };
164
+ if (this.apiKey) h['Authorization'] = `Bearer ${this.apiKey}`;
165
+ return h;
166
+ }
167
+
168
+ /**
169
+ * Make an HTTP request to Prepia
170
+ * @param {string} method
171
+ * @param {string} path
172
+ * @param {Object} [body]
173
+ * @returns {Promise<Object>}
174
+ */
175
+ async _request(method, path, body = null) {
176
+ const url = `${this.baseUrl}${path}`;
177
+ const opts = {
178
+ method,
179
+ headers: this._headers(),
180
+ signal: AbortSignal.timeout(this.timeout),
181
+ };
182
+ if (body) opts.body = JSON.stringify(body);
183
+
184
+ let lastError;
185
+ for (let attempt = 1; attempt <= this.retries; attempt++) {
186
+ try {
187
+ const res = await fetch(url, opts);
188
+ const data = await res.json();
189
+ if (!res.ok) {
190
+ const err = new Error(data.error || `HTTP ${res.status}`);
191
+ err.status = res.status;
192
+ err.data = data;
193
+ throw err;
194
+ }
195
+ return data;
196
+ } catch (err) {
197
+ lastError = err;
198
+ if (attempt < this.retries && (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.status >= 500)) {
199
+ await new Promise(r => setTimeout(r, 1000 * attempt));
200
+ continue;
201
+ }
202
+ throw err;
203
+ }
204
+ }
205
+ throw lastError;
206
+ }
207
+
208
+ /**
209
+ * Connect via WebSocket for real-time streaming
210
+ * @returns {Promise<void>}
211
+ */
212
+ async connect() {
213
+ if (this.connected) return;
214
+ return new Promise((resolve, reject) => {
215
+ const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/ws';
216
+ this.ws = new SimpleWebSocket(wsUrl, {
217
+ headers: this._headers(),
218
+ });
219
+
220
+ this.ws.on('open', () => {
221
+ this.connected = true;
222
+ this.emit('connected');
223
+ resolve();
224
+ });
225
+
226
+ this.ws.on('message', (data) => {
227
+ try {
228
+ const msg = JSON.parse(data.toString());
229
+ this.emit(msg.event, msg.data);
230
+ this.emit('message', msg);
231
+ } catch {
232
+ this.emit('raw', data);
233
+ }
234
+ });
235
+
236
+ this.ws.on('close', () => {
237
+ this.connected = false;
238
+ this.emit('disconnected');
239
+ });
240
+
241
+ this.ws.on('error', (err) => {
242
+ this.emit('error', err);
243
+ if (!this.connected) reject(err);
244
+ });
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Disconnect WebSocket
250
+ */
251
+ disconnect() {
252
+ if (this.ws) {
253
+ this.ws.close();
254
+ this.ws = null;
255
+ this.connected = false;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Submit a task to Prepia (the main method agents use)
261
+ * @param {string|Object} task - Task description or task object
262
+ * @param {Object} [options] - Task options
263
+ * @returns {Promise<Object>} Task result
264
+ *
265
+ * @example
266
+ * // Simple string task
267
+ * const result = await prepi.task('What is the capital of France?');
268
+ *
269
+ * @example
270
+ * // Detailed task with options
271
+ * const result = await prepi.task({
272
+ * description: 'Research top phones and compare',
273
+ * mode: 'shot', // 'flash', 'shot', 'stream'
274
+ * tools: ['web-search', 'calculator'],
275
+ * cache: true,
276
+ * maxTokens: 2000,
277
+ * });
278
+ */
279
+ async task(task, options = {}) {
280
+ const taskObj = typeof task === 'string' ? { description: task } : task;
281
+ const payload = {
282
+ ...taskObj,
283
+ agentId: this.agentId,
284
+ agentType: this.agentType,
285
+ ...options,
286
+ };
287
+
288
+ if (this.stream || payload.mode === 'stream') {
289
+ return this._streamTask(payload);
290
+ }
291
+
292
+ return this._request('POST', '/api/task', payload);
293
+ }
294
+
295
+ /**
296
+ * Submit task with streaming progress
297
+ * @param {Object} payload
298
+ * @returns {Promise<Object>}
299
+ */
300
+ async _streamTask(payload) {
301
+ if (!this.connected) await this.connect();
302
+
303
+ return new Promise((resolve, reject) => {
304
+ const taskId = `task-${++this._taskId}-${Date.now()}`;
305
+ const result = { phases: [], final: null };
306
+
307
+ const onMessage = (msg) => {
308
+ if (msg.taskId !== taskId) return;
309
+
310
+ switch (msg.event) {
311
+ case 'task:progress':
312
+ result.phases.push(msg.data);
313
+ this.emit('progress', msg.data);
314
+ break;
315
+ case 'task:complete':
316
+ result.final = msg.data;
317
+ cleanup();
318
+ resolve(result);
319
+ break;
320
+ case 'task:error':
321
+ cleanup();
322
+ reject(new Error(msg.data.error));
323
+ break;
324
+ }
325
+ };
326
+
327
+ const cleanup = () => {
328
+ this.removeListener('message', onMessage);
329
+ };
330
+
331
+ this.on('message', onMessage);
332
+
333
+ this.ws.send(JSON.stringify({
334
+ event: 'task:submit',
335
+ taskId,
336
+ data: payload,
337
+ }));
338
+
339
+ // Timeout safety
340
+ setTimeout(() => {
341
+ cleanup();
342
+ reject(new Error('Task timeout'));
343
+ }, this.timeout);
344
+ });
345
+ }
346
+
347
+ /**
348
+ * Quick query — instant answers (PrepiFlash mode)
349
+ * @param {string} query
350
+ * @returns {Promise<Object>}
351
+ *
352
+ * @example
353
+ * const r = await prepi.quick('What is 2+2?');
354
+ * console.log(r.answer); // 4
355
+ */
356
+ async quick(query) {
357
+ return this._request('POST', '/api/task', {
358
+ description: query,
359
+ mode: 'flash',
360
+ agentId: this.agentId,
361
+ agentType: this.agentType,
362
+ });
363
+ }
364
+
365
+ /**
366
+ * Optimized single-LLM-call task (PrepiShot mode)
367
+ * @param {string} description
368
+ * @param {Object} [options]
369
+ * @returns {Promise<Object>}
370
+ */
371
+ async shot(description, options = {}) {
372
+ return this._request('POST', '/api/task', {
373
+ description,
374
+ mode: 'shot',
375
+ agentId: this.agentId,
376
+ agentType: this.agentType,
377
+ ...options,
378
+ });
379
+ }
380
+
381
+ /**
382
+ * Check Prepia server health
383
+ * @returns {Promise<Object>}
384
+ */
385
+ async health() {
386
+ return this._request('GET', '/api/health');
387
+ }
388
+
389
+ /**
390
+ * Get usage analytics
391
+ * @param {Object} [filters]
392
+ * @returns {Promise<Object>}
393
+ */
394
+ async analytics(filters = {}) {
395
+ const params = new URLSearchParams(filters).toString();
396
+ return this._request('GET', `/api/analytics${params ? '?' + params : ''}`);
397
+ }
398
+
399
+ /**
400
+ * Clear cache
401
+ * @param {string} [key] - Specific key, or all if omitted
402
+ * @returns {Promise<Object>}
403
+ */
404
+ async clearCache(key) {
405
+ const path = key ? `/api/cache/${encodeURIComponent(key)}` : '/api/cache';
406
+ return this._request('DELETE', path);
407
+ }
408
+
409
+ /**
410
+ * List installed plugins
411
+ * @returns {Promise<Object>}
412
+ */
413
+ async plugins() {
414
+ return this._request('GET', '/api/plugins');
415
+ }
416
+
417
+ /**
418
+ * Get/set configuration
419
+ * @param {Object} [config]
420
+ * @returns {Promise<Object>}
421
+ */
422
+ async config(config) {
423
+ if (config) return this._request('POST', '/api/config', config);
424
+ return this._request('GET', '/api/config');
425
+ }
426
+
427
+ /**
428
+ * Execute a PrepiScript
429
+ * @param {string} script
430
+ * @returns {Promise<Object>}
431
+ */
432
+ async script(script) {
433
+ return this._request('POST', '/api/script', { script });
434
+ }
435
+
436
+ /**
437
+ * Get knowledge base entries
438
+ * @param {string} [query]
439
+ * @returns {Promise<Object>}
440
+ */
441
+ async knowledge(query) {
442
+ const params = query ? `?q=${encodeURIComponent(query)}` : '';
443
+ return this._request('GET', `/api/knowledge${params}`);
444
+ }
445
+
446
+ /**
447
+ * Store something in the knowledge base
448
+ * @param {string} key
449
+ * @param {*} value
450
+ * @param {Object} [meta]
451
+ * @returns {Promise<Object>}
452
+ */
453
+ async store(key, value, meta = {}) {
454
+ return this._request('POST', '/api/knowledge', { key, value, ...meta });
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Create a Prepia client with minimal config
460
+ * @param {string|Object} [config] - URL string or options object
461
+ * @returns {PrepiClient}
462
+ *
463
+ * @example
464
+ * // From URL string
465
+ * const prepi = prepiConnect('http://localhost:4710');
466
+ *
467
+ * @example
468
+ * // Simple options
469
+ * const prepi = prepiConnect({ port: 4710, apiKey: 'my-key' });
470
+ */
471
+ export function prepiConnect(config = {}) {
472
+ if (typeof config === 'string') {
473
+ const url = new URL(config);
474
+ return new PrepiClient({
475
+ protocol: url.protocol.replace(':', ''),
476
+ host: url.hostname,
477
+ port: parseInt(url.port) || 4710,
478
+ });
479
+ }
480
+ return new PrepiClient(config);
481
+ }
482
+
483
+ export default PrepiClient;