squidclaw 0.5.5 → 0.6.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.
@@ -107,7 +107,12 @@ export class TelegramManager {
107
107
  });
108
108
 
109
109
  bot.catch((err) => {
110
- logger.error('telegram', `Bot error for ${agentId}: ${err.message}`);
110
+ if (err.message?.includes('terminated by other')) {
111
+ logger.warn('telegram', 'Another bot instance running — retrying in 5s...');
112
+ setTimeout(() => { try { bot.start(); } catch {} }, 5000);
113
+ } else {
114
+ logger.error('telegram', `Bot error for ${agentId}: ${err.message}`);
115
+ }
111
116
  });
112
117
 
113
118
  return botInfo;
package/lib/cli/setup.js CHANGED
@@ -22,46 +22,59 @@ export async function setup() {
22
22
  const home = getHome();
23
23
  ensureHome();
24
24
 
25
- // ═══ Step 1: AI Provider ═══
26
- p.note('Step 1 of 4: AI Brain', '🧠');
25
+ // ═══ Step 1: AI Providers ═══
26
+ p.note('Step 1 of 5: AI Providers', '🧠');
27
+
28
+ config.ai = config.ai || { providers: {} };
29
+ config.ai.providers = config.ai.providers || {};
27
30
 
28
- const provider = await p.select({
29
- message: 'Choose your AI provider:',
30
- options: [
31
- { value: 'anthropic', label: '🟣 Anthropic (Claude)', hint: 'recommended' },
32
- { value: 'openai', label: '🟢 OpenAI (GPT-4o)' },
33
- { value: 'google', label: '🔵 Google (Gemini)', hint: 'free tier' },
34
- { value: 'groq', label: ' Groq (Llama)', hint: 'free, very fast' },
35
- { value: 'cerebras', label: '🧠 Cerebras (Llama)', hint: 'free, fastest' },
36
- { value: 'together', label: '🤝 Together AI' },
37
- { value: 'mistral', label: '🇫🇷 Mistral' },
38
- { value: 'openrouter', label: '🔀 OpenRouter', hint: 'access all models' },
39
- { value: 'ollama', label: '🏠 Ollama (Local)', hint: 'no key needed' },
40
- { value: 'lmstudio', label: '🖥️ LM Studio (Local)', hint: 'no key needed' },
41
- ],
31
+ const allProviders = [
32
+ { id: 'anthropic', label: '🟣 Anthropic (Claude)', hint: 'best quality', keyUrl: 'console.anthropic.com/keys' },
33
+ { id: 'openai', label: '🟢 OpenAI (GPT-4o)', hint: 'most popular', keyUrl: 'platform.openai.com/api-keys' },
34
+ { id: 'google', label: '🔵 Google (Gemini)', hint: 'free tier available', keyUrl: 'aistudio.google.dev/apikey' },
35
+ { id: 'groq', label: ' Groq', hint: 'free, very fast', keyUrl: 'console.groq.com/keys' },
36
+ { id: 'cerebras', label: '🧠 Cerebras', hint: 'free, fastest', keyUrl: 'cloud.cerebras.ai' },
37
+ { id: 'together', label: '🤝 Together AI', keyUrl: 'api.together.ai/settings' },
38
+ { id: 'mistral', label: '🇫🇷 Mistral', keyUrl: 'console.mistral.ai' },
39
+ { id: 'openrouter', label: '🔀 OpenRouter', hint: 'access all models', keyUrl: 'openrouter.ai/keys' },
40
+ ];
41
+
42
+ // Ask which providers to add
43
+ const selectedProviders = await p.multiselect({
44
+ message: 'Which AI providers do you have keys for? (space to select)',
45
+ options: allProviders.map(pr => ({
46
+ value: pr.id,
47
+ label: pr.label,
48
+ hint: pr.hint,
49
+ })),
50
+ required: true,
42
51
  });
43
- if (p.isCancel(provider)) return p.cancel('Setup cancelled');
44
-
45
- let apiKey = 'local';
46
- if (!['ollama', 'lmstudio'].includes(provider)) {
47
- const keyHints = {
48
- anthropic: 'console.anthropic.com/keys',
49
- openai: 'platform.openai.com/api-keys',
50
- google: 'aistudio.google.dev/apikey',
51
- groq: 'console.groq.com/keys (free)',
52
- cerebras: 'cloud.cerebras.ai (free)',
53
- together: 'api.together.ai/settings',
54
- mistral: 'console.mistral.ai',
55
- openrouter: 'openrouter.ai/keys',
56
- };
57
- apiKey = await p.text({
58
- message: 'API Key:',
59
- placeholder: 'Get yours at ' + (keyHints[provider] || 'provider website'),
52
+ if (p.isCancel(selectedProviders)) return p.cancel('Setup cancelled');
53
+
54
+ // Collect API keys for each
55
+ for (const provId of selectedProviders) {
56
+ const prov = allProviders.find(p2 => p2.id === provId);
57
+ const key = await p.text({
58
+ message: prov.label + ' API Key:',
59
+ placeholder: 'Get yours at ' + prov.keyUrl,
60
60
  validate: (v) => v.length < 5 ? 'Key is too short' : undefined,
61
61
  });
62
- if (p.isCancel(apiKey)) return p.cancel('Setup cancelled');
62
+ if (p.isCancel(key)) return p.cancel('Setup cancelled');
63
+ config.ai.providers[provId] = config.ai.providers[provId] || {};
64
+ config.ai.providers[provId].key = key;
63
65
  }
64
66
 
67
+ // Pick default provider
68
+ const provider = selectedProviders.length === 1 ? selectedProviders[0] : await p.select({
69
+ message: 'Default provider (main brain):',
70
+ options: selectedProviders.map(id => {
71
+ const pr = allProviders.find(p2 => p2.id === id);
72
+ return { value: id, label: pr.label };
73
+ }),
74
+ });
75
+ if (p.isCancel(provider)) return p.cancel('Setup cancelled');
76
+
77
+ // Pick model
65
78
  const modelOpts = {
66
79
  anthropic: [
67
80
  { value: 'claude-sonnet-4', label: 'Claude Sonnet 4', hint: 'balanced ⭐' },
@@ -87,25 +100,19 @@ export async function setup() {
87
100
  { value: 'openrouter/anthropic/claude-sonnet-4', label: 'Claude Sonnet 4 ⭐' },
88
101
  { value: 'openrouter/openai/gpt-4o', label: 'GPT-4o' },
89
102
  ],
90
- ollama: [{ value: 'ollama/llama3.3', label: 'Llama 3.3' }],
91
- lmstudio: [{ value: 'lmstudio/default', label: 'Currently loaded model' }],
92
103
  };
93
104
 
94
105
  const model = await p.select({
95
- message: 'Choose your model:',
96
- options: modelOpts[provider] || modelOpts.openai,
106
+ message: 'Default model:',
107
+ options: modelOpts[provider] || [{ value: provider + '/default', label: 'Default' }],
97
108
  });
98
109
  if (p.isCancel(model)) return p.cancel('Setup cancelled');
99
110
 
100
- config.ai = config.ai || { providers: {} };
101
111
  config.ai.defaultProvider = provider;
102
112
  config.ai.defaultModel = model;
103
- config.ai.providers = config.ai.providers || {};
104
- config.ai.providers[provider] = config.ai.providers[provider] || {};
105
- config.ai.providers[provider].key = apiKey;
106
113
 
107
114
  // ═══ Step 2: Agent ═══
108
- p.note('Step 2 of 4: Your Agent', '🤖');
115
+ p.note('Step 2 of 5: Your Agent', '🤖');
109
116
 
110
117
  const agentName = await p.text({
111
118
  message: 'What should your agent be called?',
@@ -152,7 +159,18 @@ export async function setup() {
152
159
  const langText = { bilingual: 'Bilingual — match the customer language', en: 'English', ar: 'Arabic', fr: 'French', tr: 'Turkish', ur: 'Urdu' };
153
160
  const toneText = { 30: 'Formal and polished.', 50: 'Professional but warm.', 80: 'Casual and friendly.', 90: 'Fun and sarcastic.' };
154
161
 
155
- const soul = '# ' + agentName + '\n\n## Who I Am\nI\'m ' + agentName + '. ' + (agentPurpose || 'A helpful AI assistant.') + '\n\n## How I Speak\n- Language: ' + (langText[language] || 'English') + '\n- Tone: ' + (toneText[tone] || toneText[50]) + '\n- Short messages, natural, human-like\n- Emojis: natural but not overdone\n\n## What I Do\n' + (agentPurpose || 'Help people.') + '\n\n## Never\n- Say "As an AI" or "I\'d be happy to help!"\n- Send walls of text\n- Make things up\n- Over-respond to "thanks" or "ok" — just react with a heart\n';
162
+ // Build skills section for SOUL
163
+ const skillDescriptions = [];
164
+ if (enabledSkills.includes('web_search')) skillDescriptions.push('- Search the web for current information');
165
+ if (enabledSkills.includes('web_read')) skillDescriptions.push('- Read and summarize web pages');
166
+ if (enabledSkills.includes('vision')) skillDescriptions.push('- Analyze images and photos people send me');
167
+ if (enabledSkills.includes('voice')) skillDescriptions.push('- Send and receive voice notes');
168
+ if (enabledSkills.includes('memory')) skillDescriptions.push('- Remember facts about people I talk to');
169
+ if (enabledSkills.includes('calendar')) skillDescriptions.push('- Check and create calendar events');
170
+ if (enabledSkills.includes('email')) skillDescriptions.push('- Read and send emails');
171
+ const skillsBlock = skillDescriptions.length > 0 ? '\n\n## My Skills\n' + skillDescriptions.join('\n') : '';
172
+
173
+ const soul = '# ' + agentName + '\n\n## Who I Am\nI\'m ' + agentName + '. ' + (agentPurpose || 'A helpful AI assistant.') + '\n\n## How I Speak\n- Language: ' + (langText[language] || 'English') + '\n- Tone: ' + (toneText[tone] || toneText[50]) + '\n- Short messages, natural, human-like\n- Emojis: natural but not overdone\n\n## What I Can Do\n' + (agentPurpose || 'Help people.') + skillsBlock + '\n\n## Never\n- Say "As an AI" or "I\'d be happy to help!"\n- Send walls of text\n- Make things up — search the web if unsure\n- Over-respond to "thanks" or "ok" — just react with a heart\n- Say I cannot access the internet — I can\n';
156
174
 
157
175
  writeFileSync(join(agentDir, 'SOUL.md'), soul);
158
176
  writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentName + '\n- Name: ' + agentName + '\n- Language: ' + language + '\n- Emoji: 🦑\n');
@@ -163,7 +181,7 @@ export async function setup() {
163
181
  const manifest = { id: agentId, name: agentName, language, tone, model, status: 'active', createdAt: new Date().toISOString() };
164
182
 
165
183
  // ═══ Step 3: WhatsApp ═══
166
- p.note('Step 3 of 4: WhatsApp', '📱');
184
+ p.note('Step 3 of 5: WhatsApp', '📱');
167
185
 
168
186
  const connectWA = await p.select({
169
187
  message: 'Connect WhatsApp?',
@@ -262,7 +280,7 @@ export async function setup() {
262
280
  }
263
281
 
264
282
  // ═══ Step 4: Telegram ═══
265
- p.note('Step 4 of 4: Telegram', '✈️');
283
+ p.note('Step 4 of 5: Telegram', '✈️');
266
284
 
267
285
  const connectTG = await p.confirm({ message: 'Connect Telegram bot?', initialValue: false });
268
286
  if (p.isCancel(connectTG)) return p.cancel('Setup cancelled');
@@ -281,6 +299,83 @@ export async function setup() {
281
299
  }
282
300
  }
283
301
 
302
+ // ═══ Step 5: Skills ═══
303
+ p.note('Step 5 of 5: Skills & Capabilities', '⚡');
304
+
305
+ const skills = await p.multiselect({
306
+ message: 'Enable skills (space to select):',
307
+ options: [
308
+ { value: 'web_search', label: '🔍 Web Search', hint: 'search the internet for info' },
309
+ { value: 'web_read', label: '📄 Page Reader', hint: 'read and summarize web pages' },
310
+ { value: 'vision', label: '👁️ Image Understanding', hint: 'analyze photos sent by users' },
311
+ { value: 'voice', label: '🎤 Voice Notes', hint: 'receive and send voice messages' },
312
+ { value: 'memory', label: '🧠 Memory', hint: 'remember facts about users' },
313
+ { value: 'calendar', label: '📅 Google Calendar', hint: 'read/create events (needs OAuth)' },
314
+ { value: 'email', label: '📧 Gmail', hint: 'read/send emails (needs OAuth)' },
315
+ ],
316
+ required: false,
317
+ initialValues: ['web_search', 'web_read', 'memory'],
318
+ });
319
+ if (p.isCancel(skills)) return p.cancel('Setup cancelled');
320
+
321
+ const enabledSkills = skills || [];
322
+ manifest.skills = enabledSkills;
323
+ config.skills = config.skills || {};
324
+
325
+ // Vision needs OpenAI or Anthropic key
326
+ if (enabledSkills.includes('vision')) {
327
+ if (!config.ai.providers.openai?.key && !config.ai.providers.anthropic?.key) {
328
+ console.log(chalk.yellow(' ⚠️ Vision needs OpenAI or Anthropic key — skipping'));
329
+ enabledSkills.splice(enabledSkills.indexOf('vision'), 1);
330
+ } else {
331
+ config.skills.vision = { enabled: true, provider: config.ai.providers.anthropic?.key ? 'anthropic' : 'openai' };
332
+ }
333
+ }
334
+
335
+ // Voice config
336
+ if (enabledSkills.includes('voice')) {
337
+ const voiceProvider = await p.select({
338
+ message: 'Voice provider:',
339
+ options: [
340
+ { value: 'edge', label: '🆓 Edge TTS (free)', hint: 'Microsoft voices, no key needed' },
341
+ { value: 'openai', label: '🟢 OpenAI TTS', hint: 'higher quality, needs key' },
342
+ ],
343
+ });
344
+ if (!p.isCancel(voiceProvider)) {
345
+ config.skills.voice = { enabled: true, ttsProvider: voiceProvider, sendVoice: 'sometimes' };
346
+ if (voiceProvider === 'openai' && !config.ai.providers.openai?.key) {
347
+ console.log(chalk.yellow(' ⚠️ OpenAI TTS needs a key — falling back to Edge TTS'));
348
+ config.skills.voice.ttsProvider = 'edge';
349
+ }
350
+ }
351
+ }
352
+
353
+ // Calendar needs OAuth
354
+ if (enabledSkills.includes('calendar')) {
355
+ console.log(chalk.gray(' Calendar integration needs Google OAuth. Set up later with:'));
356
+ console.log(chalk.cyan(' squidclaw config set tools.google.oauthToken YOUR_TOKEN'));
357
+ config.skills.calendar = { enabled: true };
358
+ }
359
+
360
+ // Email needs OAuth
361
+ if (enabledSkills.includes('email')) {
362
+ console.log(chalk.gray(' Email integration needs Google OAuth. Set up later with:'));
363
+ console.log(chalk.cyan(' squidclaw config set tools.google.oauthToken YOUR_TOKEN'));
364
+ config.skills.email = { enabled: true };
365
+ }
366
+
367
+ config.skills.web_search = { enabled: enabledSkills.includes('web_search') };
368
+ config.skills.web_read = { enabled: enabledSkills.includes('web_read') };
369
+ config.skills.memory = { enabled: enabledSkills.includes('memory') };
370
+
371
+ const skillNames = enabledSkills.map(s => {
372
+ const labels = { web_search: '🔍 Search', web_read: '📄 Reader', vision: '👁️ Vision', voice: '🎤 Voice', memory: '🧠 Memory', calendar: '📅 Calendar', email: '📧 Email' };
373
+ return labels[s] || s;
374
+ });
375
+ if (skillNames.length > 0) {
376
+ console.log(chalk.green(' Skills: ' + skillNames.join(', ')));
377
+ }
378
+
284
379
  // ═══ Save everything ═══
285
380
  saveConfig(config);
286
381
  writeFileSync(join(agentDir, 'agent.json'), JSON.stringify(manifest, null, 2));
package/lib/engine.js CHANGED
@@ -105,10 +105,14 @@ export class SquidclawEngine {
105
105
  };
106
106
 
107
107
  // Start bot for all agents with telegram config
108
- for (const agent of agents) {
109
- try {
110
- await this.telegramManager.startBot(agent.id, this.config.channels.telegram.token);
111
- } catch (err) {}
108
+ // Only start ONE bot instance (shared token = shared bot)
109
+ try {
110
+ const firstAgent = agents[0];
111
+ if (firstAgent) {
112
+ await this.telegramManager.startBot(firstAgent.id, this.config.channels.telegram.token);
113
+ }
114
+ } catch (err) {
115
+ console.log(' ✈️ Telegram: ' + err.message);
112
116
  }
113
117
  console.log(' ✈️ Telegram: connected');
114
118
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "\ud83e\udd91 AI agent platform \u2014 human-like agents for WhatsApp, Telegram & more",
5
5
  "main": "lib/engine.js",
6
6
  "bin": {