squidclaw 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/squidclaw.js CHANGED
@@ -500,9 +500,81 @@ if (process.argv.length <= 2) {
500
500
  process.exit(0);
501
501
  }
502
502
 
503
+ program
504
+ .command('wake')
505
+ .description('🦑 Wake up an agent (first-time hatching conversation)')
506
+ .argument('[agentId]', 'Agent ID')
507
+ .action(async (agentId) => {
508
+ const { hatchTUI } = await import('../lib/cli/hatch-tui.js');
509
+ if (!agentId) {
510
+ const config = (await import('../lib/core/config.js')).loadConfig();
511
+ const home = (await import('../lib/core/config.js')).getHome();
512
+ const { readdirSync, readFileSync } = await import('fs');
513
+ const { join } = await import('path');
514
+ const agentsDir = join(home, 'agents');
515
+ try {
516
+ const dirs = readdirSync(agentsDir);
517
+ for (const d of dirs) {
518
+ try {
519
+ const m = JSON.parse(readFileSync(join(agentsDir, d, 'agent.json'), 'utf8'));
520
+ if (!m.hatched) { agentId = m.id; break; }
521
+ } catch {}
522
+ }
523
+ if (!agentId && dirs.length > 0) {
524
+ const m = JSON.parse(readFileSync(join(agentsDir, dirs[0], 'agent.json'), 'utf8'));
525
+ agentId = m.id;
526
+ }
527
+ } catch {}
528
+ }
529
+ if (!agentId) { console.log('No agents found. Run: squidclaw hatch'); return; }
530
+ await hatchTUI(agentId);
531
+ });
503
532
  import { registerUninstallCommand } from '../lib/cli/uninstall-cmd.js';
533
+ program
534
+ .command('wake')
535
+ .description('🦑 Wake up an agent (first-time hatching conversation)')
536
+ .argument('[agentId]', 'Agent ID')
537
+ .action(async (agentId) => {
538
+ const { hatchTUI } = await import('../lib/cli/hatch-tui.js');
539
+ if (!agentId) {
540
+ const config = (await import('../lib/core/config.js')).loadConfig();
541
+ const home = (await import('../lib/core/config.js')).getHome();
542
+ const { readdirSync, readFileSync } = await import('fs');
543
+ const { join } = await import('path');
544
+ const agentsDir = join(home, 'agents');
545
+ try {
546
+ const dirs = readdirSync(agentsDir);
547
+ for (const d of dirs) {
548
+ try {
549
+ const m = JSON.parse(readFileSync(join(agentsDir, d, 'agent.json'), 'utf8'));
550
+ if (!m.hatched) { agentId = m.id; break; }
551
+ } catch {}
552
+ }
553
+ if (!agentId && dirs.length > 0) {
554
+ const m = JSON.parse(readFileSync(join(agentsDir, dirs[0], 'agent.json'), 'utf8'));
555
+ agentId = m.id;
556
+ }
557
+ } catch {}
558
+ }
559
+ if (!agentId) { console.log('No agents found. Run: squidclaw hatch'); return; }
560
+ await hatchTUI(agentId);
561
+ });
504
562
  registerUninstallCommand(program);
505
- program.parse();
563
+ // Auto-hatch on first run (no args)
564
+ if (process.argv.length <= 2) {
565
+ const { existsSync } = await import("fs");
566
+ const { getHome } = await import("../lib/core/config.js");
567
+ const home = getHome();
568
+ if (!existsSync(home + "/config.json")) {
569
+ // First run — auto hatch
570
+ const { setup } = await import("../lib/cli/setup.js");
571
+ await setup();
572
+ } else {
573
+ program.outputHelp();
574
+ }
575
+ } else {
576
+ program.parse();
577
+ }
506
578
 
507
579
  // ── TUI ────────────────────────────────────────────────
508
580
  program
