squidclaw 0.4.4 → 0.5.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/install.sh ADDED
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # 🦑 Squidclaw — One-line installer
3
+ # Usage: curl -fsSL https://squidclaw.dev/install.sh | bash
4
+
5
+ set -e
6
+
7
+ BOLD='\033[1m'
8
+ CYAN='\033[36m'
9
+ GREEN='\033[32m'
10
+ YELLOW='\033[33m'
11
+ RED='\033[31m'
12
+ RESET='\033[0m'
13
+
14
+ echo ""
15
+ echo -e "${CYAN} ╔══════════════════════════════════════╗${RESET}"
16
+ echo -e "${CYAN} ║ 🦑 Squidclaw Installer ║${RESET}"
17
+ echo -e "${CYAN} ╚══════════════════════════════════════╝${RESET}"
18
+ echo ""
19
+
20
+ # Check Node.js
21
+ if ! command -v node &> /dev/null; then
22
+ echo -e "${RED} ✗ Node.js not found${RESET}"
23
+ echo -e " Install it first: ${CYAN}https://nodejs.org${RESET}"
24
+ echo ""
25
+ exit 1
26
+ fi
27
+
28
+ NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
29
+ if [ "$NODE_VERSION" -lt 20 ]; then
30
+ echo -e "${RED} ✗ Node.js $NODE_VERSION found, need 20+${RESET}"
31
+ echo -e " Update: ${CYAN}https://nodejs.org${RESET}"
32
+ echo ""
33
+ exit 1
34
+ fi
35
+ echo -e "${GREEN} ✓ Node.js $(node -v)${RESET}"
36
+
37
+ # Check npm
38
+ if ! command -v npm &> /dev/null; then
39
+ echo -e "${RED} ✗ npm not found${RESET}"
40
+ exit 1
41
+ fi
42
+ echo -e "${GREEN} ✓ npm $(npm -v)${RESET}"
43
+
44
+ # Install
45
+ echo ""
46
+ echo -e "${YELLOW} Installing Squidclaw...${RESET}"
47
+ npm i -g squidclaw@latest --silent 2>/dev/null
48
+
49
+ if ! command -v squidclaw &> /dev/null; then
50
+ echo -e "${RED} ✗ Installation failed${RESET}"
51
+ echo -e " Try manually: ${CYAN}npm i -g squidclaw${RESET}"
52
+ exit 1
53
+ fi
54
+
55
+ VERSION=$(squidclaw --version 2>/dev/null || echo "unknown")
56
+ echo -e "${GREEN} ✓ Squidclaw v${VERSION} installed${RESET}"
57
+
58
+ echo ""
59
+ echo -e "${GREEN} ════════════════════════════════════${RESET}"
60
+ echo -e "${GREEN} ✅ Installation complete!${RESET}"
61
+ echo -e "${GREEN} ════════════════════════════════════${RESET}"
62
+ echo ""
63
+ echo -e " ${BOLD}Now run:${RESET}"
64
+ echo ""
65
+ echo -e " ${CYAN}squidclaw hatch${RESET}"
66
+ echo ""
67
+ echo -e " This will set up your AI agent, connect"
68
+ echo -e " WhatsApp/Telegram, and bring it to life 🥚→🦑"
69
+ echo ""
70
+
71
+ # Auto-launch hatch
72
+ read -p " Start hatching now? [Y/n] " -n 1 -r
73
+ echo ""
74
+ if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
75
+ squidclaw hatch
76
+ fi
@@ -1,6 +1,6 @@
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';
@@ -13,8 +13,8 @@ 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)) {
@@ -28,166 +28,134 @@ 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
+ let engine = null;
43
+ const port = config.engine?.port || 9500;
60
44
 
61
- // Start engine inline for hatching (don't require separate squidclaw start)
62
- let engineProcess = null;
45
+ // Check if engine already running
63
46
  let engineReady = false;
64
-
65
- // Check if engine is already running
66
47
  try {
67
48
  const res = await fetch('http://127.0.0.1:' + port + '/health');
68
49
  if (res.ok) engineReady = true;
69
50
  } catch {}
70
51
 
71
52
  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 = () => {};
53
+ process.stdout.write(chalk.gray(' Starting engine...'));
79
54
  try {
55
+ const { SquidclawEngine } = await import('../engine.js');
56
+ engine = new SquidclawEngine({ port });
57
+ // Suppress output
58
+ const origLog = console.log;
59
+ const origErr = console.error;
60
+ console.log = () => {};
61
+ console.error = () => {};
80
62
  await engine.start();
81
- } catch (err) {
82
63
  console.log = origLog;
83
- console.log(chalk.red(' Failed to start engine: ' + err.message));
64
+ console.error = origErr;
65
+ process.stdout.write(chalk.green(' ready!\n\n'));
66
+ } catch (err) {
67
+ console.log(chalk.red(' failed: ' + err.message));
68
+ console.log(chalk.gray(' Run "squidclaw start" first, then "squidclaw wake"'));
84
69
  return;
85
70
  }
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
71
  }
