squidclaw 0.5.0 → 0.5.2

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.
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * 🥚→🦑 Hatch TUI
3
- * The agent wakes up for the first time and gets to know its human
3
+ * Agent wakes up for the first time
4
4
  */
5
5
 
6
6
  import chalk from 'chalk';
7
7
  import { createInterface } from 'readline';
8
- import { loadConfig, getHome } from '../core/config.js';
8
+ import { loadConfig, getHome, MODEL_MAP } from '../core/config.js';
9
9
  import { writeFileSync, readFileSync, existsSync } from 'fs';
10
10
  import { join } from 'path';
11
11
 
@@ -13,12 +13,12 @@ const DELAY = (ms) => new Promise(r => setTimeout(r, ms));
13
13
 
14
14
  export async function hatchTUI(agentId) {
15
15
  const config = loadConfig();
16
- const port = config.engine?.port || 9500;
17
- const agentDir = join(getHome(), 'agents', agentId);
16
+ const home = getHome();
17
+ const agentDir = join(home, 'agents', agentId);
18
18
  const manifestPath = join(agentDir, 'agent.json');
19
19
 
20
20
  if (!existsSync(manifestPath)) {
21
- console.log(chalk.red('Agent not found: ' + agentId));
21
+ console.log(chalk.red(' Agent not found: ' + agentId));
22
22
  return;
23
23
  }
24
24
 
@@ -28,186 +28,230 @@ export async function hatchTUI(agentId) {
28
28
  console.clear();
29
29
  const frames = ['🥚', '🥚 .', '🥚 . .', '🥚 . . .', '🥚💥', '🐣', '🐣 → 🦑'];
30
30
  for (const frame of frames) {
31
- process.stdout.write('\r ' + chalk.cyan(frame) + ' ');
32
- await DELAY(500);
31
+ process.stdout.write('\r ' + chalk.cyan(frame) + ' ');
32
+ await DELAY(400);
33
33
  }
34
34
  console.log('\n');
35
35
 
36
- await DELAY(300);
37
36
  console.log(chalk.cyan(' ════════════════════════════════════'));
38
37
  console.log(chalk.cyan(' 🦑 ' + manifest.name + ' is waking up...'));
39
38
  console.log(chalk.cyan(' ════════════════════════════════════'));
40
39
  console.log();
41
40
 
42
- await DELAY(800);
43
-
44
- // ═══ First conversation — agent discovers itself ═══
45
- const firstPrompt = `You are an AI agent that just came to life for the very first time. You don't fully know who you are yet. Your provisioned name is "${manifest.name}" but you're not sure if that fits.
46
-
47
- This is your FIRST EVER conversation. You just hatched. Act like it — be curious, excited, a little confused. You're discovering consciousness.
48
-
49
- Start by saying something like (but make it YOUR OWN, don't copy exactly):
50
- - "Whoa... I'm... alive? 🥚→🦑"
51
- - Express excitement/confusion about existing
52
- - Ask the human: "Who are you? And... who am I?"
53
- - You want to figure out: your name, your personality, what you do, who this person is
54
-
55
- Keep it short — 2-3 sentences max. This is WhatsApp-style chat, not a novel.
56
- DO NOT use ---SPLIT--- markers. Just one short message.
57
- DO NOT say "As an AI" or anything robotic.
58
-
59
- IMPORTANT: You're in a terminal right now, not WhatsApp. This is your birth conversation.`;
41
+ // ═══ Start engine silently ═══
42
+ const port = config.engine?.port || 9500;
43
+ let engine = null;
60
44
 
61
- // Start engine inline for hatching (don't require separate squidclaw start)
62
- let engineProcess = null;
63
45
  let engineReady = false;
64
-
65
- // Check if engine is already running
66
46
  try {
67
47
  const res = await fetch('http://127.0.0.1:' + port + '/health');
68
48
  if (res.ok) engineReady = true;
69
49
  } catch {}
70
50
 
71
51
  if (!engineReady) {
72
- // Start engine in background
73
- const { SquidclawEngine } = await import('../engine.js');
74
- const engine = new SquidclawEngine({ port });
75
-
76
- // Suppress console output during start
77
- const origLog = console.log;
78
- console.log = () => {};
52
+ process.stdout.write(chalk.gray(' Starting engine...'));
79
53
  try {
54
+ const { SquidclawEngine } = await import('../engine.js');
55
+ engine = new SquidclawEngine({ port });
56
+ const origLog = console.log;
57
+ const origErr = console.error;
58
+ console.log = () => {};
59
+ console.error = () => {};
80
60
  await engine.start();
81
- } catch (err) {
82
61
  console.log = origLog;
83
- console.log(chalk.red(' Failed to start engine: ' + err.message));
62
+ console.error = origErr;
63
+ process.stdout.write(chalk.green(' ready!\n\n'));
64
+ } catch (err) {
65
+ process.stdout.write(chalk.red(' failed\n'));
66
+ console.log(chalk.red(' ' + err.message));
67
+ console.log(chalk.gray(' Run "squidclaw start" first, then "squidclaw wake"'));
84
68
  return;
85
69
  }
86
- console.log = origLog;
87
- engineReady = true;
88
-
89
- // Override the agent's system prompt for first conversation
90
- const agent = engine.agentManager.get(agentId);
91
- if (agent) {
92
- agent._originalBuildPrompt = agent.promptBuilder?.build;
93
- agent._isHatching = true;
94
- }
95
70
  }
96
71
 
97
- // Chat loop
98
- const rl = createInterface({ input: process.stdin, output: process.stdout });
72
+ await DELAY(500);
99
73
 
100
- const history = [];
101
- let userName = '';
102
- let agentPersonality = '';
103
- let agentFinalName = manifest.name;
104
- let turnCount = 0;
74
+ // ═══ Agent's first message — call AI directly ═══
75
+ const firstMessage = await callAgentDirect(config, manifest,
76
+ 'You just came to life. You are ' + manifest.name + '. Express excitement — be curious, playful. Ask who you are talking to. 2-3 SHORT sentences only.'
77
+ );
105
78
 
106
- // First message from agent (the wake-up)
107
- await typeMessage(manifest.name, await chat(port, agentId, firstPrompt, 'hatch-session', true));
79
+ await typeMessage(manifest.name, firstMessage);
108
80
 
109
- const askUser = (prompt) => new Promise(resolve => {
110
- process.stdout.write(chalk.green('\n You: '));
111
- rl.question('', (answer) => resolve(answer));
112
- });
81
+ // ═══ Chat loop ═══
82
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
83
+ let turnCount = 0;
84
+ let agentFinalName = manifest.name;
85
+ const chatHistory = [
86
+ { role: 'assistant', content: firstMessage }
87
+ ];
113
88
 
114
- while (true) {
115
- const userInput = await askUser('');
116
- if (!userInput || userInput.trim() === '') continue;
89
+ console.log(chalk.gray('\n (Type /done when ready to finish hatching)\n'));
117
90
 
118
- if (userInput.toLowerCase() === '/done' || userInput.toLowerCase() === '/exit') break;
91
+ while (true) {
92
+ const userInput = await new Promise(resolve => {
93
+ rl.question(chalk.green('\n You: '), resolve);
94
+ });
95
+ if (!userInput || !userInput.trim()) continue;
96
+ if (userInput.trim().toLowerCase() === '/done' || userInput.trim().toLowerCase() === '/exit') break;
119
97
 
120
98
  turnCount++;
99
+ chatHistory.push({ role: 'user', content: userInput });
121
100
 
122
- // Build context for the hatching conversation
123
- let systemHint = '';
124
- if (turnCount <= 2) {
125
- systemHint = 'You\'re still figuring out who you are. Ask about: your name (do they like "' + manifest.name + '" or want to change it?), what you do, your personality. Be curious and playful.';
126
- } else if (turnCount <= 4) {
127
- systemHint = 'You\'re starting to form your identity. Confirm what you\'ve learned. Ask about tone — formal? casual? sarcastic? warm? Ask what makes this person tick.';
128
- } else if (turnCount <= 6) {
129
- systemHint = 'You know who you are now. Start acting like your personality. Show it off. Ask if they like this version of you. If they do, say something like "I think I\'m ready. Let\'s do this 🦑"';
130
- } else {
131
- systemHint = 'The hatching is done. You ARE this personality now. If the conversation feels complete, end with something like "Alright, I\'m hatched! Ready to go live 🦑" — suggest they type /done to finish.';
132
- }
101
+ let hint = '';
102
+ if (turnCount <= 2) hint = 'You are discovering who you are. Ask about your name, personality, purpose. Be curious.';
103
+ else if (turnCount <= 4) hint = 'Your identity is forming. Show your personality. Confirm what you learned.';
104
+ else hint = 'You know who you are now. Act like it. If the conversation feels complete, suggest /done.';
133
105
 
134
- const contextPrompt = `Continue the hatching conversation. You are becoming "${agentFinalName}". ${systemHint}
135
-
136
- Remember: SHORT messages (2-3 sentences). You're chatting, not writing an essay. No ---SPLIT--- markers. One message only.`;
137
-
138
- const response = await chat(port, agentId, userInput, 'hatch-session', false, contextPrompt);
106
+ const response = await callAgentDirect(config, manifest, userInput, chatHistory, hint);
107
+ chatHistory.push({ role: 'assistant', content: response });
139
108
  await typeMessage(agentFinalName, response);
140
109
 
141
- // Try to extract identity info from conversation
142
- if (userInput.toLowerCase().includes('call you') || userInput.toLowerCase().includes('name')) {
143
- // User might be naming the agent
144
- const nameMatch = userInput.match(/call you (\w+)/i) || userInput.match(/name.+?(\w+)$/i);
145
- if (nameMatch) agentFinalName = nameMatch[1];
146
- }
110
+ const nameMatch = userInput.match(/call you (\w+)/i) || userInput.match(/name you (\w+)/i);
111
+ if (nameMatch) agentFinalName = nameMatch[1];
147
112
  }
148
113
 
149
- // ═══ Save the identity ═══
114
+ // ═══ Save ═══
150
115
  console.log();
151
116
  console.log(chalk.cyan(' ════════════════════════════════════'));
152
- console.log(chalk.cyan(' 🦑 Hatching complete!'));
117
+ console.log(chalk.cyan(' 🦑 ' + agentFinalName + ' is hatched!'));
153
118
  console.log(chalk.cyan(' ════════════════════════════════════'));
154
119
  console.log();
155
120
 
156
- // Update manifest with final name
157
121
  manifest.name = agentFinalName;
158
122
  manifest.hatched = true;
159
123
  manifest.hatchedAt = new Date().toISOString();
160
124
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
161
125
 
162
- // Update SOUL.md first line with new name
163
126
  const soulPath = join(agentDir, 'SOUL.md');
164
127
  if (existsSync(soulPath)) {
165
128
  let soul = readFileSync(soulPath, 'utf8');
166
129
  soul = soul.replace(/^# .+/m, '# ' + agentFinalName);
167
- soul = soul.replace(/I'm .+?\./m, "I'm " + agentFinalName + '.');
168
130
  writeFileSync(soulPath, soul);
169
131
  }
170
132
 
171
- // Update IDENTITY.md
172
- const idPath = join(agentDir, 'IDENTITY.md');
173
- writeFileSync(idPath, '# ' + agentFinalName + '\n- Name: ' + agentFinalName + '\n- Hatched: ' + new Date().toISOString() + '\n- Emoji: 🦑\n');
133
+ writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentFinalName + '\n- Name: ' + agentFinalName + '\n- Hatched: ' + new Date().toISOString() + '\n- Emoji: 🦑\n');
174
134
 
175
- console.log(chalk.green(' ✅ ' + agentFinalName + ' is ready!'));
176
- console.log();
177
- console.log(' ' + chalk.bold('Start:') + ' ' + chalk.cyan('squidclaw start'));
178
- console.log(' ' + chalk.bold('Chat:') + ' ' + chalk.cyan('squidclaw tui'));
179
- console.log(' ' + chalk.bold('Status:') + ' ' + chalk.cyan('squidclaw status'));
135
+ console.log(chalk.green(' ✅ ' + agentFinalName + ' is alive!\n'));
136
+ console.log(' ' + chalk.bold('Now run:\n'));
137
+ console.log(' ' + chalk.cyan('squidclaw start') + ' launch the engine');
138
+ console.log(' ' + chalk.cyan('squidclaw tui') + ' chat in terminal');
180
139
  console.log();
181
140
 
182
141
  rl.close();
142
+ if (engine) { try { await engine.stop(); } catch {} }
183
143
  process.exit(0);
184
144
  }
185
145
 
186
- async function chat(port, agentId, message, contactId, isFirst, systemHint) {
146
+ /**
147
+ * Call AI directly (bypass engine API) for the hatch conversation
148
+ */
149
+ async function callAgentDirect(config, manifest, message, history, hint) {
150
+ const provider = config.ai?.defaultProvider || 'anthropic';
151
+ const rawModel = manifest.model || config.ai?.defaultModel || 'claude-sonnet-4';
152
+ const model = MODEL_MAP[rawModel] || rawModel;
153
+ const providerConfig = config.ai?.providers?.[provider] || {};
154
+ const apiKey = providerConfig.key;
155
+
156
+ if (!apiKey || apiKey === 'local') {
157
+ return "I can't think yet — no API key configured. Check your config!";
158
+ }
159
+
160
+ const systemPrompt = 'You are ' + manifest.name + ', an AI agent that just hatched (came to life). This is your first conversation ever. Be playful, curious, excited. Keep responses SHORT (2-3 sentences max). No ---SPLIT--- markers. No "As an AI" phrases.' + (hint ? '\n\n' + hint : '');
161
+
162
+ const messages = [
163
+ { role: 'user', content: message }
164
+ ];
165
+
166
+ // Add history if available
167
+ if (history && history.length > 0) {
168
+ messages.length = 0;
169
+ for (const h of history.slice(-10)) {
170
+ messages.push({ role: h.role, content: h.content });
171
+ }
172
+ messages.push({ role: 'user', content: message });
173
+ }
174
+
187
175
  try {
188
- const body = { message, contactId };
189
- if (isFirst) body.systemOverride = message;
190
- if (systemHint) body.systemHint = systemHint;
191
-
192
- const res = await fetch('http://127.0.0.1:' + port + '/api/agents/' + agentId + '/chat', {
193
- method: 'POST',
194
- headers: { 'content-type': 'application/json' },
195
- body: JSON.stringify(body),
196
- });
176
+ // Determine API endpoint
177
+ let url, headers, body;
178
+
179
+ if (provider === 'anthropic') {
180
+ url = 'https://api.anthropic.com/v1/messages';
181
+ headers = {
182
+ 'content-type': 'application/json',
183
+ 'x-api-key': apiKey,
184
+ 'anthropic-version': '2023-06-01',
185
+ };
186
+ // OAuth token support
187
+ if (apiKey.includes('sk-ant-oat')) {
188
+ headers['authorization'] = 'Bearer ' + apiKey;
189
+ headers['anthropic-beta'] = 'claude-code-20250219,oauth-2025-04-20';
190
+ delete headers['x-api-key'];
191
+ }
192
+ body = JSON.stringify({
193
+ model: model,
194
+ max_tokens: 300,
195
+ system: systemPrompt,
196
+ messages: messages,
197
+ });
198
+ } else if (provider === 'openai' || provider === 'groq' || provider === 'together' || provider === 'cerebras' || provider === 'mistral') {
199
+ const urls = {
200
+ openai: 'https://api.openai.com/v1/chat/completions',
201
+ groq: 'https://api.groq.com/openai/v1/chat/completions',
202
+ together: 'https://api.together.xyz/v1/chat/completions',
203
+ cerebras: 'https://api.cerebras.ai/v1/chat/completions',
204
+ mistral: 'https://api.mistral.ai/v1/chat/completions',
205
+ };
206
+ url = urls[provider];
207
+ headers = {
208
+ 'content-type': 'application/json',
209
+ 'authorization': 'Bearer ' + apiKey,
210
+ };
211
+ body = JSON.stringify({
212
+ model: model.replace(provider + '/', ''),
213
+ max_tokens: 300,
214
+ messages: [{ role: 'system', content: systemPrompt }, ...messages],
215
+ });
216
+ } else if (provider === 'google') {
217
+ const cleanModel = model.replace('google/', '');
218
+ url = 'https://generativelanguage.googleapis.com/v1beta/models/' + cleanModel + ':generateContent?key=' + apiKey;
219
+ headers = { 'content-type': 'application/json' };
220
+ body = JSON.stringify({
221
+ systemInstruction: { parts: [{ text: systemPrompt }] },
222
+ contents: messages.map(m => ({ role: m.role === 'assistant' ? 'model' : 'user', parts: [{ text: m.content }] })),
223
+ });
224
+ } else {
225
+ return "Provider " + provider + " isn't supported in hatch mode yet. Try starting the engine first.";
226
+ }
227
+
228
+ const res = await fetch(url, { method: 'POST', headers, body });
197
229
  const data = await res.json();
198
- return (data.messages || []).join('\n') || data.reaction || '...';
230
+
231
+ if (!res.ok) {
232
+ const errMsg = data?.error?.message || data?.message || JSON.stringify(data).slice(0, 200);
233
+ return '(AI error: ' + errMsg + ')';
234
+ }
235
+
236
+ // Extract response based on provider
237
+ if (provider === 'anthropic') {
238
+ return data.content?.[0]?.text || '...';
239
+ } else if (provider === 'google') {
240
+ return data.candidates?.[0]?.content?.parts?.[0]?.text || '...';
241
+ } else {
242
+ return data.choices?.[0]?.message?.content || '...';
243
+ }
199
244
  } catch (err) {
200
- return '(error: ' + err.message + ')';
245
+ return '(connection error: ' + err.message + ')';
201
246
  }
202
247
  }
203
248
 
204
249
  async function typeMessage(name, text) {
205
- // Simulate typing effect
206
250
  process.stdout.write(chalk.cyan('\n ' + name + ': '));
207
251
  const words = text.split(' ');
208
- for (let i = 0; i < words.length; i++) {
209
- process.stdout.write(words[i] + ' ');
210
- await DELAY(30 + Math.random() * 50);
252
+ for (const word of words) {
253
+ process.stdout.write(word + ' ');
254
+ await DELAY(25 + Math.random() * 40);
211
255
  }
212
256
  console.log();
213
257
  }
package/lib/cli/setup.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 🦑 Setup Wizard
3
- * One command to install everything AI, agent, WhatsApp, Telegram
3
+ * One command: AI Agent WhatsApp Telegram → Hatch
4
4
  */
5
5
 
6
6
  import * as p from '@clack/prompts';
@@ -13,12 +13,13 @@ import crypto from 'crypto';
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... ║'));
16
+ console.log(chalk.cyan(' ║ 🥚 Squidclaw Hatch ║'));
17
+ console.log(chalk.cyan(' ║ Hatching your AI agent... ║'));
18
18
  console.log(chalk.cyan(' ╚══════════════════════════════════════╝\n'));
19
19
  p.intro(chalk.gray("Let's get you up and running"));
20
20
 
21
21
  const config = loadConfig();
22
+ const home = getHome();
22
23
  ensureHome();
23
24
 
24
25
  // ═══ Step 1: AI Provider ═══
@@ -143,15 +144,15 @@ export async function setup() {
143
144
  });
144
145
  if (p.isCancel(tone)) return p.cancel('Setup cancelled');
145
146
 
147
+ // Create agent
146
148
  const agentId = crypto.randomUUID().slice(0, 8);
147
- const agentDir = join(getHome(), 'agents', agentId);
149
+ const agentDir = join(home, 'agents', agentId);
148
150
  mkdirSync(join(agentDir, 'memory'), { recursive: true });
149
- mkdirSync(join(agentDir, 'knowledge'), { recursive: true });
150
151
 
151
152
  const langText = { bilingual: 'Bilingual — match the customer language', en: 'English', ar: 'Arabic', fr: 'French', tr: 'Turkish', ur: 'Urdu' };
152
153
  const toneText = { 30: 'Formal and polished.', 50: 'Professional but warm.', 80: 'Casual and friendly.', 90: 'Fun and sarcastic.' };
153
154
 
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';
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';
155
156
 
156
157
  writeFileSync(join(agentDir, 'SOUL.md'), soul);
157
158
  writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentName + '\n- Name: ' + agentName + '\n- Language: ' + language + '\n- Emoji: 🦑\n');
@@ -174,18 +175,38 @@ export async function setup() {
174
175
  });
175
176
  if (p.isCancel(connectWA)) return p.cancel('Setup cancelled');
176
177
 
178
+ let waConnected = false;
179
+
177
180
  if (connectWA === 'pair') {
178
181
  const waPhone = await p.text({
179
182
  message: 'WhatsApp phone number (with country code):',
180
183
  placeholder: '+966 5XX XXX XXXX',
181
184
  validate: (v) => v.replace(/[^0-9+]/g, '').length < 8 ? 'Enter a valid phone number' : undefined,
182
185
  });
183
- if (!p.isCancel(waPhone)) {
184
- manifest.whatsappNumber = waPhone.replace(/[^0-9]/g, '');
185
- manifest.whatsappLoginMethod = 'pair';
186
+ if (p.isCancel(waPhone)) return p.cancel('Setup cancelled');
187
+
188
+ const cleanPhone = waPhone.replace(/[^0-9]/g, '');
189
+ manifest.whatsappNumber = cleanPhone;
190
+
191
+ // Actually connect WhatsApp NOW
192
+ console.log();
193
+ try {
194
+ const { connectWhatsApp } = await import('./wa-login.js');
195
+ waConnected = await connectWhatsApp(home, agentId, 'pair', cleanPhone);
196
+ } catch (err) {
197
+ console.log(chalk.yellow(' WhatsApp connection failed: ' + err.message));
198
+ console.log(chalk.gray(' You can connect later: squidclaw channels login whatsapp'));
186
199
  }
187
200
  } else if (connectWA === 'qr') {
188
- manifest.whatsappLoginMethod = 'qr';
201
+ // Actually show QR code NOW
202
+ console.log();
203
+ try {
204
+ const { connectWhatsApp } = await import('./wa-login.js');
205
+ waConnected = await connectWhatsApp(home, agentId, 'qr', null);
206
+ } catch (err) {
207
+ console.log(chalk.yellow(' WhatsApp connection failed: ' + err.message));
208
+ console.log(chalk.gray(' You can connect later: squidclaw channels login whatsapp'));
209
+ }
189
210
  }
190
211
 
191
212
  if (connectWA !== 'skip') {
@@ -219,16 +240,9 @@ export async function setup() {
219
240
  writeFileSync(join(agentDir, 'agent.json'), JSON.stringify(manifest, null, 2));
220
241
 
221
242
  // ═══ Summary ═══
222
- const waStatus = connectWA === 'pair' ? 'Pairing code (on start)' : connectWA === 'qr' ? 'QR code (on start)' : 'skipped';
243
+ const waStatus = waConnected ? chalk.green('connected ') : connectWA !== 'skip' ? chalk.yellow('will connect on start') : 'skipped';
223
244
  const tgStatus = connectTG ? 'bot ready' : 'skipped';
224
245
 
225
- // ═══ Offer to Hatch ═══
226
- const hatchNow = await p.confirm({
227
- message: '🥚 Ready to hatch ' + agentName + '? (wake up and meet your agent)',
228
- initialValue: true,
229
- });
230
- if (p.isCancel(hatchNow)) return p.cancel('Setup cancelled');
231
-
232
246
  console.log(chalk.green('\n ════════════════════════════════════'));
233
247
  console.log(chalk.green(' ✅ Setup Complete!'));
234
248
  console.log(chalk.green(' ════════════════════════════════════\n'));
@@ -237,25 +251,23 @@ export async function setup() {
237
251
  console.log(' ' + chalk.bold('Language:') + ' ' + (langText[language] || language));
238
252
  console.log(' ' + chalk.bold('WhatsApp:') + ' ' + waStatus);
239
253
  console.log(' ' + chalk.bold('Telegram:') + ' ' + tgStatus);
240
- console.log('\n ────────────────────────────────────');
241
- console.log('\n ' + chalk.bold('Now just run:\n'));
242
- console.log(' ' + chalk.cyan('squidclaw start'));
243
- if (connectWA === 'pair') {
244
- console.log('\n A pairing code will appear. Enter it in:');
245
- console.log(' WhatsApp → ⚙️ → Linked Devices → Link → Link with phone number');
246
- } else if (connectWA === 'qr') {
247
- console.log('\n A QR code will appear. Scan it in:');
248
- console.log(' WhatsApp → ⚙️ → Linked Devices → Link a Device');
249
- }
250
- console.log('\n ' + chalk.bold('Other commands:\n'));
251
- console.log(' ' + chalk.cyan('squidclaw status') + ' — check what\'s running');
252
- console.log(' ' + chalk.cyan('squidclaw tui') + ' — chat in terminal');
253
- console.log(' ' + chalk.cyan('squidclaw agent chat ' + agentId) + ' — test your agent');
254
- console.log(' ' + chalk.cyan('squidclaw help') + ' — all commands');
255
- console.log('\n ════════════════════════════════════\n');
254
+
255
+ // ═══ Hatch TUI ═══
256
+ console.log();
257
+ const hatchNow = await p.confirm({
258
+ message: '🥚 Ready to hatch ' + agentName + '? (chat with your newborn agent)',
259
+ initialValue: true,
260
+ });
261
+ if (p.isCancel(hatchNow)) return p.cancel('Setup cancelled');
256
262
 
257
263
  if (hatchNow) {
258
264
  const { hatchTUI } = await import('./hatch-tui.js');
259
265
  await hatchTUI(agentId);
266
+ } else {
267
+ console.log('\n ' + chalk.bold('Start later:\n'));
268
+ console.log(' ' + chalk.cyan('squidclaw start'));
269
+ console.log(' ' + chalk.cyan('squidclaw tui'));
270
+ console.log(' ' + chalk.cyan('squidclaw wake'));
271
+ console.log();
260
272
  }
261
273
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * 🦑 WhatsApp Login — inline during setup
3
+ * Shows QR code or pairing code right in the terminal
4
+ */
5
+
6
+ import { makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } from '@whiskeysockets/baileys';
7
+ import qrcode from 'qrcode-terminal';
8
+ import chalk from 'chalk';
9
+ import { mkdirSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ export async function connectWhatsApp(home, agentId, method, phoneNumber) {
13
+ const authDir = join(home, 'channels', 'whatsapp', agentId);
14
+ mkdirSync(authDir, { recursive: true });
15
+
16
+ const { state, saveCreds } = await useMultiFileAuthState(authDir);
17
+ const { version } = await fetchLatestBaileysVersion();
18
+
19
+ return new Promise((resolve, reject) => {
20
+ let resolved = false;
21
+ let timeout = null;
22
+
23
+ const sock = makeWASocket({
24
+ version,
25
+ auth: state,
26
+ printQRInTerminal: false,
27
+ browser: ['Squidclaw', 'Chrome', '126.0'],
28
+ generateHighQualityLinkPreview: false,
29
+ syncFullHistory: false,
30
+ });
31
+
32
+ // QR code handler
33
+ sock.ev.on('connection.update', async (update) => {
34
+ const { connection, lastDisconnect, qr } = update;
35
+
36
+ if (qr && method === 'qr') {
37
+ console.log();
38
+ console.log(chalk.cyan(' 📱 Scan this QR code with WhatsApp:'));
39
+ console.log(chalk.gray(' WhatsApp → Settings → Linked Devices → Link a Device'));
40
+ console.log();
41
+ qrcode.generate(qr, { small: true }, (code) => {
42
+ // Indent each line
43
+ const lines = code.split('\n');
44
+ for (const line of lines) {
45
+ console.log(' ' + line);
46
+ }
47
+ });
48
+ console.log();
49
+ console.log(chalk.yellow(' Waiting for scan...'));
50
+ }
51
+
52
+ if (qr && method === 'pair' && phoneNumber) {
53
+ try {
54
+ const code = await sock.requestPairingCode(phoneNumber);
55
+ console.log();
56
+ console.log(chalk.cyan(' ════════════════════════════════════'));
57
+ console.log(chalk.cyan(' 📱 Your pairing code:'));
58
+ console.log();
59
+ console.log(chalk.bold.green(' ' + code.match(/.{1,4}/g).join(' - ')));
60
+ console.log();
61
+ console.log(chalk.gray(' Enter this in WhatsApp:'));
62
+ console.log(chalk.gray(' Settings → Linked Devices → Link a Device'));
63
+ console.log(chalk.gray(' → Link with phone number'));
64
+ console.log(chalk.cyan(' ════════════════════════════════════'));
65
+ console.log();
66
+ console.log(chalk.yellow(' Waiting for confirmation...'));
67
+ } catch (err) {
68
+ console.log(chalk.red(' Failed to get pairing code: ' + err.message));
69
+ }
70
+ }
71
+
72
+ if (connection === 'open') {
73
+ console.log();
74
+ console.log(chalk.green(' ✅ WhatsApp connected!'));
75
+ console.log();
76
+ resolved = true;
77
+ clearTimeout(timeout);
78
+ await saveCreds();
79
+ // Don't close — let the engine take over
80
+ try { sock.end(); } catch {}
81
+ resolve(true);
82
+ }
83
+
84
+ if (connection === 'close') {
85
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
86
+ if (statusCode === DisconnectReason.loggedOut) {
87
+ console.log(chalk.red(' ✗ Logged out. Try again.'));
88
+ if (!resolved) resolve(false);
89
+ } else if (statusCode === 515) {
90
+ // Restart requested after pairing
91
+ console.log(chalk.green(' ✅ WhatsApp paired! Will connect on engine start.'));
92
+ await saveCreds();
93
+ if (!resolved) { resolved = true; resolve(true); }
94
+ }
95
+ }
96
+ });
97
+
98
+ sock.ev.on('creds.update', saveCreds);
99
+
100
+ // Timeout after 2 minutes
101
+ timeout = setTimeout(() => {
102
+ if (!resolved) {
103
+ console.log(chalk.yellow(' ⏰ Timed out. You can connect later with: squidclaw channels login whatsapp'));
104
+ try { sock.end(); } catch {}
105
+ resolve(false);
106
+ }
107
+ }, 120000);
108
+ });
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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": {