@@ -0,0 +1,213 @@
1
+ /**
2
+ * 🥚→🦑 Hatch TUI
3
+ * The agent wakes up for the first time and gets to know its human
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import { createInterface } from 'readline';
8
+ import { loadConfig, getHome } from '../core/config.js';
9
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ const DELAY = (ms) => new Promise(r => setTimeout(r, ms));
13
+
14
+ export async function hatchTUI(agentId) {
15
+ const config = loadConfig();
16
+ const port = config.engine?.port || 9500;
17
+ const agentDir = join(getHome(), 'agents', agentId);
18
+ const manifestPath = join(agentDir, 'agent.json');
19
+
20
+ if (!existsSync(manifestPath)) {
21
+ console.log(chalk.red('Agent not found: ' + agentId));
22
+ return;
23
+ }
24
+
25
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
26
+
27
+ // ═══ Hatching Animation ═══
28
+ console.clear();
29
+ const frames = ['🥚', '🥚 .', '🥚 . .', '🥚 . . .', '🥚💥', '🐣', '🐣 → 🦑'];
30
+ for (const frame of frames) {
31
+ process.stdout.write('\r ' + chalk.cyan(frame) + ' ');
32
+ await DELAY(500);
33
+ }
34
+ console.log('\n');
35
+
36
+ await DELAY(300);
37
+ console.log(chalk.cyan(' ════════════════════════════════════'));
38
+ console.log(chalk.cyan(' 🦑 ' + manifest.name + ' is waking up...'));
39
+ console.log(chalk.cyan(' ════════════════════════════════════'));
40
+ console.log();
41
+
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.`;
60
+
61
+ // Start engine inline for hatching (don't require separate squidclaw start)
62
+ let engineProcess = null;
63
+ let engineReady = false;
64
+
65
+ // Check if engine is already running
66
+ try {
67
+ const res = await fetch('http://127.0.0.1:' + port + '/health');
68
+ if (res.ok) engineReady = true;
69
+ } catch {}
70
+
71
+ 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 = () => {};
79
+ try {
80
+ await engine.start();
81
+ } catch (err) {
82
+ console.log = origLog;
83
+ console.log(chalk.red(' Failed to start engine: ' + err.message));
84
+ return;
85
+ }
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
+ }
96
+
97
+ // Chat loop
98
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
99
+
100
+ const history = [];
101
+ let userName = '';
102
+ let agentPersonality = '';
103
+ let agentFinalName = manifest.name;
104
+ let turnCount = 0;
105
+
106
+ // First message from agent (the wake-up)
107
+ await typeMessage(manifest.name, await chat(port, agentId, firstPrompt, 'hatch-session', true));
108
+
109
+ const askUser = (prompt) => new Promise(resolve => {
110
+ process.stdout.write(chalk.green('\n You: '));
111
+ rl.question('', (answer) => resolve(answer));
112
+ });
113
+
114
+ while (true) {
115
+ const userInput = await askUser('');
116
+ if (!userInput || userInput.trim() === '') continue;
117
+
118
+ if (userInput.toLowerCase() === '/done' || userInput.toLowerCase() === '/exit') break;
119
+
120
+ turnCount++;
121
+
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
+ }
133
+
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);
139
+ await typeMessage(agentFinalName, response);
140
+
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
+ }
147
+ }
148
+
149
+ // ═══ Save the identity ═══
150
+ console.log();
151
+ console.log(chalk.cyan(' ════════════════════════════════════'));
152
+ console.log(chalk.cyan(' 🦑 Hatching complete!'));
153
+ console.log(chalk.cyan(' ════════════════════════════════════'));
154
+ console.log();
155
+
156
+ // Update manifest with final name
157
+ manifest.name = agentFinalName;
158
+ manifest.hatched = true;
159
+ manifest.hatchedAt = new Date().toISOString();
160
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
161
+
162
+ // Update SOUL.md first line with new name
163
+ const soulPath = join(agentDir, 'SOUL.md');
164
+ if (existsSync(soulPath)) {
165
+ let soul = readFileSync(soulPath, 'utf8');
166
+ soul = soul.replace(/^# .+/m, '# ' + agentFinalName);
167
+ soul = soul.replace(/I'm .+?\./m, "I'm " + agentFinalName + '.');
168
+ writeFileSync(soulPath, soul);
169
+ }
170
+
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');
174
+
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'));
180
+ console.log();
181
+
182
+ rl.close();
183
+ process.exit(0);
184
+ }
185
+
186
+ async function chat(port, agentId, message, contactId, isFirst, systemHint) {
187
+ 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
+ });
197
+ const data = await res.json();
198
+ return (data.messages || []).join('\n') || data.reaction || '...';
199
+ } catch (err) {
200
+ return '(error: ' + err.message + ')';
201
+ }
202
+ }
203
+
204
+ async function typeMessage(name, text) {
205
+ // Simulate typing effect
206
+ process.stdout.write(chalk.cyan('\n ' + name + ': '));
207
+ 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);
211
+ }
212
+ console.log();
213
+ }
package/lib/cli/setup.js CHANGED
@@ -264,6 +264,13 @@ export async function setup() {
264
264
  const waStatus = connectWA === 'pair' ? 'Pairing code (on start)' : connectWA === 'qr' ? 'QR code (on start)' : 'skipped';
265
265
  const tgStatus = connectTG ? 'bot ready' : 'skipped';
266
266
 
267
+ // ═══ Offer to Hatch ═══
268
+ const hatchNow = await p.confirm({
269
+ message: '🥚 Ready to hatch ' + agentName + '? (wake up and meet your agent)',
270
+ initialValue: true,
271
+ });
272
+ if (p.isCancel(hatchNow)) return p.cancel('Setup cancelled');
273
+
267
274
  console.log(chalk.green('\n ════════════════════════════════════'));
268
275
  console.log(chalk.green(' ✅ Setup Complete!'));
269
276
  console.log(chalk.green(' ════════════════════════════════════\n'));
@@ -290,4 +297,9 @@ export async function setup() {
290
297
  console.log(' ' + chalk.cyan('squidclaw knowledge upload') + ' — add documents');
291
298
  console.log(' ' + chalk.cyan('squidclaw help') + ' — all commands');
292
299
  console.log('\n ════════════════════════════════════\n');
300
+
301
+ if (hatchNow) {
302
+ const { hatchTUI } = await import('./hatch-tui.js');
303
+ await hatchTUI(agentId);
304
+ }
293
305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "🦑 AI agent platform — human-like agents for WhatsApp, Telegram & more",
5
5
  "main": "lib/engine.js",
6
6
  "bin": {