squidclaw 0.3.0 → 0.4.0
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 +62 -3
- package/lib/cli/hatch-tui.js +213 -0
- package/lib/cli/setup.js +14 -2
- package/package.json +1 -1
package/bin/squidclaw.js
CHANGED
|
@@ -24,8 +24,9 @@ program
|
|
|
24
24
|
|
|
25
25
|
// ── Setup ──────────────────────────────────────────────
|
|
26
26
|
program
|
|
27
|
-
.command('
|
|
28
|
-
.
|
|
27
|
+
.command('hatch')
|
|
28
|
+
.alias('setup')
|
|
29
|
+
.description('🥚 Hatch your first Squidclaw agent')
|
|
29
30
|
.action(async () => {
|
|
30
31
|
const { setup } = await import('../lib/cli/setup.js');
|
|
31
32
|
await setup();
|
|
@@ -492,14 +493,72 @@ if (process.argv.length <= 2) {
|
|
|
492
493
|
🦑 Squidclaw v${pkg.version}
|
|
493
494
|
AI agent platform — human-like agents for WhatsApp & more
|
|
494
495
|
|
|
495
|
-
${chalk.gray('Get started:')} squidclaw
|
|
496
|
+
${chalk.gray('Get started:')} squidclaw hatch
|
|
496
497
|
${chalk.gray('Help:')} squidclaw --help
|
|
497
498
|
${chalk.gray('Docs:')} https://squidclaw.dev
|
|
498
499
|
`));
|
|
499
500
|
process.exit(0);
|
|
500
501
|
}
|
|
501
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
|
+
});
|
|
502
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
|
+
});
|
|
503
562
|
registerUninstallCommand(program);
|
|
504
563
|
program.parse();
|
|
505
564
|
|
|
@@ -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
|
@@ -13,8 +13,8 @@ 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(' ║
|
|
17
|
-
console.log(chalk.cyan(' ║
|
|
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
|
|
|
@@ -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
|
}
|