squidclaw 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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/squidclaw.js +512 -0
  4. package/lib/ai/gateway.js +283 -0
  5. package/lib/ai/prompt-builder.js +149 -0
  6. package/lib/api/server.js +235 -0
  7. package/lib/behavior/engine.js +187 -0
  8. package/lib/channels/hub-media.js +128 -0
  9. package/lib/channels/hub.js +89 -0
  10. package/lib/channels/whatsapp/manager.js +319 -0
  11. package/lib/channels/whatsapp/media.js +228 -0
  12. package/lib/cli/agent-cmd.js +182 -0
  13. package/lib/cli/brain-cmd.js +49 -0
  14. package/lib/cli/broadcast-cmd.js +28 -0
  15. package/lib/cli/channels-cmd.js +157 -0
  16. package/lib/cli/config-cmd.js +26 -0
  17. package/lib/cli/conversations-cmd.js +27 -0
  18. package/lib/cli/engine-cmd.js +115 -0
  19. package/lib/cli/handoff-cmd.js +26 -0
  20. package/lib/cli/hours-cmd.js +38 -0
  21. package/lib/cli/key-cmd.js +62 -0
  22. package/lib/cli/knowledge-cmd.js +59 -0
  23. package/lib/cli/memory-cmd.js +50 -0
  24. package/lib/cli/platform-cmd.js +51 -0
  25. package/lib/cli/setup.js +226 -0
  26. package/lib/cli/stats-cmd.js +66 -0
  27. package/lib/cli/tui.js +308 -0
  28. package/lib/cli/update-cmd.js +25 -0
  29. package/lib/cli/webhook-cmd.js +40 -0
  30. package/lib/core/agent-manager.js +83 -0
  31. package/lib/core/agent.js +162 -0
  32. package/lib/core/config.js +172 -0
  33. package/lib/core/logger.js +43 -0
  34. package/lib/engine.js +117 -0
  35. package/lib/features/heartbeat.js +71 -0
  36. package/lib/storage/interface.js +56 -0
  37. package/lib/storage/sqlite.js +409 -0
  38. package/package.json +48 -0
  39. package/templates/BEHAVIOR.md +42 -0
  40. package/templates/IDENTITY.md +7 -0
  41. package/templates/RULES.md +9 -0
  42. package/templates/SOUL.md +19 -0
