pacs-core 0.1.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/src/cli.js ADDED
@@ -0,0 +1,384 @@
1
+ /**
2
+ * PACS Core — CLI Tool
3
+ * Command-line interface for PACS operations
4
+ *
5
+ * Usage:
6
+ * node src/cli.js <command> [args]
7
+ */
8
+
9
+ const { readFileSync } = require('fs');
10
+
11
+ // --- Argument parsing (no external libs) ---
12
+
13
+ const argv = process.argv.slice(2);
14
+
15
+ function flag(name) {
16
+ const idx = argv.findIndex((a) => a === `--${name}` || a === `-${name[0]}`);
17
+ if (idx === -1) return null;
18
+ return argv[idx + 1] !== undefined && !argv[idx + 1].startsWith('-')
19
+ ? argv.splice(idx + 1, 1)[0]
20
+ : true;
21
+ }
22
+
23
+ function boolFlag(name) {
24
+ return argv.includes(`--${name}`) || argv.includes(`-${name[0]}`);
25
+ }
26
+
27
+ function positional(index) {
28
+ return argv[index];
29
+ }
30
+
31
+ function cmd(first, second) {
32
+ return argv[0] === first && (second === undefined || argv[1] === second);
33
+ }
34
+
35
+ // --- Output helpers ---
36
+
37
+ function info(msg) {
38
+ console.log(msg);
39
+ }
40
+
41
+ function error(msg) {
42
+ console.error(`[ERROR] ${msg}`);
43
+ process.exit(1);
44
+ }
45
+
46
+ function success(msg) {
47
+ console.log(msg);
48
+ }
49
+
50
+ // --- Lazy-loaded modules (CJS) ---
51
+
52
+ function getAgentRegistry() {
53
+ return require('./agent-registry.js').default;
54
+ }
55
+
56
+ function getMemoryStore() {
57
+ return require('./memory-store.js');
58
+ }
59
+
60
+ function getLearnMode() {
61
+ return require('./learn-mode.js').default;
62
+ }
63
+
64
+ function getBridgeStatus() {
65
+ try {
66
+ return require('./openclaw-bridge.cjs').status;
67
+ } catch {
68
+ return () => ({ initialized: false, mode: 'unknown', enabled: false, openClawConnected: false });
69
+ }
70
+ }
71
+
72
+ // --- Commands ---
73
+
74
+ function showHelp() {
75
+ console.log(`
76
+ PACS — Personal AI Control System
77
+ ==================================
78
+
79
+ Usage: pacs <command> [options]
80
+
81
+ Commands:
82
+ pacs --help
83
+ Show this help text.
84
+
85
+ pacs init
86
+ Initialize PACS storage directory.
87
+
88
+ pacs status
89
+ Show PACS status (bridge, mode, agents count, memory facts).
90
+
91
+ pacs agents list
92
+ List all registered agents.
93
+
94
+ pacs agents add <name> [--text "..."] [--md ./file.md]
95
+ Register a new agent. Use --text or --md for description.
96
+
97
+ pacs agents remove <name>
98
+ Remove an agent by name.
99
+
100
+ pacs agents get <name>
101
+ Show details for a specific agent.
102
+
103
+ pacs memory list
104
+ List all memory facts.
105
+
106
+ pacs memory search <query>
107
+ Search memory facts for a query string.
108
+
109
+ pacs memory add "<fact>"
110
+ Add a new memory fact.
111
+
112
+ pacs memory forget "<fact>"
113
+ Remove a memory fact by content match.
114
+
115
+ pacs learn-mode get
116
+ Show the current learn mode (safe | explicit | auto).
117
+
118
+ pacs learn-mode set <mode>
119
+ Set learn mode to safe, explicit, or auto.
120
+
121
+ pacs messages <agent>
122
+ Show message history for an agent.
123
+
124
+ Examples:
125
+ pacs agents add finbot --text "Finance agent"
126
+ pacs memory add "Lech prefers German in the morning"
127
+ pacs memory search "preferences"
128
+ pacs learn-mode set explicit
129
+ `);
130
+ }
131
+
132
+ // -- init --
133
+
134
+ function handleInit() {
135
+ const ms = getMemoryStore();
136
+ ms.ensureStorageDir();
137
+ info('PACS initialisiert. Storage: ~/.openclaw/pacs/');
138
+ info('Hinweis: PACS_ENCRYPTION_KEY muss in der Umgebung gesetzt sein.');
139
+ success('✓ Init abgeschlossen.');
140
+ }
141
+
142
+ // -- status --
143
+
144
+ function handleStatus() {
145
+ const bridge = getBridgeStatus()();
146
+ let agentCount = '?';
147
+ let factCount = '?';
148
+
149
+ try {
150
+ const registry = getAgentRegistry();
151
+ agentCount = registry.listAgents().length;
152
+ } catch {
153
+ agentCount = 'Fehler (falscher Key?)';
154
+ }
155
+
156
+ try {
157
+ const ms = getMemoryStore();
158
+ const memory = ms.readMemory() || {};
159
+ factCount = memory.facts ? memory.facts.length : 0;
160
+ } catch {
161
+ factCount = 'Fehler (falscher Key?)';
162
+ }
163
+
164
+ info('=== PACS Status ===');
165
+ info(`Bridge initialisiert : ${bridge.initialized ? 'ja' : 'nein'}`);
166
+ info(`Bridge Mode : ${bridge.mode}`);
167
+ info(`Bridge aktiviert : ${bridge.enabled ? 'ja' : 'nein'}`);
168
+ info(`OpenClaw verbunden : ${bridge.openClawConnected ? 'ja' : 'nein'}`);
169
+ info(`Agenten registriert : ${agentCount}`);
170
+ info(`Memory Facts : ${factCount}`);
171
+ success('✓ Status abgerufen.');
172
+ }
173
+
174
+ // -- agents --
175
+
176
+ function handleAgents() {
177
+ const registry = getAgentRegistry();
178
+ const sub = positional(1);
179
+
180
+ if (cmd('list') || sub === 'list') {
181
+ const agents = registry.listAgents();
182
+ if (agents.length === 0) {
183
+ info('Keine Agenten registriert.');
184
+ return;
185
+ }
186
+ info(`=== Agenten (${agents.length}) ===`);
187
+ for (const a of agents) {
188
+ info(` ${String(a.name).padEnd(20)} ${String(a.domain).padEnd(15)} ${a.status}`);
189
+ }
190
+ return;
191
+ }
192
+
193
+ if (sub === 'add') {
194
+ const name = positional(2);
195
+ if (!name) error('Usage: pacs agents add <name> [--text "..."] [--md ./file.md]');
196
+
197
+ let description = flag('text') || '';
198
+ const mdFile = flag('md');
199
+ if (mdFile) {
200
+ try {
201
+ description = readFileSync(mdFile, 'utf8').slice(0, 500);
202
+ } catch {
203
+ error(`Datei nicht gefunden: ${mdFile}`);
204
+ }
205
+ }
206
+
207
+ const agent = registry.registerAgent(name, { description });
208
+ success(`✓ Agent "${agent.name}" registriert (ID: ${agent.id})`);
209
+ return;
210
+ }
211
+
212
+ if (sub === 'remove') {
213
+ const name = positional(2);
214
+ if (!name) error('Usage: pacs agents remove <name>');
215
+ const removed = registry.removeAgent(name);
216
+ if (removed) {
217
+ success(`✓ Agent "${name}" entfernt.`);
218
+ } else {
219
+ error(`Agent "${name}" nicht gefunden.`);
220
+ }
221
+ return;
222
+ }
223
+
224
+ if (sub === 'get') {
225
+ const name = positional(2);
226
+ if (!name) error('Usage: pacs agents get <name>');
227
+ const agent = registry.getAgent(name);
228
+ if (!agent) error(`Agent "${name}" nicht gefunden.`);
229
+ info(`=== Agent: ${agent.name} ===`);
230
+ info(` ID : ${agent.id}`);
231
+ info(` Domain : ${agent.domain}`);
232
+ info(` Status : ${agent.status}`);
233
+ info(` Capabilities: ${(agent.capabilities || []).join(', ') || '(none)'}`);
234
+ info(` Created : ${agent.createdAt}`);
235
+ info(` Last Active : ${agent.lastActive}`);
236
+ info(` Description : ${agent.description}`);
237
+ return;
238
+ }
239
+
240
+ error(`Unknown agents subcommand: ${sub}. Use: list, add, remove, get`);
241
+ }
242
+
243
+ // -- memory --
244
+
245
+ function handleMemory() {
246
+ const ms = getMemoryStore();
247
+ const sub = positional(1);
248
+
249
+ if (sub === 'list') {
250
+ const mem = ms.readMemory() || { facts: [] };
251
+ const facts = mem.facts || [];
252
+ if (facts.length === 0) {
253
+ info('Keine Memory Facts gespeichert.');
254
+ return;
255
+ }
256
+ info(`=== Memory (${facts.length}) ===`);
257
+ for (const fact of facts) {
258
+ info(` • ${fact}`);
259
+ }
260
+ return;
261
+ }
262
+
263
+ if (sub === 'search') {
264
+ const query = argv.slice(2).join(' ').replace(/^(search|list)\s*/i, '');
265
+ if (!query) error('Usage: pacs memory search <query>');
266
+ const results = ms.searchFacts(query);
267
+ if (results.length === 0) {
268
+ info(`Keine Ergebnisse für "${query}".`);
269
+ return;
270
+ }
271
+ info(`=== Suchergebnisse für "${query}" (${results.length}) ===`);
272
+ for (const r of results) info(` • ${r}`);
273
+ return;
274
+ }
275
+
276
+ if (sub === 'add') {
277
+ const fact = argv.slice(2).join(' ').replace(/^(add)\s*/i, '');
278
+ if (!fact) error('Usage: pacs memory add "<fact>"');
279
+ ms.addFact(fact);
280
+ success(`✓ Fact gespeichert: "${fact}"`);
281
+ return;
282
+ }
283
+
284
+ if (sub === 'forget') {
285
+ const fact = argv.slice(2).join(' ').replace(/^(forget)\s*/i, '');
286
+ if (!fact) error('Usage: pacs memory forget "<fact>"');
287
+ ms.removeFact(fact);
288
+ success(`✓ Fact entfernt.`);
289
+ return;
290
+ }
291
+
292
+ error(`Unknown memory subcommand: ${sub}. Use: list, search, add, forget`);
293
+ }
294
+
295
+ // -- learn-mode --
296
+
297
+ function handleLearnMode() {
298
+ const lm = getLearnMode();
299
+ const sub = positional(1);
300
+
301
+ if (sub === 'get' || !sub) {
302
+ success(`Learn-Mode: ${lm.getMode()}`);
303
+ return;
304
+ }
305
+
306
+ if (sub === 'set') {
307
+ const newMode = positional(2);
308
+ if (!newMode) error('Usage: pacs learn-mode set <safe|explicit|auto>');
309
+ const valid = ['safe', 'explicit', 'auto'];
310
+ if (!valid.includes(newMode.toLowerCase())) {
311
+ error(`Ungültiger Modus: ${newMode}. Gültig: ${valid.join(', ')}`);
312
+ }
313
+ lm.setMode(newMode.toLowerCase());
314
+ success(`✓ Learn-Mode gesetzt auf: ${newMode.toLowerCase()}`);
315
+ return;
316
+ }
317
+
318
+ error(`Unknown learn-mode subcommand: ${sub}. Use: get, set`);
319
+ }
320
+
321
+ // -- messages --
322
+
323
+ function handleMessages() {
324
+ const registry = getAgentRegistry();
325
+ const agentName = positional(1);
326
+ if (!agentName) error('Usage: pacs messages <agent>');
327
+ const agent = registry.getAgent(agentName);
328
+ if (!agent) error(`Agent "${agentName}" nicht gefunden.`);
329
+
330
+ const messages = agent.metadata && agent.metadata.messages ? agent.metadata.messages : [];
331
+ if (messages.length === 0) {
332
+ info(`Keine Nachrichten für Agent "${agentName}".`);
333
+ return;
334
+ }
335
+ info(`=== Nachrichten für ${agentName} (${messages.length}) ===`);
336
+ for (const m of messages) {
337
+ info(` [${m.role}] ${m.text}`);
338
+ }
339
+ }
340
+
341
+ // --- Main router ---
342
+
343
+ function main() {
344
+ const first = argv[0];
345
+
346
+ if (!first || first === '--help' || first === '-h' || first === 'help') {
347
+ showHelp();
348
+ return;
349
+ }
350
+
351
+ if (cmd('init')) {
352
+ handleInit();
353
+ return;
354
+ }
355
+
356
+ if (cmd('status')) {
357
+ handleStatus();
358
+ return;
359
+ }
360
+
361
+ if (cmd('agents')) {
362
+ handleAgents();
363
+ return;
364
+ }
365
+
366
+ if (cmd('memory')) {
367
+ handleMemory();
368
+ return;
369
+ }
370
+
371
+ if (cmd('learn-mode')) {
372
+ handleLearnMode();
373
+ return;
374
+ }
375
+
376
+ if (cmd('messages')) {
377
+ handleMessages();
378
+ return;
379
+ }
380
+
381
+ error(`Unknown command: ${first}. Run "pacs --help" for usage.`);
382
+ }
383
+
384
+ main();
package/src/crypto.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * PACS Core — Cryptography Module
3
+ * AES-256-GCM encryption/decryption using Node.js built-in crypto
4
+ * Key is read from PACS_ENCRYPTION_KEY environment variable only.
5
+ */
6
+
7
+ const crypto = require('crypto');
8
+
9
+ const ALGORITHM = 'aes-256-gcm';
10
+ const IV_LENGTH = 16;
11
+ const AUTH_TAG_LENGTH = 16;
12
+ const SALT_LENGTH = 32;
13
+ const KEY_LENGTH = 32;
14
+ const PBKDF2_ITERATIONS = 100000;
15
+
16
+ /**
17
+ * Get encryption key from environment variable.
18
+ * Derives a proper 256-bit key from the env value using PBKDF2.
19
+ * @param {string} [password] - Optional password override (for testing)
20
+ * @returns {Buffer} 32-byte encryption key
21
+ */
22
+ function getKey(password) {
23
+ const envKey = password || process.env.PACS_ENCRYPTION_KEY;
24
+
25
+ if (!envKey) {
26
+ throw new Error(
27
+ 'PACS_ENCRYPTION_KEY environment variable is not set. ' +
28
+ 'Please set it before using encrypted storage.\n' +
29
+ 'Example: export PACS_ENCRYPTION_KEY="your-secure-random-key-here"'
30
+ );
31
+ }
32
+
33
+ // Use PBKDF2 to derive a proper 256-bit key from the env value
34
+ // Salt is fixed for deterministic derivation (salt is stored with ciphertext)
35
+ const salt = Buffer.from('pacs-core-salt-v1', 'utf8');
36
+ return crypto.pbkdf2Sync(envKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
37
+ }
38
+
39
+ /**
40
+ * Encrypt plaintext using AES-256-GCM.
41
+ * @param {string} plaintext - The text to encrypt
42
+ * @param {string} [password] - Optional password override
43
+ * @returns {string} Base64-encoded string: iv:authTag:ciphertext
44
+ */
45
+ function encrypt(plaintext, password) {
46
+ if (!plaintext) {
47
+ throw new Error('Plaintext is required for encryption');
48
+ }
49
+
50
+ const key = getKey(password);
51
+ const iv = crypto.randomBytes(IV_LENGTH);
52
+
53
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
54
+ authTagLength: AUTH_TAG_LENGTH,
55
+ });
56
+
57
+ let ciphertext = cipher.update(plaintext, 'utf8', 'base64');
58
+ ciphertext += cipher.final('base64');
59
+
60
+ const authTag = cipher.getAuthTag();
61
+
62
+ // Format: iv:authTag:ciphertext (all base64)
63
+ return [
64
+ iv.toString('base64'),
65
+ authTag.toString('base64'),
66
+ ciphertext,
67
+ ].join(':');
68
+ }
69
+
70
+ /**
71
+ * Decrypt ciphertext using AES-256-GCM.
72
+ * @param {string} encryptedData - Base64-encoded string: iv:authTag:ciphertext
73
+ * @param {string} [password] - Optional password override
74
+ * @returns {string} Decrypted plaintext
75
+ */
76
+ function decrypt(encryptedData, password) {
77
+ if (!encryptedData) {
78
+ throw new Error('Encrypted data is required for decryption');
79
+ }
80
+
81
+ const parts = encryptedData.split(':');
82
+ if (parts.length !== 3) {
83
+ throw new Error('Invalid encrypted data format. Expected iv:authTag:ciphertext');
84
+ }
85
+
86
+ const [ivBase64, authTagBase64, ciphertext] = parts;
87
+ const key = getKey(password);
88
+
89
+ const iv = Buffer.from(ivBase64, 'base64');
90
+ const authTag = Buffer.from(authTagBase64, 'base64');
91
+
92
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, {
93
+ authTagLength: AUTH_TAG_LENGTH,
94
+ });
95
+
96
+ decipher.setAuthTag(authTag);
97
+
98
+ let plaintext = decipher.update(ciphertext, 'base64', 'utf8');
99
+ plaintext += decipher.final('utf8');
100
+
101
+ return plaintext;
102
+ }
103
+
104
+ /**
105
+ * Generate a secure random key suitable for PACS_ENCRYPTION_KEY.
106
+ * @returns {string} 64-character hex string (256 bits)
107
+ */
108
+ function generateKey() {
109
+ return crypto.randomBytes(32).toString('hex');
110
+ }
111
+
112
+ module.exports = {
113
+ encrypt,
114
+ decrypt,
115
+ generateKey,
116
+ getKey,
117
+ ALGORITHM,
118
+ };
package/src/index.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * PACS Core — Main Entry Point
3
+ * Personal AI Control System
4
+ */
5
+
6
+ const crypto = require('./crypto');
7
+ const memoryStore = require('./memory-store');
8
+ const triggerParser = require('./trigger-parser');
9
+
10
+ // Default export object
11
+ const pacs = {
12
+ // Crypto utilities
13
+ crypto: {
14
+ encrypt: crypto.encrypt,
15
+ decrypt: crypto.decrypt,
16
+ generateKey: crypto.generateKey,
17
+ },
18
+
19
+ // Memory store
20
+ memory: {
21
+ read: memoryStore.readMemory,
22
+ write: memoryStore.writeMemory,
23
+ addFact: memoryStore.addFact,
24
+ removeFact: memoryStore.removeFact,
25
+ search: memoryStore.searchFacts,
26
+ },
27
+
28
+ // Routing store
29
+ routing: {
30
+ read: memoryStore.readRouting,
31
+ write: memoryStore.writeRouting,
32
+ addHint: memoryStore.addRoutingHint,
33
+ },
34
+
35
+ // Core rules
36
+ rules: {
37
+ read: memoryStore.readCoreRules,
38
+ write: memoryStore.writeCoreRules,
39
+ addRule: memoryStore.addRule,
40
+ },
41
+
42
+ // Trigger parsing
43
+ triggers: {
44
+ parse: triggerParser.parse,
45
+ find: triggerParser.findTrigger,
46
+ contains: triggerParser.containsTrigger,
47
+ detectLearnMode: triggerParser.detectLearnMode,
48
+ getHelp: triggerParser.getHelpText,
49
+ getAll: triggerParser.getAllTriggers,
50
+ },
51
+
52
+ // Storage info
53
+ getStorageDir: memoryStore.getStorageDir,
54
+ FILES: memoryStore.FILES,
55
+ };
56
+
57
+ module.exports = pacs;
58
+ module.exports.AgentRegistry = require('./agent-registry.js').default;
59
+ module.exports.InterAgentBus = require('./inter-agent-bus.js').default;
60
+ module.exports.AgentCreator = require('./agent-creator.js').default;
61
+ module.exports.RoutingEngine = require('./routing-engine.js').default;
62
+ module.exports.LearningLoop = require('./learning-loop.js').default;
63
+ module.exports.LearnMode = require('./learn-mode.js').default;
64
+
65
+ // ES Module re-exports (for ESM consumers)
66
+ module.exports.AgentRegistryESM = () => import('./agent-registry.js');
67
+ module.exports.InterAgentBusESM = () => import('./inter-agent-bus.js');
68
+ module.exports.AgentCreatorESM = () => import('./agent-creator.js');
69
+ module.exports.RoutingEngineESM = () => import('./routing-engine.js');
70
+ module.exports.LearningLoopESM = () => import('./learning-loop.js');
71
+ module.exports.LearnModeESM = () => import('./learn-mode.js');
72
+
73
+ // OpenClaw Bridge
74
+ module.exports.OpenClawBridge = require('./openclaw-bridge.cjs');