squidclaw 0.5.0 → 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/lib/cli/hatch-tui.js +71 -103
- package/lib/cli/setup.js +46 -34
- package/lib/cli/wa-login.js +109 -0
- package/package.json +1 -1
package/lib/cli/hatch-tui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 🥚→🦑 Hatch TUI
|
|
3
|
-
*
|
|
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
|
|
17
|
-
const agentDir = join(
|
|
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(
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
98
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
73
|
+
await DELAY(500);
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
await typeMessage(manifest.name, await chat(port, agentId, firstPrompt, 'hatch-session', true));
|
|
81
|
+
await typeMessage(manifest.name, firstMessage);
|
|
108
82
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
let systemHint = '';
|
|
101
|
+
let hint = '';
|
|
124
102
|
if (turnCount <= 2) {
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
+
hint = 'You know who you are. Act like it. If conversation feels complete, suggest typing /done.';
|
|
132
108
|
}
|
|
133
109
|
|
|
134
|
-
const
|
|
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
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
118
|
+
// ═══ Save identity ═══
|
|
150
119
|
console.log();
|
|
151
120
|
console.log(chalk.cyan(' ════════════════════════════════════'));
|
|
152
|
-
console.log(chalk.cyan(' 🦑
|
|
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
|
|
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
|
-
|
|
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('
|
|
178
|
-
console.log('
|
|
179
|
-
console.log('
|
|
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
|
|
154
|
+
async function callAgent(port, agentId, message, isFirstMessage, hint) {
|
|
187
155
|
try {
|
|
188
|
-
const body = { message, contactId };
|
|
189
|
-
if (
|
|
190
|
-
if (
|
|
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
|
-
|
|
166
|
+
const msgs = data.messages || [];
|
|
167
|
+
return msgs.join(' ') || '...';
|
|
199
168
|
} catch (err) {
|
|
200
|
-
return '(
|
|
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 (
|
|
209
|
-
process.stdout.write(
|
|
210
|
-
await DELAY(
|
|
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
|
|
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(
|
|
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
|
|
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 (
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
console.log(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
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
|
+
}
|