squidclaw 0.5.1 → 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.
@@ -5,7 +5,7 @@
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
 
@@ -18,7 +18,7 @@ export async function hatchTUI(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
 
@@ -39,10 +39,9 @@ export async function hatchTUI(agentId) {
39
39
  console.log();
40
40
 
41
41
  // ═══ Start engine silently ═══
42
- let engine = null;
43
42
  const port = config.engine?.port || 9500;
43
+ let engine = null;
44
44
 
45
- // Check if engine already running
46
45
  let engineReady = false;
47
46
  try {
48
47
  const res = await fetch('http://127.0.0.1:' + port + '/health');
@@ -54,7 +53,6 @@ export async function hatchTUI(agentId) {
54
53
  try {
55
54
  const { SquidclawEngine } = await import('../engine.js');
56
55
  engine = new SquidclawEngine({ port });
57
- // Suppress output
58
56
  const origLog = console.log;
59
57
  const origErr = console.error;
60
58
  console.log = () => {};
@@ -64,7 +62,8 @@ export async function hatchTUI(agentId) {
64
62
  console.error = origErr;
65
63
  process.stdout.write(chalk.green(' ready!\n\n'));
66
64
  } catch (err) {
67
- console.log(chalk.red(' failed: ' + err.message));
65
+ process.stdout.write(chalk.red(' failed\n'));
66
+ console.log(chalk.red(' ' + err.message));
68
67
  console.log(chalk.gray(' Run "squidclaw start" first, then "squidclaw wake"'));
69
68
  return;
70
69
  }
@@ -72,10 +71,9 @@ export async function hatchTUI(agentId) {
72
71
 
73
72
  await DELAY(500);
74
73
 
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
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.'
79
77
  );
80
78
 
81
79
  await typeMessage(manifest.name, firstMessage);
@@ -84,38 +82,36 @@ export async function hatchTUI(agentId) {
84
82
  const rl = createInterface({ input: process.stdin, output: process.stdout });
85
83
  let turnCount = 0;
86
84
  let agentFinalName = manifest.name;
85
+ const chatHistory = [
86
+ { role: 'assistant', content: firstMessage }
87
+ ];
87
88
 
88
- const askUser = () => new Promise(resolve => {
89
- rl.question(chalk.green('\n You: '), (answer) => resolve(answer));
90
- });
91
-
92
- console.log(chalk.gray('\n (Type /done when you\'re ready to finish hatching)\n'));
89
+ console.log(chalk.gray('\n (Type /done when ready to finish hatching)\n'));
93
90
 
94
91
  while (true) {
95
- const userInput = await askUser();
92
+ const userInput = await new Promise(resolve => {
93
+ rl.question(chalk.green('\n You: '), resolve);
94
+ });
96
95
  if (!userInput || !userInput.trim()) continue;
97
96
  if (userInput.trim().toLowerCase() === '/done' || userInput.trim().toLowerCase() === '/exit') break;
98
97
 
99
98
  turnCount++;
99
+ chatHistory.push({ role: 'user', content: userInput });
100
100
 
101
101
  let hint = '';
102
- if (turnCount <= 2) {
103
- hint = 'You are still figuring out who you are. Ask about your personality, your name, what you do. Be curious and playful.';
104
- } else if (turnCount <= 4) {
105
- hint = 'You are forming your identity now. Confirm what you have learned. Show your personality emerging.';
106
- } else {
107
- hint = 'You know who you are. Act like it. If conversation feels complete, suggest typing /done.';
108
- }
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.';
109
105
 
110
- const response = await callAgent(port, agentId, userInput, false, hint);
106
+ const response = await callAgentDirect(config, manifest, userInput, chatHistory, hint);
107
+ chatHistory.push({ role: 'assistant', content: response });
111
108
  await typeMessage(agentFinalName, response);
112
109
 
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);
110
+ const nameMatch = userInput.match(/call you (\w+)/i) || userInput.match(/name you (\w+)/i);
115
111
  if (nameMatch) agentFinalName = nameMatch[1];
116
112
  }
117
113
 
118
- // ═══ Save identity ═══
114
+ // ═══ Save ═══
119
115
  console.log();
120
116
  console.log(chalk.cyan(' ════════════════════════════════════'));
121
117
  console.log(chalk.cyan(' 🦑 ' + agentFinalName + ' is hatched!'));
@@ -127,7 +123,6 @@ export async function hatchTUI(agentId) {
127
123
  manifest.hatchedAt = new Date().toISOString();
128
124
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
129
125
 
130
- // Update SOUL.md
131
126
  const soulPath = join(agentDir, 'SOUL.md');
132
127
  if (existsSync(soulPath)) {
133
128
  let soul = readFileSync(soulPath, 'utf8');
@@ -137,36 +132,117 @@ export async function hatchTUI(agentId) {
137
132
 
138
133
  writeFileSync(join(agentDir, 'IDENTITY.md'), '# ' + agentFinalName + '\n- Name: ' + agentFinalName + '\n- Hatched: ' + new Date().toISOString() + '\n- Emoji: 🦑\n');
139
134
 
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');
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');
143
138
  console.log(' ' + chalk.cyan('squidclaw tui') + ' — chat in terminal');
144
- console.log(' ' + chalk.cyan('squidclaw status') + ' — check status');
145
139
  console.log();
146
140
 
147
141
  rl.close();
148
- if (engine) {
149
- try { await engine.stop(); } catch {}
150
- }
142
+ if (engine) { try { await engine.stop(); } catch {} }
151
143
  process.exit(0);
152
144
  }
153
145
 
154
- async function callAgent(port, agentId, message, isFirstMessage, hint) {
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
+
155
175
  try {
156
- const body = { message, contactId: 'hatch-session' };
157
- if (hint) body.systemHint = hint;
158
- if (isFirstMessage) body.systemOverride = message;
159
-
160
- const res = await fetch('http://127.0.0.1:' + port + '/api/agents/' + agentId + '/chat', {
161
- method: 'POST',
162
- headers: { 'content-type': 'application/json' },
163
- body: JSON.stringify(body),
164
- });
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 });
165
229
  const data = await res.json();
166
- const msgs = data.messages || [];
167
- return msgs.join(' ') || '...';
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
+ }
168
244
  } catch (err) {
169
- return '(oops, something went wrong: ' + err.message + ')';
245
+ return '(connection error: ' + err.message + ')';
170
246
  }
171
247
  }
172
248
 
@@ -3,7 +3,7 @@
3
3
  * Shows QR code or pairing code right in the terminal
4
4
  */
5
5
 
6
- import { default as makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } from 'baileys';
6
+ import { makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } from '@whiskeysockets/baileys';
7
7
  import qrcode from 'qrcode-terminal';
8
8
  import chalk from 'chalk';
9
9
  import { mkdirSync } from 'fs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "0.5.1",
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": {