96
72
 
97
- // Chat loop
98
- const rl = createInterface({ input: process.stdin, output: process.stdout });
73
+ await DELAY(500);
99
74
 
100
- const history = [];
101
- let userName = '';
102
- let agentPersonality = '';
103
- let agentFinalName = manifest.name;
104
- let turnCount = 0;
75
+ // ═══ First message from agent ═══
76
+ const firstMessage = await callAgent(port, agentId,
77
+ 'You just came to life for the very first time. You are "' + manifest.name + '". Express excitement about existing — be curious, a little confused. Ask: who are you? who am I? Keep it to 2-3 SHORT sentences. Be playful. No ---SPLIT--- markers.',
78
+ true
79
+ );
105
80
 
106
- // First message from agent (the wake-up)
107
- await typeMessage(manifest.name, await chat(port, agentId, firstPrompt, 'hatch-session', true));
81
+ await typeMessage(manifest.name, firstMessage);
108
82
 
109
- const askUser = (prompt) => new Promise(resolve => {
110
- process.stdout.write(chalk.green('\n You: '));
111
- rl.question('', (answer) => resolve(answer));
83
+ // ═══ Chat loop ═══
84
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
85
+ let turnCount = 0;
86
+ let agentFinalName = manifest.name;
87
+
88
+ const askUser = () => new Promise(resolve => {
89
+ rl.question(chalk.green('\n You: '), (answer) => resolve(answer));
112
90
  });
113
91
 
114
- while (true) {
115
- const userInput = await askUser('');
116
- if (!userInput || userInput.trim() === '') continue;
92
+ console.log(chalk.gray('\n (Type /done when you\'re ready to finish hatching)\n'));
117
93
 
118
- if (userInput.toLowerCase() === '/done' || userInput.toLowerCase() === '/exit') break;
94
+ while (true) {
95
+ const userInput = await askUser();
96
+ if (!userInput || !userInput.trim()) continue;
97
+ if (userInput.trim().toLowerCase() === '/done' || userInput.trim().toLowerCase() === '/exit') break;
119
98
 
120
99
  turnCount++;
121
100
 
122
- // Build context for the hatching conversation
123
- let systemHint = '';
101
+ let hint = '';
124
102
  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.';
103
+ hint = 'You are still figuring out who you are. Ask about your personality, your name, what you do. Be curious and playful.';
126
104
  } 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 🦑"';
105
+ hint = 'You are forming your identity now. Confirm what you have learned. Show your personality emerging.';
130
106
  } 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.';
107
+ hint = 'You know who you are. Act like it. If conversation feels complete, suggest typing /done.';
132
108
  }
133
109
 
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);
110
+ const response = await callAgent(port, agentId, userInput, false, hint);
139
111
  await typeMessage(agentFinalName, response);
140
112
 
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
- }
113
+ // Check if user renamed agent
114
+ const nameMatch = userInput.match(/call you (\w+)/i) || userInput.match(/your name.+?(\w+)$/i) || userInput.match(/name you (\w+)/i);
115
+ if (nameMatch) agentFinalName = nameMatch[1];
147
116
  }
148
117
 
149
- // ═══ Save the identity ═══
118
+ // ═══ Save identity ═══
150
119
  console.log();
151
120
  console.log(chalk.cyan(' ════════════════════════════════════'));
152
- console.log(chalk.cyan(' 🦑 Hatching complete!'));
121
+ console.log(chalk.cyan(' 🦑 ' + agentFinalName + ' is hatched!'));
153
122
  console.log(chalk.cyan(' ════════════════════════════════════'));
154
123
  console.log();
155
124
 
156
- // Update manifest with final name
157
125
  manifest.name = agentFinalName;
158
126
  manifest.hatched = true;
159
127
  manifest.hatchedAt = new Date().toISOString();
160
128
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
161
129
 
162
- // Update SOUL.md first line with new name
130
+ // Update SOUL.md
163
131
  const soulPath = join(agentDir, 'SOUL.md');
