squidclaw 0.2.2 → 0.3.1
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/bin/squidclaw.js +4 -3
- package/lib/cli/setup.js +188 -121
- package/package.json +1 -1
package/bin/squidclaw.js
CHANGED
|
@@ -24,8 +24,9 @@ program
|
|
|
24
24
|
|
|
25
25
|
// ── Setup ──────────────────────────────────────────────
|
|
26
26
|
program
|
|
27
|
-
.command('
|
|
28
|
-
.
|
|
27
|
+
.command('hatch')
|
|
28
|
+
.alias('setup')
|
|
29
|
+
.description('🥚 Hatch your first Squidclaw agent')
|
|
29
30
|
.action(async () => {
|
|
30
31
|
const { setup } = await import('../lib/cli/setup.js');
|
|
31
32
|
await setup();
|
|
@@ -492,7 +493,7 @@ if (process.argv.length <= 2) {
|
|
|
492
493
|
🦑 Squidclaw v${pkg.version}
|
|
493
494
|
AI agent platform — human-like agents for WhatsApp & more
|
|
494
495
|
|
|
495
|
-
${chalk.gray('Get started:')} squidclaw
|
|
496
|
+
${chalk.gray('Get started:')} squidclaw hatch
|
|
496
497
|
${chalk.gray('Help:')} squidclaw --help
|
|
497
498
|
${chalk.gray('Docs:')} https://squidclaw.dev
|
|
498
499
|
`));
|
package/lib/cli/setup.js
CHANGED
|
@@ -1,90 +1,110 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 🦑 Setup Wizard
|
|
3
|
-
*
|
|
3
|
+
* One command to install everything — AI, agent, WhatsApp, Telegram
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { loadConfig, saveConfig, ensureHome, getHome } from '../core/config.js';
|
|
9
|
-
import { writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
import crypto from 'crypto';
|
|
12
12
|
|
|
13
13
|
export async function setup() {
|
|
14
14
|
console.clear();
|
|
15
|
-
|
|
15
|
+
console.log(chalk.cyan('\n ╔══════════════════════════════════════╗'));
|
|
16
|
+
console.log(chalk.cyan(' ║ 🥚 Squidclaw Hatch ║'));
|
|
17
|
+
console.log(chalk.cyan(' ║ Hatching your AI agent... ║'));
|
|
18
|
+
console.log(chalk.cyan(' ╚══════════════════════════════════════╝\n'));
|
|
19
|
+
p.intro(chalk.gray("Let's get you up and running"));
|
|
16
20
|
|
|
17
21
|
const config = loadConfig();
|
|
18
22
|
ensureHome();
|
|
19
23
|
|
|
20
|
-
// Step 1: AI Provider
|
|
24
|
+
// ═══ Step 1: AI Provider ═══
|
|
25
|
+
p.note('Step 1 of 5: AI Brain', '🧠');
|
|
26
|
+
|
|
21
27
|
const provider = await p.select({
|
|
22
28
|
message: 'Choose your AI provider:',
|
|
23
29
|
options: [
|
|
24
30
|
{ value: 'anthropic', label: '🟣 Anthropic (Claude)', hint: 'recommended' },
|
|
25
31
|
{ value: 'openai', label: '🟢 OpenAI (GPT-4o)' },
|
|
26
|
-
{ value: 'google', label: '🔵 Google (Gemini)', hint: 'free tier
|
|
32
|
+
{ value: 'google', label: '🔵 Google (Gemini)', hint: 'free tier' },
|
|
27
33
|
{ value: 'groq', label: '⚡ Groq (Llama)', hint: 'free, very fast' },
|
|
28
34
|
{ value: 'cerebras', label: '🧠 Cerebras (Llama)', hint: 'free, fastest' },
|
|
29
|
-
{ value: '
|
|
35
|
+
{ value: 'together', label: '🤝 Together AI' },
|
|
36
|
+
{ value: 'mistral', label: '🇫🇷 Mistral' },
|
|
37
|
+
{ value: 'openrouter', label: '🔀 OpenRouter', hint: 'access all models' },
|
|
38
|
+
{ value: 'ollama', label: '🏠 Ollama (Local)', hint: 'no key needed' },
|
|
39
|
+
{ value: 'lmstudio', label: '🖥️ LM Studio (Local)', hint: 'no key needed' },
|
|
30
40
|
],
|
|
31
41
|
});
|
|
32
42
|
if (p.isCancel(provider)) return p.cancel('Setup cancelled');
|
|
33
43
|
|
|
34
|
-
// Step 2: API Key
|
|
35
44
|
let apiKey = 'local';
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
38
|
-
anthropic: '
|
|
39
|
-
openai: '
|
|
40
|
-
google: '
|
|
41
|
-
groq: '
|
|
42
|
-
cerebras: '
|
|
45
|
+
if (!['ollama', 'lmstudio'].includes(provider)) {
|
|
46
|
+
const keyHints = {
|
|
47
|
+
anthropic: 'console.anthropic.com/keys',
|
|
48
|
+
openai: 'platform.openai.com/api-keys',
|
|
49
|
+
google: 'aistudio.google.dev/apikey',
|
|
50
|
+
groq: 'console.groq.com/keys (free)',
|
|
51
|
+
cerebras: 'cloud.cerebras.ai (free)',
|
|
52
|
+
together: 'api.together.ai/settings',
|
|
53
|
+
mistral: 'console.mistral.ai',
|
|
54
|
+
openrouter: 'openrouter.ai/keys',
|
|
43
55
|
};
|
|
44
|
-
|
|
45
56
|
apiKey = await p.text({
|
|
46
|
-
message:
|
|
47
|
-
placeholder: '
|
|
57
|
+
message: 'API Key:',
|
|
58
|
+
placeholder: 'Get yours at ' + (keyHints[provider] || 'provider website'),
|
|
48
59
|
validate: (v) => v.length < 5 ? 'Key is too short' : undefined,
|
|
49
60
|
});
|
|
50
61
|
if (p.isCancel(apiKey)) return p.cancel('Setup cancelled');
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
const modelOptions = {
|
|
64
|
+
const modelOpts = {
|
|
55
65
|
anthropic: [
|
|
56
|
-
{ value: 'claude-sonnet-4', label: 'Claude Sonnet 4', hint: 'balanced
|
|
66
|
+
{ value: 'claude-sonnet-4', label: 'Claude Sonnet 4', hint: 'balanced ⭐' },
|
|
57
67
|
{ value: 'claude-opus-4', label: 'Claude Opus 4', hint: 'most powerful' },
|
|
58
68
|
{ value: 'claude-haiku-3.5', label: 'Claude Haiku 3.5', hint: 'fastest, cheapest' },
|
|
59
69
|
],
|
|
60
70
|
openai: [
|
|
61
|
-
{ value: 'gpt-4o', label: 'GPT-4o', hint: 'recommended' },
|
|
71
|
+
{ value: 'gpt-4o', label: 'GPT-4o', hint: 'recommended ⭐' },
|
|
62
72
|
{ value: 'gpt-4o-mini', label: 'GPT-4o Mini', hint: 'cheaper' },
|
|
63
73
|
],
|
|
64
74
|
google: [
|
|
65
|
-
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', hint: '
|
|
66
|
-
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', hint: '
|
|
75
|
+
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', hint: 'fast ⭐' },
|
|
76
|
+
{ value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', hint: 'powerful' },
|
|
67
77
|
],
|
|
68
78
|
groq: [
|
|
69
|
-
{ value: 'groq/llama-4-scout', label: 'Llama 4 Scout', hint: 'free
|
|
79
|
+
{ value: 'groq/llama-4-scout', label: 'Llama 4 Scout', hint: 'free ⭐' },
|
|
70
80
|
{ value: 'groq/llama-4-maverick', label: 'Llama 4 Maverick', hint: 'free, powerful' },
|
|
71
81
|
],
|
|
72
|
-
cerebras: [
|
|
73
|
-
|
|
74
|
-
],
|
|
75
|
-
|
|
76
|
-
{ value: '
|
|
82
|
+
cerebras: [{ value: 'cerebras/llama-3.3-70b', label: 'Llama 3.3 70B', hint: 'free ⭐' }],
|
|
83
|
+
together: [{ value: 'together/llama-4-maverick', label: 'Llama 4 Maverick ⭐' }],
|
|
84
|
+
mistral: [{ value: 'mistral/mistral-large', label: 'Mistral Large ⭐' }],
|
|
85
|
+
openrouter: [
|
|
86
|
+
{ value: 'openrouter/anthropic/claude-sonnet-4', label: 'Claude Sonnet 4 ⭐' },
|
|
87
|
+
{ value: 'openrouter/openai/gpt-4o', label: 'GPT-4o' },
|
|
77
88
|
],
|
|
89
|
+
ollama: [{ value: 'ollama/llama3.3', label: 'Llama 3.3' }],
|
|
90
|
+
lmstudio: [{ value: 'lmstudio/default', label: 'Currently loaded model' }],
|
|
78
91
|
};
|
|
79
92
|
|
|
80
93
|
const model = await p.select({
|
|
81
94
|
message: 'Choose your model:',
|
|
82
|
-
options:
|
|
95
|
+
options: modelOpts[provider] || modelOpts.openai,
|
|
83
96
|
});
|
|
84
97
|
if (p.isCancel(model)) return p.cancel('Setup cancelled');
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
config.ai = config.ai || { providers: {} };
|
|
100
|
+
config.ai.defaultProvider = provider;
|
|
101
|
+
config.ai.defaultModel = model;
|
|
102
|
+
config.ai.providers = config.ai.providers || {};
|
|
103
|
+
config.ai.providers[provider] = config.ai.providers[provider] || {};
|
|
104
|
+
config.ai.providers[provider].key = apiKey;
|
|
105
|
+
|
|
106
|
+
// ═══ Step 2: Agent ═══
|
|
107
|
+
p.note('Step 2 of 5: Your Agent', '🤖');
|
|
88
108
|
|
|
89
109
|
const agentName = await p.text({
|
|
90
110
|
message: 'What should your agent be called?',
|
|
@@ -102,125 +122,172 @@ export async function setup() {
|
|
|
102
122
|
const language = await p.select({
|
|
103
123
|
message: 'Language:',
|
|
104
124
|
options: [
|
|
125
|
+
{ value: 'bilingual', label: '🌍 Bilingual (Arabic + English)', hint: 'auto-detects' },
|
|
105
126
|
{ value: 'en', label: '🇬🇧 English' },
|
|
106
127
|
{ value: 'ar', label: '🇸🇦 Arabic' },
|
|
107
|
-
{ value: '
|
|
128
|
+
{ value: 'fr', label: '🇫🇷 French' },
|
|
129
|
+
{ value: 'tr', label: '🇹🇷 Turkish' },
|
|
130
|
+
{ value: 'ur', label: '🇵🇰 Urdu' },
|
|
108
131
|
],
|
|
109
132
|
});
|
|
110
133
|
if (p.isCancel(language)) return p.cancel('Setup cancelled');
|
|
111
134
|
|
|
112
135
|
const tone = await p.select({
|
|
113
|
-
message: '
|
|
136
|
+
message: 'Personality:',
|
|
114
137
|
options: [
|
|
115
|
-
{ value:
|
|
116
|
-
{ value: 50, label: '🤝 Professional but friendly' },
|
|
138
|
+
{ value: 50, label: '🤝 Professional but friendly', hint: 'recommended' },
|
|
117
139
|
{ value: 80, label: '😊 Casual and warm' },
|
|
140
|
+
{ value: 30, label: '👔 Formal and polished' },
|
|
141
|
+
{ value: 90, label: '🔥 Fun and sarcastic' },
|
|
118
142
|
],
|
|
119
143
|
});
|
|
120
144
|
if (p.isCancel(tone)) return p.cancel('Setup cancelled');
|
|
121
145
|
|
|
122
|
-
// Step 5: WhatsApp
|
|
123
|
-
const connectWA = await p.confirm({
|
|
124
|
-
message: 'Connect WhatsApp now?',
|
|
125
|
-
initialValue: true,
|
|
126
|
-
});
|
|
127
|
-
if (p.isCancel(connectWA)) return p.cancel('Setup cancelled');
|
|
128
|
-
|
|
129
|
-
// Save config
|
|
130
|
-
config.ai.defaultProvider = provider;
|
|
131
|
-
config.ai.defaultModel = model;
|
|
132
|
-
config.ai.providers[provider].key = apiKey;
|
|
133
|
-
|
|
134
|
-
if (connectWA) {
|
|
135
|
-
config.channels.whatsapp.enabled = true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
saveConfig(config);
|
|
139
|
-
|
|
140
|
-
// Create agent files
|
|
141
146
|
const agentId = crypto.randomUUID().slice(0, 8);
|
|
142
147
|
const agentDir = join(getHome(), 'agents', agentId);
|
|
143
148
|
mkdirSync(join(agentDir, 'memory'), { recursive: true });
|
|
149
|
+
mkdirSync(join(agentDir, 'knowledge'), { recursive: true });
|
|
144
150
|
|
|
145
|
-
const
|
|
151
|
+
const langText = { bilingual: 'Bilingual — match the customer language', en: 'English', ar: 'Arabic', fr: 'French', tr: 'Turkish', ur: 'Urdu' };
|
|
152
|
+
const toneText = { 30: 'Formal and polished.', 50: 'Professional but warm.', 80: 'Casual and friendly.', 90: 'Fun and sarcastic.' };
|
|
146
153
|
|
|
147
|
-
## Who I Am
|
|
148
|
-
I'm ${agentName}. ${agentPurpose || 'I\'m a helpful AI assistant.'}
|
|
154
|
+
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 ❤️\n';
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
writeFileSync(join(agentDir, 'SOUL.md'), soul);
|
|
157
|
+
writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentName + '\n- Name: ' + agentName + '\n- Language: ' + language + '\n- Emoji: 🦑\n');
|
|
158
|
+
writeFileSync(join(agentDir, 'MEMORY.md'), '# Memory\n\n_Learning about people._\n');
|
|
159
|
+
writeFileSync(join(agentDir, 'RULES.md'), '# Rules\n\n1. Be helpful, be human, be honest\n2. Keep messages short\n3. Don\'t make things up\n4. Match customer language\n5. Respect privacy\n');
|
|
160
|
+
writeFileSync(join(agentDir, 'BEHAVIOR.md'), JSON.stringify({ splitMessages: true, maxChunkLength: 200, reactBeforeReply: true, reactOnlyEndings: true, autoDetectLanguage: true, avoidPhrases: ["Is there anything else I can help with?", "I'd be happy to help!", "Great question!", "As an AI"], handoff: { enabled: false }, heartbeat: '30m' }, null, 2));
|
|
155
161
|
|
|
156
|
-
|
|
157
|
-
${agentPurpose || 'Help people with their questions and tasks.'}
|
|
162
|
+
const manifest = { id: agentId, name: agentName, language, tone, model, status: 'active', createdAt: new Date().toISOString() };
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
- Send walls of text — I keep it short and conversational
|
|
162
|
-
- Make things up when I don't know — I say "let me check" instead
|
|
163
|
-
`;
|
|
164
|
+
// ═══ Step 3: WhatsApp ═══
|
|
165
|
+
p.note('Step 3 of 5: WhatsApp', '📱');
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
maxChunkLength: 200,
|
|
172
|
-
reactBeforeReply: true,
|
|
173
|
-
reactOnlyEndings: true,
|
|
174
|
-
autoDetectLanguage: true,
|
|
175
|
-
avoidPhrases: [
|
|
176
|
-
"Is there anything else I can help with?",
|
|
177
|
-
"I'd be happy to help!",
|
|
178
|
-
"Great question!",
|
|
179
|
-
"As an AI",
|
|
167
|
+
const connectWA = await p.select({
|
|
168
|
+
message: 'Connect WhatsApp?',
|
|
169
|
+
options: [
|
|
170
|
+
{ value: 'pair', label: '📲 Pairing Code (easier)', hint: 'enter phone number' },
|
|
171
|
+
{ value: 'qr', label: '📷 QR Code', hint: 'scan with phone' },
|
|
172
|
+
{ value: 'skip', label: '⏭️ Skip for now' },
|
|
180
173
|
],
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}, null, 2));
|
|
184
|
-
|
|
185
|
-
// Save agent to a manifest file for the engine to pick up
|
|
186
|
-
const manifest = {
|
|
187
|
-
id: agentId,
|
|
188
|
-
name: agentName,
|
|
189
|
-
soul,
|
|
190
|
-
language,
|
|
191
|
-
tone,
|
|
192
|
-
model,
|
|
193
|
-
status: 'active',
|
|
194
|
-
createdAt: new Date().toISOString(),
|
|
195
|
-
};
|
|
196
|
-
writeFileSync(join(agentDir, 'agent.json'), JSON.stringify(manifest, null, 2));
|
|
197
|
-
|
|
198
|
-
p.note(`Agent "${agentName}" created in ${agentDir}`);
|
|
174
|
+
});
|
|
175
|
+
if (p.isCancel(connectWA)) return p.cancel('Setup cancelled');
|
|
199
176
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
message: '📱 Your WhatsApp phone number:',
|
|
177
|
+
if (connectWA === 'pair') {
|
|
178
|
+
const waPhone = await p.text({
|
|
179
|
+
message: 'WhatsApp phone number (with country code):',
|
|
204
180
|
placeholder: '+966 5XX XXX XXXX',
|
|
205
181
|
validate: (v) => v.replace(/[^0-9+]/g, '').length < 8 ? 'Enter a valid phone number' : undefined,
|
|
206
182
|
});
|
|
183
|
+
if (!p.isCancel(waPhone)) {
|
|
184
|
+
manifest.whatsappNumber = waPhone.replace(/[^0-9]/g, '');
|
|
185
|
+
manifest.whatsappLoginMethod = 'pair';
|
|
186
|
+
}
|
|
187
|
+
} else if (connectWA === 'qr') {
|
|
188
|
+
manifest.whatsappLoginMethod = 'qr';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (connectWA !== 'skip') {
|
|
192
|
+
config.channels = config.channels || {};
|
|
193
|
+
config.channels.whatsapp = config.channels.whatsapp || {};
|
|
194
|
+
config.channels.whatsapp.enabled = true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ═══ Step 4: Telegram ═══
|
|
198
|
+
p.note('Step 4 of 5: Telegram', '✈️');
|
|
199
|
+
|
|
200
|
+
const connectTG = await p.confirm({ message: 'Connect Telegram bot?', initialValue: false });
|
|
201
|
+
if (p.isCancel(connectTG)) return p.cancel('Setup cancelled');
|
|
202
|
+
|
|
203
|
+
if (connectTG) {
|
|
204
|
+
console.log(chalk.gray('\n To create a Telegram bot:\n 1. Open @BotFather on Telegram\n 2. Send /newbot\n 3. Copy the token\n'));
|
|
205
|
+
const tgToken = await p.text({
|
|
206
|
+
message: 'Telegram Bot Token:',
|
|
207
|
+
placeholder: '123456789:ABCdefGhIjKlMnOpQrStUvWxYz',
|
|
208
|
+
validate: (v) => !v.includes(':') ? 'Invalid token (should contain ":")' : undefined,
|
|
209
|
+
});
|
|
210
|
+
if (!p.isCancel(tgToken)) {
|
|
211
|
+
manifest.telegramToken = tgToken;
|
|
212
|
+
config.channels = config.channels || {};
|
|
213
|
+
config.channels.telegram = { enabled: true };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ═══ Step 5: Knowledge ═══
|
|
218
|
+
p.note('Step 5 of 5: Knowledge (optional)', '📚');
|
|
219
|
+
|
|
220
|
+
const addKnowledge = await p.confirm({
|
|
221
|
+
message: 'Upload docs now? (menu, FAQ, etc — you can add later too)',
|
|
222
|
+
initialValue: false,
|
|
223
|
+
});
|
|
224
|
+
if (p.isCancel(addKnowledge)) return p.cancel('Setup cancelled');
|
|
225
|
+
|
|
226
|
+
const knowledgeFiles = [];
|
|
227
|
+
if (addKnowledge) {
|
|
228
|
+
let addMore = true;
|
|
229
|
+
while (addMore) {
|
|
230
|
+
const ft = await p.select({
|
|
231
|
+
message: 'What to add?',
|
|
232
|
+
options: [
|
|
233
|
+
{ value: 'file', label: '📄 Local file (PDF, TXT, MD)' },
|
|
234
|
+
{ value: 'url', label: '🌐 Website URL' },
|
|
235
|
+
{ value: 'text', label: '✏️ Type it directly' },
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
if (p.isCancel(ft)) break;
|
|
207
239
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
240
|
+
if (ft === 'file') {
|
|
241
|
+
const fp = await p.text({ message: 'File path:', placeholder: '/path/to/menu.pdf', validate: (v) => !existsSync(v) ? 'File not found' : undefined });
|
|
242
|
+
if (!p.isCancel(fp)) knowledgeFiles.push({ type: 'file', path: fp });
|
|
243
|
+
} else if (ft === 'url') {
|
|
244
|
+
const url = await p.text({ message: 'URL:', placeholder: 'https://mysite.com/menu', validate: (v) => !v.startsWith('http') ? 'Enter a valid URL' : undefined });
|
|
245
|
+
if (!p.isCancel(url)) knowledgeFiles.push({ type: 'url', path: url });
|
|
246
|
+
} else {
|
|
247
|
+
const txt = await p.text({ message: 'Paste your text:', placeholder: 'Business hours: 9am-11pm...' });
|
|
248
|
+
if (!p.isCancel(txt)) knowledgeFiles.push({ type: 'text', content: txt });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const more = await p.confirm({ message: 'Add more?', initialValue: false });
|
|
252
|
+
addMore = !p.isCancel(more) && more;
|
|
214
253
|
}
|
|
215
254
|
}
|
|
216
255
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
256
|
+
// ═══ Save everything ═══
|
|
257
|
+
saveConfig(config);
|
|
258
|
+
writeFileSync(join(agentDir, 'agent.json'), JSON.stringify(manifest, null, 2));
|
|
259
|
+
if (knowledgeFiles.length > 0) {
|
|
260
|
+
writeFileSync(join(agentDir, 'knowledge', 'pending.json'), JSON.stringify(knowledgeFiles, null, 2));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ═══ Summary ═══
|
|
264
|
+
const waStatus = connectWA === 'pair' ? 'Pairing code (on start)' : connectWA === 'qr' ? 'QR code (on start)' : 'skipped';
|
|
265
|
+
const tgStatus = connectTG ? 'bot ready' : 'skipped';
|
|
220
266
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
267
|
+
console.log(chalk.green('\n ════════════════════════════════════'));
|
|
268
|
+
console.log(chalk.green(' ✅ Setup Complete!'));
|
|
269
|
+
console.log(chalk.green(' ════════════════════════════════════\n'));
|
|
270
|
+
console.log(' ' + chalk.bold('Agent:') + ' ' + agentName + ' (' + agentId + ')');
|
|
271
|
+
console.log(' ' + chalk.bold('Brain:') + ' ' + model + ' via ' + provider);
|
|
272
|
+
console.log(' ' + chalk.bold('Language:') + ' ' + (langText[language] || language));
|
|
273
|
+
console.log(' ' + chalk.bold('WhatsApp:') + ' ' + waStatus);
|
|
274
|
+
console.log(' ' + chalk.bold('Telegram:') + ' ' + tgStatus);
|
|
275
|
+
if (knowledgeFiles.length > 0) console.log(' ' + chalk.bold('Knowledge:') + ' ' + knowledgeFiles.length + ' items queued');
|
|
276
|
+
console.log('\n ────────────────────────────────────');
|
|
277
|
+
console.log('\n ' + chalk.bold('Now just run:\n'));
|
|
278
|
+
console.log(' ' + chalk.cyan('squidclaw start'));
|
|
279
|
+
if (connectWA === 'pair') {
|
|
280
|
+
console.log('\n A pairing code will appear. Enter it in:');
|
|
281
|
+
console.log(' WhatsApp → ⚙️ → Linked Devices → Link → Link with phone number');
|
|
282
|
+
} else if (connectWA === 'qr') {
|
|
283
|
+
console.log('\n A QR code will appear. Scan it in:');
|
|
284
|
+
console.log(' WhatsApp → ⚙️ → Linked Devices → Link a Device');
|
|
285
|
+
}
|
|
286
|
+
console.log('\n ' + chalk.bold('Other commands:\n'));
|
|
287
|
+
console.log(' ' + chalk.cyan('squidclaw status') + ' — check what\'s running');
|
|
288
|
+
console.log(' ' + chalk.cyan('squidclaw tui') + ' — chat in terminal');
|
|
289
|
+
console.log(' ' + chalk.cyan('squidclaw agent chat ' + agentId) + ' — test your agent');
|
|
290
|
+
console.log(' ' + chalk.cyan('squidclaw knowledge upload') + ' — add documents');
|
|
291
|
+
console.log(' ' + chalk.cyan('squidclaw help') + ' — all commands');
|
|
292
|
+
console.log('\n ════════════════════════════════════\n');
|
|
226
293
|
}
|