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,172 @@
1
+ /**
2
+ * 🦑 Squidclaw Config System
3
+ * Handles loading, saving, and validating configuration
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { homedir } from 'os';
9
+
10
+ const SQUIDCLAW_HOME = process.env.SQUIDCLAW_HOME || join(homedir(), '.squidclaw');
11
+ const CONFIG_FILE = join(SQUIDCLAW_HOME, 'config.json');
12
+
13
+ const DEFAULT_CONFIG = {
14
+ engine: {
15
+ port: 9500,
16
+ bind: '127.0.0.1',
17
+ mode: 'standalone', // standalone | platform
18
+ },
19
+ ai: {
20
+ defaultProvider: null,
21
+ defaultModel: null,
22
+ fallbackChain: [],
23
+ providers: {
24
+ anthropic: { key: null, models: ['claude-opus-4', 'claude-sonnet-4', 'claude-haiku-3.5'] },
25
+ openai: { key: null, models: ['gpt-4o', 'gpt-4o-mini', 'o3', 'o4-mini'] },
26
+ google: { key: null, models: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash'] },
27
+ groq: { key: null, models: ['llama-4-scout', 'llama-4-maverick', 'llama-3.3-70b'] },
28
+ together: { key: null, models: ['llama-4-scout', 'llama-4-maverick'] },
29
+ cerebras: { key: null, models: ['llama-3.3-70b'] },
30
+ openrouter: { key: null, models: [] },
31
+ mistral: { key: null, models: ['mistral-large', 'mistral-medium', 'mistral-small'] },
32
+ ollama: { key: 'local', baseUrl: 'http://localhost:11434', models: [] },
33
+ lmstudio: { key: 'local', baseUrl: 'http://localhost:1234', models: [] },
34
+ },
35
+ },
36
+ channels: {
37
+ whatsapp: { enabled: false },
38
+ telegram: { enabled: false },
39
+ },
40
+ storage: {
41
+ type: 'sqlite', // sqlite | supabase
42
+ sqlite: { path: join(SQUIDCLAW_HOME, 'squidclaw.db') },
43
+ supabase: { url: null, key: null },
44
+ },
45
+ platform: {
46
+ enabled: false,
47
+ platformKey: null, // default API key for all agents
48
+ allowUserKeys: true,
49
+ },
50
+ };
51
+
52
+ // Model name → full API model ID mapping
53
+ export const MODEL_MAP = {
54
+ // Anthropic
55
+ 'claude-opus-4': 'claude-opus-4-20250514',
56
+ 'claude-sonnet-4': 'claude-sonnet-4-20250514',
57
+ 'claude-haiku-3.5': 'claude-3-5-haiku-20241022',
58
+ // OpenAI
59
+ 'gpt-4o': 'gpt-4o',
60
+ 'gpt-4o-mini': 'gpt-4o-mini',
61
+ 'o3': 'o3',
62
+ 'o4-mini': 'o4-mini',
63
+ // Google
64
+ 'gemini-2.5-pro': 'gemini-2.5-pro-latest',
65
+ 'gemini-2.5-flash': 'gemini-2.5-flash-preview-05-20',
66
+ 'gemini-2.0-flash': 'gemini-2.0-flash',
67
+ // Groq
68
+ 'groq/llama-4-scout': 'meta-llama/llama-4-scout-17b-16e-instruct',
69
+ 'groq/llama-4-maverick': 'meta-llama/llama-4-maverick-17b-128e-instruct',
70
+ 'groq/llama-3.3-70b': 'llama-3.3-70b-versatile',
71
+ // Together
72
+ 'together/llama-4-scout': 'meta-llama/Llama-4-Scout-17B-16E-Instruct',
73
+ 'together/llama-4-maverick': 'meta-llama/Llama-4-Maverick-17B-128E-Instruct',
74
+ // Cerebras
75
+ 'cerebras/llama-3.3-70b': 'llama-3.3-70b',
76
+ // Mistral
77
+ 'mistral-large': 'mistral-large-latest',
78
+ 'mistral-medium': 'mistral-medium-latest',
79
+ 'mistral-small': 'mistral-small-latest',
80
+ };
81
+
82
+ // Pricing per 1M tokens (input/output)
83
+ export const MODEL_PRICING = {
84
+ 'claude-opus-4': { input: 15, output: 75 },
85
+ 'claude-sonnet-4': { input: 3, output: 15 },
86
+ 'claude-haiku-3.5': { input: 0.8, output: 4 },
87
+ 'gpt-4o': { input: 2.5, output: 10 },
88
+ 'gpt-4o-mini': { input: 0.15, output: 0.6 },
89
+ 'o3': { input: 10, output: 40 },
90
+ 'o4-mini': { input: 1.1, output: 4.4 },
91
+ 'gemini-2.5-pro': { input: 1.25, output: 10 },
92
+ 'gemini-2.5-flash': { input: 0.15, output: 0.6 },
93
+ 'gemini-2.0-flash': { input: 0.1, output: 0.4 },
94
+ 'groq/llama-4-scout': { input: 0, output: 0 },
95
+ 'groq/llama-4-maverick': { input: 0, output: 0 },
96
+ 'groq/llama-3.3-70b': { input: 0, output: 0 },
97
+ 'together/llama-4-scout': { input: 0, output: 0 },
98
+ 'cerebras/llama-3.3-70b': { input: 0, output: 0 },
99
+ };
100
+
101
+ export function getHome() {
102
+ return SQUIDCLAW_HOME;
103
+ }
104
+
105
+ export function ensureHome() {
106
+ const dirs = [
107
+ SQUIDCLAW_HOME,
108
+ join(SQUIDCLAW_HOME, 'agents'),
109
+ join(SQUIDCLAW_HOME, 'channels'),
110
+ join(SQUIDCLAW_HOME, 'channels', 'whatsapp'),
111
+ join(SQUIDCLAW_HOME, 'knowledge'),
112
+ join(SQUIDCLAW_HOME, 'logs'),
113
+ ];
114
+ for (const dir of dirs) {
115
+ mkdirSync(dir, { recursive: true });
116
+ }
117
+ }
118
+
119
+ export function loadConfig() {
120
+ ensureHome();
121
+ if (!existsSync(CONFIG_FILE)) {
122
+ return { ...DEFAULT_CONFIG };
123
+ }
124
+ try {
125
+ const raw = readFileSync(CONFIG_FILE, 'utf8');
126
+ const loaded = JSON.parse(raw);
127
+ return deepMerge(DEFAULT_CONFIG, loaded);
128
+ } catch {
129
+ return { ...DEFAULT_CONFIG };
130
+ }
131
+ }
132
+
133
+ export function saveConfig(config) {
134
+ ensureHome();
135
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
136
+ }
137
+
138
+ export function getConfig(key) {
139
+ const config = loadConfig();
140
+ if (!key) return config;
141
+ return key.split('.').reduce((obj, k) => obj?.[k], config);
142
+ }
143
+
144
+ export function setConfigValue(key, value) {
145
+ const config = loadConfig();
146
+ const keys = key.split('.');
147
+ let obj = config;
148
+ for (let i = 0; i < keys.length - 1; i++) {
149
+ if (!obj[keys[i]]) obj[keys[i]] = {};
150
+ obj = obj[keys[i]];
151
+ }
152
+ // Try to parse JSON values
153
+ try {
154
+ obj[keys[keys.length - 1]] = JSON.parse(value);
155
+ } catch {
156
+ obj[keys[keys.length - 1]] = value;
157
+ }
158
+ saveConfig(config);
159
+ return config;
160
+ }
161
+
162
+ function deepMerge(target, source) {
163
+ const result = { ...target };
164
+ for (const key of Object.keys(source)) {
165
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
166
+ result[key] = deepMerge(result[key] || {}, source[key]);
167
+ } else {
168
+ result[key] = source[key];
169
+ }
170
+ }
171
+ return result;
172
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 🦑 Squidclaw Logger
3
+ */
4
+
5
+ import { createWriteStream, mkdirSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { getHome } from './config.js';
8
+
9
+ const LEVELS = { error: 0, warn: 1, info: 2, debug: 3, trace: 4 };
10
+ let currentLevel = LEVELS.info;
11
+ let logStream = null;
12
+
13
+ export function initLogger(level = 'info') {
14
+ currentLevel = LEVELS[level] ?? LEVELS.info;
15
+ const logDir = join(getHome(), 'logs');
16
+ mkdirSync(logDir, { recursive: true });
17
+ const logFile = join(logDir, 'squidclaw.log');
18
+ logStream = createWriteStream(logFile, { flags: 'a' });
19
+ }
20
+
21
+ function log(level, component, message, data) {
22
+ if (LEVELS[level] > currentLevel) return;
23
+ const entry = {
24
+ ts: new Date().toISOString(),
25
+ level,
26
+ component,
27
+ msg: message,
28
+ ...(data ? { data } : {}),
29
+ };
30
+ const line = JSON.stringify(entry);
31
+ if (logStream) logStream.write(line + '\n');
32
+ if (level === 'error') console.error(`[${component}] ${message}`);
33
+ else if (level === 'warn') console.warn(`[${component}] ${message}`);
34
+ else if (currentLevel >= LEVELS.debug) console.log(`[${component}] ${message}`);
35
+ }
36
+
37
+ export const logger = {
38
+ error: (component, msg, data) => log('error', component, msg, data),
39
+ warn: (component, msg, data) => log('warn', component, msg, data),
40
+ info: (component, msg, data) => log('info', component, msg, data),
41
+ debug: (component, msg, data) => log('debug', component, msg, data),
42
+ trace: (component, msg, data) => log('trace', component, msg, data),
43
+ };
package/lib/engine.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 🦑 Squidclaw Engine
3
+ * Main entry point — starts all subsystems
4
+ */
5
+
6
+ import { loadConfig, getHome, ensureHome } from './core/config.js';
7
+ import { initLogger, logger } from './core/logger.js';
8
+ import { AgentManager } from './core/agent-manager.js';
9
+ import { AIGateway } from './ai/gateway.js';
10
+ import { SQLiteStorage } from './storage/sqlite.js';
11
+ import { WhatsAppManager } from './channels/whatsapp/manager.js';
12
+ import { ChannelHub } from './channels/hub.js';
13
+ import { HeartbeatSystem } from './features/heartbeat.js';
14
+ import { createAPIServer } from './api/server.js';
15
+ import { addMediaSupport } from './channels/hub-media.js';
16
+
17
+ export class SquidclawEngine {
18
+ constructor(options = {}) {
19
+ this.config = loadConfig();
20
+ this.home = getHome();
21
+ this.port = options.port || this.config.engine?.port || 9500;
22
+ this.storage = null;
23
+ this.aiGateway = null;
24
+ this.agentManager = null;
25
+ this.whatsappManager = null;
26
+ this.channelHub = null;
27
+ this.heartbeat = null;
28
+ this.server = null;
29
+ }
30
+
31
+ async start() {
32
+ ensureHome();
33
+ initLogger(this.config.engine?.logLevel || 'info');
34
+
35
+ console.log(`
36
+ 🦑 Squidclaw Engine v0.1.0
37
+ ──────────────────────────`);
38
+
39
+ // 1. Storage
40
+ const dbPath = this.config.storage?.sqlite?.path || `${this.home}/squidclaw.db`;
41
+ this.storage = new SQLiteStorage(dbPath);
42
+ console.log(` 💾 Storage: SQLite (${dbPath})`);
43
+
44
+ // 2. AI Gateway
45
+ this.aiGateway = new AIGateway(this.config);
46
+ const providers = Object.entries(this.config.ai?.providers || {})
47
+ .filter(([_, v]) => v.key)
48
+ .map(([k]) => k);
49
+ console.log(` 🧠 AI: ${providers.join(', ') || 'none configured'}`);
50
+ if (this.config.ai?.defaultModel) {
51
+ console.log(` 🎯 Default model: ${this.config.ai.defaultModel}`);
52
+ }
53
+
54
+ // 3. Agent Manager
55
+ this.agentManager = new AgentManager(this.storage, this.aiGateway);
56
+ await this.agentManager.loadAll();
57
+ const agents = this.agentManager.getAll();
58
+ console.log(` 👥 Agents: ${agents.length} loaded`);
59
+
60
+ // 4. WhatsApp Manager
61
+ this.whatsappManager = new WhatsAppManager(this.config, this.agentManager, this.home);
62
+
63
+ // Start WhatsApp sessions for all agents that have them configured
64
+ for (const agent of agents) {
65
+ if (agent.status === 'active' && agent.whatsappNumber) {
66
+ try {
67
+ await this.whatsappManager.startSession(agent.id);
68
+ } catch (err) {
69
+ logger.warn('engine', `Failed to start WhatsApp for "${agent.name}": ${err.message}`);
70
+ }
71
+ }
72
+ }
73
+ const waStatuses = this.whatsappManager.getStatuses();
74
+ const connected = Object.values(waStatuses).filter(s => s.connected).length;
75
+ console.log(` 📱 WhatsApp: ${connected}/${agents.length} connected`);
76
+
77
+ // 5. Channel Hub
78
+ this.channelHub = new ChannelHub(this.agentManager, this.whatsappManager, this.storage);
79
+ addMediaSupport(this.channelHub, this.config, this.home);
80
+
81
+ // 6. Heartbeat
82
+ this.heartbeat = new HeartbeatSystem(this.agentManager, this.whatsappManager, this.storage);
83
+ this.heartbeat.start();
84
+ console.log(` 💓 Heartbeat: active`);
85
+
86
+ // 7. API Server
87
+ const app = createAPIServer(this);
88
+ this.server = app.listen(this.port, this.config.engine?.bind || '127.0.0.1', () => {
89
+ console.log(` 🌐 API: http://${this.config.engine?.bind || '127.0.0.1'}:${this.port}`);
90
+ console.log(` ──────────────────────────`);
91
+ console.log(` ✅ Engine running!\n`);
92
+ });
93
+
94
+ // Graceful shutdown
95
+ process.on('SIGINT', () => this.stop());
96
+ process.on('SIGTERM', () => this.stop());
97
+ }
98
+
99
+ async stop() {
100
+ console.log('\n 🦑 Shutting down...');
101
+ this.heartbeat?.stop();
102
+ await this.whatsappManager?.stopAll();
103
+ this.storage?.close();
104
+ this.server?.close();
105
+ console.log(' 👋 Bye!\n');
106
+ process.exit(0);
107
+ }
108
+ }
109
+
110
+ // Auto-start if run directly
111
+ if (process.argv[1]?.endsWith('engine.js')) {
112
+ const engine = new SquidclawEngine();
113
+ engine.start().catch(err => {
114
+ console.error('Fatal:', err.message);
115
+ process.exit(1);
116
+ });
117
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 🦑 Heartbeat System
3
+ * Periodic agent tasks — proactive messages, follow-ups, scheduled sends
4
+ */
5
+
6
+ import { Cron } from 'croner';
7
+ import { logger } from '../core/logger.js';
8
+
9
+ export class HeartbeatSystem {
10
+ constructor(agentManager, whatsappManager, storage) {
11
+ this.agentManager = agentManager;
12
+ this.whatsappManager = whatsappManager;
13
+ this.storage = storage;
14
+ this.jobs = [];
15
+ }
16
+
17
+ start() {
18
+ // Check scheduled messages every minute
19
+ this.jobs.push(new Cron('* * * * *', async () => {
20
+ await this._processScheduledMessages();
21
+ }));
22
+
23
+ // Health check all agents every 5 minutes
24
+ this.jobs.push(new Cron('*/5 * * * *', async () => {
25
+ await this._healthCheck();
26
+ }));
27
+
28
+ logger.info('heartbeat', 'Heartbeat system started');
29
+ }
30
+
31
+ stop() {
32
+ for (const job of this.jobs) {
33
+ job.stop();
34
+ }
35
+ this.jobs = [];
36
+ }
37
+
38
+ async _processScheduledMessages() {
39
+ try {
40
+ const due = await this.storage.getDueMessages();
41
+ for (const msg of due) {
42
+ try {
43
+ await this.whatsappManager.sendMessage(msg.agent_id, msg.contact_id, msg.message);
44
+ await this.storage.markMessageSent(msg.id);
45
+ logger.info('heartbeat', `Sent scheduled message ${msg.id} to ${msg.contact_id}`);
46
+ } catch (err) {
47
+ logger.error('heartbeat', `Failed to send scheduled message ${msg.id}: ${err.message}`);
48
+ }
49
+ }
50
+ } catch (err) {
51
+ logger.error('heartbeat', `Scheduled message check failed: ${err.message}`);
52
+ }
53
+ }
54
+
55
+ async _healthCheck() {
56
+ const statuses = this.whatsappManager.getStatuses();
57
+ const agents = this.agentManager.getAll();
58
+
59
+ for (const agent of agents) {
60
+ const waStatus = statuses[agent.id];
61
+ if (agent.status === 'active' && agent.whatsappNumber && (!waStatus || !waStatus.connected)) {
62
+ logger.warn('heartbeat', `Agent "${agent.name}" WhatsApp disconnected — attempting reconnect`);
63
+ try {
64
+ await this.whatsappManager.startSession(agent.id);
65
+ } catch (err) {
66
+ logger.error('heartbeat', `Reconnect failed for "${agent.name}": ${err.message}`);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 🦑 Storage Interface
3
+ * Abstract interface for all storage operations
4
+ */
5
+
6
+ export class StorageInterface {
7
+ // ── Agents ──
8
+ async createAgent(agent) { throw new Error('Not implemented'); }
9
+ async getAgent(id) { throw new Error('Not implemented'); }
10
+ async listAgents() { throw new Error('Not implemented'); }
11
+ async updateAgent(id, updates) { throw new Error('Not implemented'); }
12
+ async deleteAgent(id) { throw new Error('Not implemented'); }
13
+
14
+ // ── Conversations ──
15
+ async saveMessage(agentId, contactId, role, content, metadata) { throw new Error('Not implemented'); }
16
+ async getConversation(agentId, contactId, limit) { throw new Error('Not implemented'); }
17
+ async searchConversations(query, agentId) { throw new Error('Not implemented'); }
18
+
19
+ // ── Memory ──
20
+ async saveMemory(agentId, key, value, type) { throw new Error('Not implemented'); }
21
+ async getMemories(agentId, type) { throw new Error('Not implemented'); }
22
+ async deleteMemory(agentId, key) { throw new Error('Not implemented'); }
23
+ async clearMemories(agentId) { throw new Error('Not implemented'); }
24
+
25
+ // ── Knowledge / RAG ──
26
+ async saveDocument(agentId, doc) { throw new Error('Not implemented'); }
27
+ async saveChunks(docId, chunks) { throw new Error('Not implemented'); }
28
+ async searchKnowledge(agentId, embedding, limit) { throw new Error('Not implemented'); }
29
+ async listDocuments(agentId) { throw new Error('Not implemented'); }
30
+ async deleteDocument(docId) { throw new Error('Not implemented'); }
31
+
32
+ // ── Usage ──
33
+ async trackUsage(agentId, model, inputTokens, outputTokens, costUsd) { throw new Error('Not implemented'); }
34
+ async getUsage(agentId, period) { throw new Error('Not implemented'); }
35
+ async getUsageAll(period) { throw new Error('Not implemented'); }
36
+
37
+ // ── Handoffs ──
38
+ async createHandoff(agentId, contactId, reason) { throw new Error('Not implemented'); }
39
+ async getActiveHandoffs() { throw new Error('Not implemented'); }
40
+ async resolveHandoff(id) { throw new Error('Not implemented'); }
41
+
42
+ // ── Webhooks ──
43
+ async addWebhook(url, events) { throw new Error('Not implemented'); }
44
+ async getWebhooks() { throw new Error('Not implemented'); }
45
+ async removeWebhook(id) { throw new Error('Not implemented'); }
46
+
47
+ // ── Contacts ──
48
+ async saveContact(agentId, contactId, name, metadata) { throw new Error('Not implemented'); }
49
+ async getContact(agentId, contactId) { throw new Error('Not implemented'); }
50
+ async listContacts(agentId) { throw new Error('Not implemented'); }
51
+
52
+ // ── Scheduled Messages ──
53
+ async scheduleMessage(agentId, contactId, message, sendAt) { throw new Error('Not implemented'); }
54
+ async getDueMessages() { throw new Error('Not implemented'); }
55
+ async markMessageSent(id) { throw new Error('Not implemented'); }
56
+ }