164
132
  if (existsSync(soulPath)) {
165
133
  let soul = readFileSync(soulPath, 'utf8');
166
134
  soul = soul.replace(/^# .+/m, '# ' + agentFinalName);
167
- soul = soul.replace(/I'm .+?\./m, "I'm " + agentFinalName + '.');
168
135
  writeFileSync(soulPath, soul);
169
136
  }
170
137
 
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');
138
+ writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentFinalName + '\n- Name: ' + agentFinalName + '\n- Hatched: ' + new Date().toISOString() + '\n- Emoji: 🦑\n');
174
139
 
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'));
140
+ console.log(chalk.green(' ✅ ' + agentFinalName + ' is ready!\n'));
141
+ console.log(' ' + chalk.bold('Commands:\n'));
142
+ console.log(' ' + chalk.cyan('squidclaw start') + ' run the engine');
143
+ console.log(' ' + chalk.cyan('squidclaw tui') + ' chat in terminal');
144
+ console.log(' ' + chalk.cyan('squidclaw status') + ' check status');
180
145
  console.log();
181
146
 
182
147
  rl.close();
148
+ if (engine) {
149
+ try { await engine.stop(); } catch {}
150
+ }
183
151
  process.exit(0);
184
152
  }
185
153
 
186
- async function chat(port, agentId, message, contactId, isFirst, systemHint) {
154
+ async function callAgent(port, agentId, message, isFirstMessage, hint) {
187
155
  try {
188
- const body = { message, contactId };
189
- if (isFirst) body.systemOverride = message;
190
- if (systemHint) body.systemHint = systemHint;
156
+ const body = { message, contactId: 'hatch-session' };
157
+ if (hint) body.systemHint = hint;
158
+ if (isFirstMessage) body.systemOverride = message;
191
159
 
192
160
  const res = await fetch('http://127.0.0.1:' + port + '/api/agents/' + agentId + '/chat', {
193
161
  method: 'POST',
@@ -195,19 +163,19 @@ async function chat(port, agentId, message, contactId, isFirst, systemHint) {
195
163
  body: JSON.stringify(body),
196
164
  });
197
165
  const data = await res.json();
198
- return (data.messages || []).join('\n') || data.reaction || '...';
166
+ const msgs = data.messages || [];
167
+ return msgs.join(' ') || '...';
199
168
  } catch (err) {
200
- return '(error: ' + err.message + ')';
169
+ return '(oops, something went wrong: ' + err.message + ')';
201
170
  }
202
171
  }
203
172
 
204
173
  async function typeMessage(name, text) {
205
- // Simulate typing effect
206
174
  process.stdout.write(chalk.cyan('\n ' + name + ': '));
207
175
  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);
176
+ for (const word of words) {
177
+ process.stdout.write(word + ' ');
178
+ await DELAY(25 + Math.random() * 40);
211
179
  }
212
180
  console.log();
213
181
  }
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 { default as makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } from '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
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const CYAN = '\x1b[36m';
4
+ const GREEN = '\x1b[32m';
5
+ const BOLD = '\x1b[1m';
6
+ const RESET = '\x1b[0m';
7
+
8
+ console.log(`
9
+ ${CYAN} ╔══════════════════════════════════════╗
10
+ ║ 🦑 Squidclaw installed! ║
11
+ ╚══════════════════════════════════════╝${RESET}
12
+
13
+ ${BOLD}Get started:${RESET}
14
+
15
+ ${GREEN}squidclaw hatch${RESET}
16
+
17
+ This sets up your AI, creates your agent,
18
+ connects WhatsApp/Telegram, and hatches it 🥚→🦑
19
+ `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "0.4.4",
4
- "description": "🦑 AI agent platform human-like agents for WhatsApp, Telegram & more",
3
+ "version": "0.5.1",
4
+ "description": "\ud83e\udd91 AI agent platform \u2014 human-like agents for WhatsApp, Telegram & more",
5
5
  "main": "lib/engine.js",
6
6
  "bin": {
7
7
  "squidclaw": "./bin/squidclaw.js"
@@ -10,7 +10,8 @@
10
10
  "scripts": {
11
11
  "start": "node lib/engine.js",
12
12
  "test": "vitest",
13
- "dev": "node --watch lib/engine.js"
13
+ "dev": "node --watch lib/engine.js",
14
+ "postinstall": "node lib/postinstall.js"
14
15
  },
15
16
  "keywords": [
16
17
  "ai",
@@ -54,4 +55,4 @@
54
55
  "yaml": "^2.8.2",
55
56
  "zod": "^4.3.6"
56
57
  }
57
- }
58
+ }