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.
- package/lib/channels/telegram/bot.js +6 -1
- package/lib/cli/setup.js +141 -46
- package/lib/engine.js +8 -4
- package/package.json +1 -1
|
@@ -107,7 +107,12 @@ export class TelegramManager {
|
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
bot.catch((err) => {
|
|
110
|
-
|
|
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
|
|
26
|
-
p.note('Step 1 of
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
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: '
|
|
96
|
-
options: modelOpts[provider] ||
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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) {
|