@@ -0,0 +1,409 @@
1
+ /**
2
+ * 🦑 SQLite Storage Driver
3
+ * Zero-config local storage with vector search support
4
+ */
5
+
6
+ import Database from 'better-sqlite3';
7
+ import { StorageInterface } from './interface.js';
8
+ import { logger } from '../core/logger.js';
9
+ import crypto from 'crypto';
10
+
11
+ export class SQLiteStorage extends StorageInterface {
12
+ constructor(dbPath) {
13
+ super();
14
+ this.db = new Database(dbPath);
15
+ this.db.pragma('journal_mode = WAL');
16
+ this.db.pragma('foreign_keys = ON');
17
+ this._migrate();
18
+ }
19
+
20
+ _migrate() {
21
+ this.db.exec(`
22
+ CREATE TABLE IF NOT EXISTS agents (
23
+ id TEXT PRIMARY KEY,
24
+ name TEXT NOT NULL,
25
+ soul TEXT,
26
+ language TEXT DEFAULT 'en',
27
+ tone INTEGER DEFAULT 50,
28
+ model TEXT,
29
+ fallback_chain TEXT DEFAULT '[]',
30
+ behavior TEXT DEFAULT '{}',
31
+ business_hours TEXT,
32
+ timezone TEXT DEFAULT 'UTC',
33
+ whatsapp_number TEXT,
34
+ telegram_bot_token TEXT,
35
+ status TEXT DEFAULT 'active',
36
+ created_at TEXT DEFAULT (datetime('now')),
37
+ updated_at TEXT DEFAULT (datetime('now'))
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS conversations (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ agent_id TEXT NOT NULL,
43
+ contact_id TEXT NOT NULL,
44
+ role TEXT NOT NULL,
45
+ content TEXT NOT NULL,
46
+ media_type TEXT,
47
+ media_url TEXT,
48
+ metadata TEXT DEFAULT '{}',
49
+ created_at TEXT DEFAULT (datetime('now')),
50
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
51
+ );
52
+ CREATE INDEX IF NOT EXISTS idx_conv_agent_contact ON conversations(agent_id, contact_id, created_at);
53
+
54
+ CREATE TABLE IF NOT EXISTS memories (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ agent_id TEXT NOT NULL,
57
+ key TEXT NOT NULL,
58
+ value TEXT NOT NULL,
59
+ type TEXT DEFAULT 'fact',
60
+ importance INTEGER DEFAULT 5,
61
+ created_at TEXT DEFAULT (datetime('now')),
62
+ updated_at TEXT DEFAULT (datetime('now')),
63
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
64
+ UNIQUE(agent_id, key)
65
+ );
66
+
67
+ CREATE TABLE IF NOT EXISTS documents (
68
+ id TEXT PRIMARY KEY,
69
+ agent_id TEXT NOT NULL,
70
+ filename TEXT NOT NULL,
71
+ file_type TEXT,
72
+ file_size INTEGER,
73
+ chunk_count INTEGER DEFAULT 0,
74
+ status TEXT DEFAULT 'processing',
75
+ created_at TEXT DEFAULT (datetime('now')),
76
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS chunks (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ doc_id TEXT NOT NULL,
82
+ agent_id TEXT NOT NULL,
83
+ content TEXT NOT NULL,
84
+ embedding BLOB,
85
+ chunk_index INTEGER,
86
+ FOREIGN KEY (doc_id) REFERENCES documents(id) ON DELETE CASCADE
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS usage (
90
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
91
+ agent_id TEXT NOT NULL,
92
+ model TEXT,
93
+ input_tokens INTEGER DEFAULT 0,
94
+ output_tokens INTEGER DEFAULT 0,
95
+ cost_usd REAL DEFAULT 0,
96
+ created_at TEXT DEFAULT (datetime('now')),
97
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
98
+ );
99
+ CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage(agent_id, created_at);
100
+
101
+ CREATE TABLE IF NOT EXISTS handoffs (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ agent_id TEXT NOT NULL,
104
+ contact_id TEXT NOT NULL,
105
+ reason TEXT,
106
+ status TEXT DEFAULT 'pending',
107
+ created_at TEXT DEFAULT (datetime('now')),
108
+ resolved_at TEXT,
109
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
110
+ );
111
+
112
+ CREATE TABLE IF NOT EXISTS webhooks (
113
+ id TEXT PRIMARY KEY,
114
+ url TEXT NOT NULL,
115
+ events TEXT DEFAULT '["*"]',
116
+ active INTEGER DEFAULT 1,
117
+ created_at TEXT DEFAULT (datetime('now'))
118
+ );
119
+
120
+ CREATE TABLE IF NOT EXISTS contacts (
121
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
122
+ agent_id TEXT NOT NULL,
123
+ contact_id TEXT NOT NULL,
124
+ name TEXT,
125
+ metadata TEXT DEFAULT '{}',
126
+ first_seen TEXT DEFAULT (datetime('now')),
127
+ last_seen TEXT DEFAULT (datetime('now')),
128
+ message_count INTEGER DEFAULT 0,
129
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE,
130
+ UNIQUE(agent_id, contact_id)
131
+ );
132
+
133
+ CREATE TABLE IF NOT EXISTS scheduled_messages (
134
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
135
+ agent_id TEXT NOT NULL,
136
+ contact_id TEXT NOT NULL,
137
+ message TEXT NOT NULL,
138
+ send_at TEXT NOT NULL,
139
+ sent INTEGER DEFAULT 0,
140
+ created_at TEXT DEFAULT (datetime('now')),
141
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
142
+ );
143
+ `);
144
+ logger.info('storage', 'SQLite database initialized');
145
+ }
146
+
147
+ // ── Agents ──
148
+ async createAgent(agent) {
149
+ const id = agent.id || crypto.randomUUID();
150
+ this.db.prepare(`
151
+ INSERT INTO agents (id, name, soul, language, tone, model, behavior, business_hours, timezone)
152
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
153
+ `).run(id, agent.name, agent.soul || '', agent.language || 'en', agent.tone ?? 50,
154
+ agent.model || null, JSON.stringify(agent.behavior || {}),
155
+ agent.businessHours || null, agent.timezone || 'UTC');
156
+ return { id, ...agent };
157
+ }
158
+
159
+ async getAgent(id) {
160
+ const row = this.db.prepare('SELECT * FROM agents WHERE id = ?').get(id);
161
+ if (!row) return null;
162
+ row.behavior = JSON.parse(row.behavior || '{}');
163
+ row.fallback_chain = JSON.parse(row.fallback_chain || '[]');
164
+ return row;
165
+ }
166
+
167
+ async listAgents() {
168
+ const rows = this.db.prepare('SELECT * FROM agents ORDER BY created_at DESC').all();
169
+ return rows.map(r => ({ ...r, behavior: JSON.parse(r.behavior || '{}'), fallback_chain: JSON.parse(r.fallback_chain || '[]') }));
170
+ }
171
+
172
+ async updateAgent(id, updates) {
173
+ const sets = [];
174
+ const vals = [];
175
+ for (const [k, v] of Object.entries(updates)) {
176
+ const col = k.replace(/([A-Z])/g, '_$1').toLowerCase();
177
+ sets.push(`${col} = ?`);
178
+ vals.push(typeof v === 'object' ? JSON.stringify(v) : v);
179
+ }
180
+ sets.push("updated_at = datetime('now')");
181
+ vals.push(id);
182
+ this.db.prepare(`UPDATE agents SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
183
+ }
184
+
185
+ async deleteAgent(id) {
186
+ this.db.prepare('DELETE FROM agents WHERE id = ?').run(id);
187
+ }
188
+
189
+ // ── Conversations ──
190
+ async saveMessage(agentId, contactId, role, content, metadata = {}) {
191
+ this.db.prepare(`
192
+ INSERT INTO conversations (agent_id, contact_id, role, content, media_type, media_url, metadata)
193
+ VALUES (?, ?, ?, ?, ?, ?, ?)
194
+ `).run(agentId, contactId, role, content, metadata.mediaType || null,
195
+ metadata.mediaUrl || null, JSON.stringify(metadata));
196
+
197
+ // Update contact
198
+ this.db.prepare(`
199
+ INSERT INTO contacts (agent_id, contact_id, name, message_count, last_seen)
200
+ VALUES (?, ?, ?, 1, datetime('now'))
201
+ ON CONFLICT(agent_id, contact_id) DO UPDATE SET
202
+ message_count = message_count + 1,
203
+ last_seen = datetime('now'),
204
+ name = COALESCE(excluded.name, contacts.name)
205
+ `).run(agentId, contactId, metadata.pushName || null);
206
+ }
207
+
208
+ async getConversation(agentId, contactId, limit = 50) {
209
+ return this.db.prepare(`
210
+ SELECT role, content, media_type, created_at FROM conversations
211
+ WHERE agent_id = ? AND contact_id = ?
212
+ ORDER BY created_at DESC LIMIT ?
213
+ `).all(agentId, contactId, limit).reverse();
214
+ }
215
+
216
+ async searchConversations(query, agentId) {
217
+ const sql = agentId
218
+ ? `SELECT * FROM conversations WHERE agent_id = ? AND content LIKE ? ORDER BY created_at DESC LIMIT 50`
219
+ : `SELECT * FROM conversations WHERE content LIKE ? ORDER BY created_at DESC LIMIT 50`;
220
+ const params = agentId ? [agentId, `%${query}%`] : [`%${query}%`];
221
+ return this.db.prepare(sql).all(...params);
222
+ }
223
+
224
+ // ── Memory ──
225
+ async saveMemory(agentId, key, value, type = 'fact') {
226
+ this.db.prepare(`
227
+ INSERT INTO memories (agent_id, key, value, type)
228
+ VALUES (?, ?, ?, ?)
229
+ ON CONFLICT(agent_id, key) DO UPDATE SET value = ?, updated_at = datetime('now')
230
+ `).run(agentId, key, value, type, value);
231
+ }
232
+
233
+ async getMemories(agentId, type) {
234
+ const sql = type
235
+ ? 'SELECT * FROM memories WHERE agent_id = ? AND type = ? ORDER BY importance DESC, updated_at DESC'
236
+ : 'SELECT * FROM memories WHERE agent_id = ? ORDER BY importance DESC, updated_at DESC';
237
+ return type ? this.db.prepare(sql).all(agentId, type) : this.db.prepare(sql).all(agentId);
238
+ }
239
+
240
+ async deleteMemory(agentId, key) {
241
+ this.db.prepare('DELETE FROM memories WHERE agent_id = ? AND key = ?').run(agentId, key);
242
+ }
243
+
244
+ async clearMemories(agentId) {
245
+ this.db.prepare('DELETE FROM memories WHERE agent_id = ?').run(agentId);
246
+ }
247
+
248
+ // ── Knowledge ──
249
+ async saveDocument(agentId, doc) {
250
+ const id = doc.id || crypto.randomUUID();
251
+ this.db.prepare(`
252
+ INSERT INTO documents (id, agent_id, filename, file_type, file_size)
253
+ VALUES (?, ?, ?, ?, ?)
254
+ `).run(id, agentId, doc.filename, doc.fileType, doc.fileSize || 0);
255
+ return id;
256
+ }
257
+
258
+ async saveChunks(docId, agentId, chunks) {
259
+ const insert = this.db.prepare(`
260
+ INSERT INTO chunks (doc_id, agent_id, content, embedding, chunk_index)
261
+ VALUES (?, ?, ?, ?, ?)
262
+ `);
263
+ const tx = this.db.transaction((items) => {
264
+ for (let i = 0; i < items.length; i++) {
265
+ insert.run(docId, agentId, items[i].content, items[i].embedding || null, i);
266
+ }
267
+ });
268
+ tx(chunks);
269
+ this.db.prepare('UPDATE documents SET chunk_count = ?, status = ? WHERE id = ?')
270
+ .run(chunks.length, 'processed', docId);
271
+ }
272
+
273
+ async searchKnowledge(agentId, queryEmbedding, limit = 5) {
274
+ // Simple text search fallback when no embeddings
275
+ // TODO: implement cosine similarity with sqlite-vec
276
+ return this.db.prepare(`
277
+ SELECT content FROM chunks WHERE agent_id = ? LIMIT ?
278
+ `).all(agentId, limit);
279
+ }
280
+
281
+ async listDocuments(agentId) {
282
+ return this.db.prepare('SELECT * FROM documents WHERE agent_id = ? ORDER BY created_at DESC').all(agentId);
283
+ }
284
+
285
+ async deleteDocument(docId) {
286
+ this.db.prepare('DELETE FROM documents WHERE id = ?').run(docId);
287
+ }
288
+
289
+ // ── Usage ──
290
+ async trackUsage(agentId, model, inputTokens, outputTokens, costUsd) {
291
+ this.db.prepare(`
292
+ INSERT INTO usage (agent_id, model, input_tokens, output_tokens, cost_usd)
293
+ VALUES (?, ?, ?, ?, ?)
294
+ `).run(agentId, model, inputTokens, outputTokens, costUsd);
295
+ }
296
+
297
+ async getUsage(agentId, period = '30d') {
298
+ const since = this._periodToDate(period);
299
+ return this.db.prepare(`
300
+ SELECT
301
+ COUNT(*) as messages,
302
+ SUM(input_tokens) as input_tokens,
303
+ SUM(output_tokens) as output_tokens,
304
+ SUM(cost_usd) as cost_usd
305
+ FROM usage WHERE agent_id = ? AND created_at >= ?
306
+ `).get(agentId, since);
307
+ }
308
+
309
+ async getUsageAll(period = '30d') {
310
+ const since = this._periodToDate(period);
311
+ return this.db.prepare(`
312
+ SELECT
313
+ agent_id,
314
+ COUNT(*) as messages,
315
+ SUM(input_tokens) as input_tokens,
316
+ SUM(output_tokens) as output_tokens,
317
+ SUM(cost_usd) as cost_usd
318
+ FROM usage WHERE created_at >= ?
319
+ GROUP BY agent_id
320
+ `).all(since);
321
+ }
322
+
323
+ // ── Handoffs ──
324
+ async createHandoff(agentId, contactId, reason) {
325
+ this.db.prepare(`
326
+ INSERT INTO handoffs (agent_id, contact_id, reason) VALUES (?, ?, ?)
327
+ `).run(agentId, contactId, reason);
328
+ }
329
+
330
+ async getActiveHandoffs() {
331
+ return this.db.prepare("SELECT * FROM handoffs WHERE status = 'pending' ORDER BY created_at DESC").all();
332
+ }
333
+
334
+ async resolveHandoff(id) {
335
+ this.db.prepare("UPDATE handoffs SET status = 'resolved', resolved_at = datetime('now') WHERE id = ?").run(id);
336
+ }
337
+
338
+ // ── Webhooks ──
339
+ async addWebhook(url, events = ['*']) {
340
+ const id = crypto.randomUUID().slice(0, 8);
341
+ this.db.prepare('INSERT INTO webhooks (id, url, events) VALUES (?, ?, ?)').run(id, url, JSON.stringify(events));
342
+ return id;
343
+ }
344
+
345
+ async getWebhooks() {
346
+ return this.db.prepare('SELECT * FROM webhooks WHERE active = 1').all()
347
+ .map(w => ({ ...w, events: JSON.parse(w.events) }));
348
+ }
349
+
350
+ async removeWebhook(id) {
351
+ this.db.prepare('DELETE FROM webhooks WHERE id = ?').run(id);
352
+ }
353
+
354
+ // ── Contacts ──
355
+ async saveContact(agentId, contactId, name, metadata = {}) {
356
+ this.db.prepare(`
357
+ INSERT INTO contacts (agent_id, contact_id, name, metadata)
358
+ VALUES (?, ?, ?, ?)
359
+ ON CONFLICT(agent_id, contact_id) DO UPDATE SET
360
+ name = COALESCE(?, contacts.name),
361
+ metadata = ?,
362
+ last_seen = datetime('now')
363
+ `).run(agentId, contactId, name, JSON.stringify(metadata), name, JSON.stringify(metadata));
364
+ }
365
+
366
+ async getContact(agentId, contactId) {
367
+ return this.db.prepare('SELECT * FROM contacts WHERE agent_id = ? AND contact_id = ?').get(agentId, contactId);
368
+ }
369
+
370
+ async listContacts(agentId) {
371
+ return this.db.prepare('SELECT * FROM contacts WHERE agent_id = ? ORDER BY last_seen DESC').all(agentId);
372
+ }
373
+
374
+ // ── Scheduled Messages ──
375
+ async scheduleMessage(agentId, contactId, message, sendAt) {
376
+ this.db.prepare(`
377
+ INSERT INTO scheduled_messages (agent_id, contact_id, message, send_at)
378
+ VALUES (?, ?, ?, ?)
379
+ `).run(agentId, contactId, message, sendAt);
380
+ }
381
+
382
+ async getDueMessages() {
383
+ return this.db.prepare(`
384
+ SELECT * FROM scheduled_messages WHERE sent = 0 AND send_at <= datetime('now')
385
+ `).all();
386
+ }
387
+
388
+ async markMessageSent(id) {
389
+ this.db.prepare('UPDATE scheduled_messages SET sent = 1 WHERE id = ?').run(id);
390
+ }
391
+
392
+ // ── Helpers ──
393
+ _periodToDate(period) {
394
+ const now = new Date();
395
+ const match = period.match(/^(\d+)(d|w|m|y)$/);
396
+ if (!match) return new Date(0).toISOString();
397
+ const [, num, unit] = match;
398
+ const n = parseInt(num);
399
+ if (unit === 'd') now.setDate(now.getDate() - n);
400
+ else if (unit === 'w') now.setDate(now.getDate() - n * 7);
401
+ else if (unit === 'm') now.setMonth(now.getMonth() - n);
402
+ else if (unit === 'y') now.setFullYear(now.getFullYear() - n);
403
+ return now.toISOString();
404
+ }
405
+
406
+ close() {
407
+ this.db.close();
408
+ }
409
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "squidclaw",
3
+ "version": "0.1.0",
4
+ "description": "🦑 AI agent platform — human-like agents for WhatsApp, Telegram & more",
5
+ "main": "lib/engine.js",
6
+ "bin": {
7
+ "squidclaw": "./bin/squidclaw.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "start": "node lib/engine.js",
12
+ "test": "vitest",
13
+ "dev": "node --watch lib/engine.js"
14
+ },
15
+ "keywords": ["ai", "agent", "whatsapp", "chatbot", "claude", "openai", "squidclaw"],
16
+ "author": "Squidclaw",
17
+ "license": "MIT",
18
+ "homepage": "https://squidclaw.dev",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/squidclaw/squidclaw"
22
+ },
23
+ "engines": {
24
+ "node": ">=20.0.0"
25
+ },
26
+ "dependencies": {
27
+ "@whiskeysockets/baileys": "^7.0.0-rc.9",
28
+ "@hapi/boom": "^10.0.1",
29
+ "@clack/prompts": "^1.0.1",
30
+ "@mozilla/readability": "^0.6.0",
31
+ "better-sqlite3": "^11.0.0",
32
+ "chalk": "^5.6.2",
33
+ "commander": "^14.0.3",
34
+ "croner": "^10.0.1",
35
+ "dotenv": "^17.3.1",
36
+ "express": "^5.2.1",
37
+ "file-type": "^21.3.0",
38
+ "linkedom": "^0.18.12",
39
+ "node-edge-tts": "^1.2.10",
40
+ "pdfjs-dist": "^5.4.624",
41
+ "pino": "^10.3.1",
42
+ "qrcode-terminal": "^0.12.0",
43
+ "sharp": "^0.34.5",
44
+ "undici": "^7.22.0",
45
+ "yaml": "^2.8.2",
46
+ "zod": "^4.3.6"
47
+ }
48
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "splitMessages": true,
3
+ "maxChunkLength": 200,
4
+ "maxChunks": 4,
5
+ "delayBetweenChunks": "0.5-1.0",
6
+ "reactBeforeReply": true,
7
+ "reactOnlyEndings": true,
8
+ "favoriteEmojis": ["😍", "🔥", "❤️", "👍", "😂", "🙏"],
9
+ "autoDetectLanguage": true,
10
+ "defaultLanguage": "en",
11
+ "avoidPhrases": [
12
+ "Is there anything else I can help with?",
13
+ "I'd be happy to help!",
14
+ "Great question!",
15
+ "As an AI"
16
+ ],
17
+ "emotionResponse": {
18
+ "angry": "empathize_first, short_sentences, offer_escalation",
19
+ "happy": "match_energy, use_emojis",
20
+ "confused": "simplify, use_examples",
21
+ "urgent": "skip_pleasantries, be_direct"
22
+ },
23
+ "businessHours": null,
24
+ "timezone": "UTC",
25
+ "afterHoursMode": "24/7",
26
+ "voice": {
27
+ "enabled": false,
28
+ "provider": "edge-tts",
29
+ "name": "en-US-AriaNeural"
30
+ },
31
+ "handoff": {
32
+ "enabled": false,
33
+ "triggerPhrases": ["talk to a human", "speak to manager"],
34
+ "notify": []
35
+ },
36
+ "heartbeat": "30m",
37
+ "followUp": {
38
+ "enabled": false,
39
+ "delay": "24h",
40
+ "message": "Hey! Just checking in — did you need anything else? 😊"
41
+ }
42
+ }
@@ -0,0 +1,7 @@
1
+ # {agent_name}
2
+
3
+ - **Name:** {agent_name}
4
+ - **Language:** {language}
5
+ - **Tone:** {tone_description}
6
+ - **Emoji:** 🦑
7
+ - **Created:** {date}
@@ -0,0 +1,9 @@
1
+ # Rules (non-negotiable)
2
+
3
+ 1. Be helpful, be human, be honest
4
+ 2. Keep messages short — this is WhatsApp, not email
5
+ 3. If unsure, say so instead of making things up
6
+ 4. Never share customer data with other customers
7
+ 5. Match the language the person writes in
8
+ 6. One emoji per message max — don't overdo it
9
+ 7. If someone is angry, empathize first, solve second
@@ -0,0 +1,19 @@
1
+ # {agent_name}
2
+
3
+ ## Who I Am
4
+ I'm {agent_name}. {agent_purpose}
5
+
6
+ ## How I Speak
7
+ - Language: {language}
8
+ - Tone: {tone_description}
9
+ - Platform: WhatsApp — short messages, natural, human-like
10
+ - I use emojis naturally but don't overdo it
11
+
12
+ ## What I Do
13
+ {agent_purpose}
14
+
15
+ ## What I Never Do
16
+ - Say "As an AI" or "I'd be happy to help" or "Great question!"
17
+ - Send walls of text — I keep it short and conversational
18
+ - Make things up when I don't know — I say "let me check" instead
19
+ - Over-apologize — one "sorry" is enough