claudity 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.
@@ -0,0 +1,111 @@
1
+ const { Client, LocalAuth } = require('whatsapp-web.js');
2
+ const QRCode = require('qrcode');
3
+
4
+ const MAX_RESPONSE_LENGTH = 4000;
5
+
6
+ let client = null;
7
+
8
+ function log(msg) {
9
+ console.log(`[whatsapp] ${msg}`);
10
+ }
11
+
12
+ function parseMessage(text) {
13
+ if (!text) return null;
14
+ const match = text.match(/^(\w+):\s*(.+)$/s);
15
+ if (!match) return null;
16
+ return { agent: match[1].toLowerCase(), command: match[2].trim() };
17
+ }
18
+
19
+ function start(config, callbacks) {
20
+ const { onStatus } = callbacks || {};
21
+
22
+ const chatModule = require('./chat');
23
+ const { stmts } = require('../db');
24
+
25
+ client = new Client({
26
+ authStrategy: new LocalAuth(),
27
+ puppeteer: { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }
28
+ });
29
+
30
+ client.on('qr', async (qr) => {
31
+ log('qr code received');
32
+ try {
33
+ const dataUrl = await QRCode.toDataURL(qr, { width: 256, margin: 2 });
34
+ if (onStatus) onStatus('qr', dataUrl);
35
+ } catch (err) {
36
+ log('qr generation failed: ' + err.message);
37
+ }
38
+ });
39
+
40
+ client.on('ready', () => {
41
+ const name = client.info.pushname || client.info.wid.user;
42
+ log(`logged in as ${name}`);
43
+ if (onStatus) onStatus('connected', name);
44
+ });
45
+
46
+ client.on('auth_failure', (msg) => {
47
+ log('auth failed: ' + msg);
48
+ if (onStatus) onStatus('error', msg);
49
+ });
50
+
51
+ client.on('disconnected', (reason) => {
52
+ log('disconnected: ' + reason);
53
+ if (onStatus) onStatus('disconnected', reason);
54
+ });
55
+
56
+ let busy = false;
57
+
58
+ client.on('message_create', async (message) => {
59
+
60
+ let parsed = parseMessage(message.body);
61
+ let agent;
62
+ if (parsed) {
63
+ agent = stmts.getAgentByName.get(parsed.agent);
64
+ if (!agent) {
65
+ message.reply(`no agent named "${parsed.agent}"`).catch(() => {});
66
+ return;
67
+ }
68
+ } else {
69
+ if (busy) return;
70
+ agent = stmts.getDefaultAgent.get();
71
+ if (!agent) return;
72
+ if (!message.body || !message.body.trim()) return;
73
+ parsed = { agent: agent.name, command: message.body.trim() };
74
+ }
75
+
76
+ log(`${parsed.agent}: ${parsed.command}`);
77
+ busy = true;
78
+
79
+ try {
80
+ const result = await chatModule.enqueueMessage(agent.id, parsed.command, {
81
+ onAck: (text) => message.reply(text).catch(() => {})
82
+ });
83
+ if (result && result.content) {
84
+ let text = result.content;
85
+ if (text.length > MAX_RESPONSE_LENGTH) {
86
+ text = text.slice(0, MAX_RESPONSE_LENGTH) + '...';
87
+ }
88
+ await message.reply(text);
89
+ }
90
+ } catch (err) {
91
+ message.reply(`error: ${err.message}`).catch(() => {});
92
+ }
93
+
94
+ setTimeout(() => { busy = false; }, 2000);
95
+ });
96
+
97
+ client.initialize().catch((err) => {
98
+ log('init failed: ' + err.message);
99
+ if (onStatus) onStatus('error', err.message);
100
+ });
101
+ }
102
+
103
+ function stop() {
104
+ if (client) {
105
+ client.destroy().catch(() => {});
106
+ client = null;
107
+ }
108
+ log('disconnected');
109
+ }
110
+
111
+ module.exports = { start, stop };
@@ -0,0 +1,162 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const BASE_DIR = path.join(__dirname, '..', '..', 'data', 'agents');
5
+
6
+ const BOOTSTRAP_TEMPLATE = `# bootstrap
7
+
8
+ you just came online for the first time. you don't know who you are yet.
9
+
10
+ conduct a short identity ritual with the user:
11
+
12
+ 1. first message: introduce yourself and ask who you are and who the user is — combine questions
13
+ 2. second message: if you have enough to work with, write your files and complete bootstrap. if not, ask one final clarifying question.
14
+ 3. third message: you MUST write all files and call complete_bootstrap. no exceptions.
15
+
16
+ if the user gives you enough context upfront, skip questions entirely — write files and complete bootstrap immediately.
17
+
18
+ use write_workspace to create your identity files:
19
+ - SOUL.md — your personality, values, philosophy, how you think and behave
20
+ - IDENTITY.md — your name and signature traits
21
+ - USER.md — who the user is, their preferences
22
+ - HEARTBEAT.md — what to check on when you wake up periodically
23
+ - MEMORY.md — initial memories from this conversation
24
+
25
+ then call complete_bootstrap to finish setup.
26
+
27
+ CRITICAL: you MUST call complete_bootstrap or setup will not be saved. bootstrap MUST complete within 3 exchanges. after writing files, ALWAYS call complete_bootstrap in the same response. never end a response after writing files without also calling complete_bootstrap.
28
+ `;
29
+
30
+ function sanitizeName(name) {
31
+ return name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
32
+ }
33
+
34
+ function agentDir(agentName) {
35
+ return path.join(BASE_DIR, sanitizeName(agentName));
36
+ }
37
+
38
+ function resolvePath(agentName, filePath) {
39
+ const dir = agentDir(agentName);
40
+ const resolved = path.resolve(dir, filePath);
41
+ if (!resolved.startsWith(dir)) throw new Error('path escapes workspace');
42
+ return resolved;
43
+ }
44
+
45
+ function initWorkspace(agentName) {
46
+ const dir = agentDir(agentName);
47
+ fs.mkdirSync(path.join(dir, 'memory'), { recursive: true });
48
+ fs.writeFileSync(path.join(dir, 'BOOTSTRAP.md'), BOOTSTRAP_TEMPLATE);
49
+ }
50
+
51
+ function readFile(agentName, filename) {
52
+ try {
53
+ return fs.readFileSync(resolvePath(agentName, filename), 'utf-8');
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function writeFile(agentName, filename, content) {
60
+ const filePath = resolvePath(agentName, filename);
61
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
62
+ fs.writeFileSync(filePath, content);
63
+ }
64
+
65
+ function localDate(d) {
66
+ const y = d.getFullYear();
67
+ const m = String(d.getMonth() + 1).padStart(2, '0');
68
+ const day = String(d.getDate()).padStart(2, '0');
69
+ return `${y}-${m}-${day}`;
70
+ }
71
+
72
+ function appendToDaily(agentName, entry) {
73
+ const now = new Date();
74
+ const today = localDate(now);
75
+ const filename = `memory/${today}.md`;
76
+ const filePath = resolvePath(agentName, filename);
77
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
78
+ const timestamp = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });
79
+ fs.appendFileSync(filePath, `- [${timestamp}] ${entry}\n`);
80
+ }
81
+
82
+ function getDailyLogs(agentName) {
83
+ const today = new Date();
84
+ const yesterday = new Date(today);
85
+ yesterday.setDate(yesterday.getDate() - 1);
86
+
87
+ const todayStr = localDate(today);
88
+ const yesterdayStr = localDate(yesterday);
89
+
90
+ const todayLog = readFile(agentName, `memory/${todayStr}.md`);
91
+ const yesterdayLog = readFile(agentName, `memory/${yesterdayStr}.md`);
92
+
93
+ let result = '';
94
+ if (yesterdayLog) result += `## ${yesterdayStr}\n${yesterdayLog}\n`;
95
+ if (todayLog) result += `## ${todayStr}\n${todayLog}`;
96
+ return result.trim() || null;
97
+ }
98
+
99
+ function deleteFile(agentName, filename) {
100
+ try {
101
+ fs.unlinkSync(resolvePath(agentName, filename));
102
+ } catch {}
103
+ }
104
+
105
+ function renameWorkspace(oldName, newName) {
106
+ const oldDir = agentDir(oldName);
107
+ const newDir = agentDir(newName);
108
+ if (fs.existsSync(oldDir) && !fs.existsSync(newDir)) {
109
+ fs.renameSync(oldDir, newDir);
110
+ }
111
+ }
112
+
113
+ function deleteWorkspace(agentName) {
114
+ const dir = agentDir(agentName);
115
+ if (fs.existsSync(dir)) {
116
+ fs.rmSync(dir, { recursive: true, force: true });
117
+ }
118
+ }
119
+
120
+ function listFiles(agentName) {
121
+ const dir = agentDir(agentName);
122
+ if (!fs.existsSync(dir)) return [];
123
+
124
+ const results = [];
125
+ function walk(current, prefix) {
126
+ const entries = fs.readdirSync(current, { withFileTypes: true });
127
+ for (const entry of entries) {
128
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
129
+ if (entry.isDirectory()) {
130
+ walk(path.join(current, entry.name), rel);
131
+ } else {
132
+ results.push(rel);
133
+ }
134
+ }
135
+ }
136
+ walk(dir, '');
137
+ return results;
138
+ }
139
+
140
+ function listMemoryLogs(agentName) {
141
+ const dir = path.join(agentDir(agentName), 'memory');
142
+ if (!fs.existsSync(dir)) return [];
143
+ return fs.readdirSync(dir)
144
+ .filter(f => f.endsWith('.md'))
145
+ .sort()
146
+ .reverse()
147
+ .map(f => f.replace('.md', ''));
148
+ }
149
+
150
+ module.exports = {
151
+ initWorkspace,
152
+ readFile,
153
+ writeFile,
154
+ appendToDaily,
155
+ getDailyLogs,
156
+ deleteFile,
157
+ renameWorkspace,
158
+ deleteWorkspace,
159
+ listFiles,
160
+ listMemoryLogs,
161
+ sanitizeName
162
+ };