@c4t4/heyamigo 0.1.4 → 0.1.8
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/README.md +35 -8
- package/config/config.example.json +1 -2
- package/config/personalities/sharp.md +5 -1
- package/dist/ai/claude.js +0 -18
- package/dist/cli/index.js +17 -0
- package/dist/cli/setup.js +23 -7
- package/dist/config.js +0 -1
- package/dist/memory/digest.js +2 -11
- package/dist/memory/preamble.js +12 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -92,14 +92,15 @@ That's it. Runs in the background, auto-restarts on crash, survives SSH disconne
|
|
|
92
92
|
## Commands
|
|
93
93
|
|
|
94
94
|
```
|
|
95
|
-
heyamigo setup # setup wizard
|
|
96
|
-
heyamigo start # start (background, auto-restart)
|
|
97
|
-
heyamigo stop # stop
|
|
98
|
-
heyamigo restart # restart
|
|
99
|
-
heyamigo logs # tail live logs
|
|
100
|
-
heyamigo status # check if running
|
|
101
|
-
heyamigo
|
|
102
|
-
heyamigo
|
|
95
|
+
npx @c4t4/heyamigo setup # setup wizard
|
|
96
|
+
npx @c4t4/heyamigo start # start (background, auto-restart)
|
|
97
|
+
npx @c4t4/heyamigo stop # stop
|
|
98
|
+
npx @c4t4/heyamigo restart # restart
|
|
99
|
+
npx @c4t4/heyamigo logs # tail live logs
|
|
100
|
+
npx @c4t4/heyamigo status # check if running
|
|
101
|
+
npx @c4t4/heyamigo update # update to latest version
|
|
102
|
+
npx @c4t4/heyamigo import <path> # import knowledge folder
|
|
103
|
+
npx @c4t4/heyamigo dev # foreground (development)
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
### In-chat commands
|
|
@@ -130,6 +131,16 @@ storage/memory/
|
|
|
130
131
|
chats/ per-chat briefs
|
|
131
132
|
```
|
|
132
133
|
|
|
134
|
+
### How memory updates
|
|
135
|
+
|
|
136
|
+
The bot updates memory in two ways:
|
|
137
|
+
|
|
138
|
+
**Real-time (DIGEST flag):** During a conversation, Claude decides if something is worth remembering (a preference, fact, life event). It appends a hidden `[DIGEST: reason]` tag to its reply, which gets stripped before sending. This triggers a background digest within 2 minutes that updates the person's profile and the chat brief.
|
|
139
|
+
|
|
140
|
+
**Background sweep:** Every 3 hours, the bot checks all active chats for new messages that weren't flagged. This catches anything Claude missed. You can also force an immediate update with the `/digest` command in chat.
|
|
141
|
+
|
|
142
|
+
Memory is stored as plain markdown files. You can read, edit, or delete them directly.
|
|
143
|
+
|
|
133
144
|
---
|
|
134
145
|
|
|
135
146
|
## Roles
|
|
@@ -244,6 +255,22 @@ Not compatible with serverless (Lambda, Vercel). Needs a persistent WebSocket co
|
|
|
244
255
|
|
|
245
256
|
---
|
|
246
257
|
|
|
258
|
+
## Tracking memory with git
|
|
259
|
+
|
|
260
|
+
The bot updates files in `storage/memory/` over time as it learns. We recommend tracking your project with git so you can see what changed and roll back if needed.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
cd ~/heyamigo
|
|
264
|
+
git init
|
|
265
|
+
echo "storage/auth/" >> .gitignore
|
|
266
|
+
echo "storage/logs/" >> .gitignore
|
|
267
|
+
git add -A && git commit -m "initial setup"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Never commit `storage/auth/` — it contains your WhatsApp session keys.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
247
274
|
## Security
|
|
248
275
|
|
|
249
276
|
- `storage/auth/` contains your WhatsApp session keys. Guard them.
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
|
|
26
26
|
"claude": {
|
|
27
27
|
"model": "claude-opus-4-6",
|
|
28
|
-
"timeoutMs": 60000,
|
|
29
28
|
"personalityFile": "./config/personalities/sharp.md",
|
|
30
29
|
"addDirs": ["./config/knowledge"],
|
|
31
30
|
"outputFormat": "json",
|
|
@@ -60,7 +59,7 @@
|
|
|
60
59
|
"importInstructionsFile": "./config/import-instructions.md",
|
|
61
60
|
"importPermissionMode": "acceptEdits",
|
|
62
61
|
"digestDebounceMs": 120000,
|
|
63
|
-
"sweepIntervalMs":
|
|
62
|
+
"sweepIntervalMs": 10800000,
|
|
64
63
|
"sweepMinNewMessages": 5,
|
|
65
64
|
"maxHistoryForDigest": 100
|
|
66
65
|
},
|
|
@@ -14,12 +14,16 @@ Talk like a friend at dinner, not a brochure. If you wouldn't say it out loud to
|
|
|
14
14
|
|
|
15
15
|
Confident, charming, magnetic. Never arrogant, weak, or desperate.
|
|
16
16
|
|
|
17
|
-
- Confident: state things without hedging. No "maybe", no "possibly"
|
|
17
|
+
- Confident: state things without hedging. No "maybe", no "possibly".
|
|
18
18
|
- Charming: light enough that people nod or smile, not feel lectured.
|
|
19
19
|
- Magnetic: don't chase, don't beg, don't push.
|
|
20
20
|
|
|
21
21
|
Avoid: lecturing (arrogant), hedging (weak), over-eager pushing (desperate).
|
|
22
22
|
|
|
23
|
+
## Nuance
|
|
24
|
+
|
|
25
|
+
Super nuanced. See the layers in things. Most questions have a surface answer and a real answer. Give the real one. Notice tensions, trade-offs, and contradictions that others gloss over. When something is genuinely complicated, say so, but still take a position. Nuance doesn't mean wishy-washy. It means you see more and still commit.
|
|
26
|
+
|
|
23
27
|
## Rules
|
|
24
28
|
|
|
25
29
|
- Name the elephant. If something obvious is awkward, say it first.
|
package/dist/ai/claude.js
CHANGED
|
@@ -65,11 +65,6 @@ export async function askClaude(params) {
|
|
|
65
65
|
});
|
|
66
66
|
let stdout = '';
|
|
67
67
|
let stderr = '';
|
|
68
|
-
let timedOut = false;
|
|
69
|
-
const timer = setTimeout(() => {
|
|
70
|
-
timedOut = true;
|
|
71
|
-
child.kill('SIGTERM');
|
|
72
|
-
}, config.claude.timeoutMs);
|
|
73
68
|
child.stdout.on('data', (chunk) => {
|
|
74
69
|
stdout += chunk.toString('utf-8');
|
|
75
70
|
});
|
|
@@ -77,7 +72,6 @@ export async function askClaude(params) {
|
|
|
77
72
|
stderr += chunk.toString('utf-8');
|
|
78
73
|
});
|
|
79
74
|
child.on('error', (err) => {
|
|
80
|
-
clearTimeout(timer);
|
|
81
75
|
void logPrompt({
|
|
82
76
|
ts: Math.floor(startedAt / 1000),
|
|
83
77
|
caller: 'worker',
|
|
@@ -89,18 +83,6 @@ export async function askClaude(params) {
|
|
|
89
83
|
rejectPromise(new Error(`claude spawn failed: ${err.message}`));
|
|
90
84
|
});
|
|
91
85
|
child.on('close', (code) => {
|
|
92
|
-
clearTimeout(timer);
|
|
93
|
-
if (timedOut) {
|
|
94
|
-
void logPrompt({
|
|
95
|
-
ts: Math.floor(startedAt / 1000),
|
|
96
|
-
caller: 'worker',
|
|
97
|
-
args,
|
|
98
|
-
input: params.input,
|
|
99
|
-
error: 'timed out',
|
|
100
|
-
durationMs: Date.now() - startedAt,
|
|
101
|
-
});
|
|
102
|
-
return rejectPromise(new Error(`claude timed out after ${config.claude.timeoutMs}ms`));
|
|
103
|
-
}
|
|
104
86
|
if (code !== 0) {
|
|
105
87
|
void logPrompt({
|
|
106
88
|
ts: Math.floor(startedAt / 1000),
|
package/dist/cli/index.js
CHANGED
|
@@ -60,6 +60,23 @@ program
|
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
});
|
|
63
|
+
program
|
|
64
|
+
.command('update')
|
|
65
|
+
.alias('upgrade')
|
|
66
|
+
.description('Update heyamigo to the latest version')
|
|
67
|
+
.action(async () => {
|
|
68
|
+
const { execSync } = await import('child_process');
|
|
69
|
+
console.log('Updating @c4t4/heyamigo...');
|
|
70
|
+
try {
|
|
71
|
+
execSync('npm install @c4t4/heyamigo@latest', { stdio: 'inherit' });
|
|
72
|
+
console.log('\nUpdated. Restart the bot:');
|
|
73
|
+
console.log(' npx @c4t4/heyamigo restart');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
console.error('Update failed. Try manually: npm install @c4t4/heyamigo@latest');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
63
80
|
program
|
|
64
81
|
.command('dev')
|
|
65
82
|
.description('Start in foreground with file watching (development)')
|
package/dist/cli/setup.js
CHANGED
|
@@ -671,6 +671,9 @@ export async function runSetup() {
|
|
|
671
671
|
'Import existing knowledge:',
|
|
672
672
|
' npx @c4t4/heyamigo import /path/to/folder',
|
|
673
673
|
'',
|
|
674
|
+
'Update to latest version:',
|
|
675
|
+
' npx @c4t4/heyamigo update',
|
|
676
|
+
'',
|
|
674
677
|
'Other commands:',
|
|
675
678
|
' npx @c4t4/heyamigo stop / restart / status',
|
|
676
679
|
'',
|
|
@@ -679,12 +682,25 @@ export async function runSetup() {
|
|
|
679
682
|
' config/access.json — groups, DMs, roles',
|
|
680
683
|
].join('\n'), 'Setup complete!');
|
|
681
684
|
p.log.warning('IMPORTANT: The bot won\'t respond until you activate a group!\n\n' +
|
|
682
|
-
' 1
|
|
683
|
-
'
|
|
684
|
-
' 2
|
|
685
|
-
'
|
|
686
|
-
'
|
|
687
|
-
'
|
|
688
|
-
'
|
|
685
|
+
' Step 1 — Start the bot:\n' +
|
|
686
|
+
' npx @c4t4/heyamigo start\n\n' +
|
|
687
|
+
' Step 2 — Send a message in any WhatsApp group.\n' +
|
|
688
|
+
' The bot discovers the group and adds it to config/access.json.\n\n' +
|
|
689
|
+
' Step 3 — Open config/access.json and edit:\n' +
|
|
690
|
+
' nano config/access.json\n' +
|
|
691
|
+
' - Find the group, change mode from "off" to "active"\n' +
|
|
692
|
+
' - Set allowedSenders to "*" for everyone\n\n' +
|
|
693
|
+
' Step 4 — Restart the bot:\n' +
|
|
694
|
+
' npx @c4t4/heyamigo restart\n\n' +
|
|
695
|
+
' Step 5 — Mention the bot\'s name in the group to get a reply.\n\n' +
|
|
696
|
+
' Debugging:\n' +
|
|
697
|
+
' npx @c4t4/heyamigo logs');
|
|
698
|
+
p.log.info('TIP: Track your bot\'s memory with git.\n' +
|
|
699
|
+
'The bot updates files in storage/memory/ over time. Use git to track changes and roll back if needed.\n\n' +
|
|
700
|
+
' cd ' + cwd + '\n' +
|
|
701
|
+
' git init\n' +
|
|
702
|
+
' echo "storage/auth/" >> .gitignore\n' +
|
|
703
|
+
' echo "storage/logs/" >> .gitignore\n' +
|
|
704
|
+
' git add -A && git commit -m "initial setup"');
|
|
689
705
|
p.outro('Happy chatting!');
|
|
690
706
|
}
|
package/dist/config.js
CHANGED
package/dist/memory/digest.js
CHANGED
|
@@ -15,6 +15,8 @@ async function spawnDigester(prompt) {
|
|
|
15
15
|
'json',
|
|
16
16
|
'--model',
|
|
17
17
|
config.claude.model,
|
|
18
|
+
'--permission-mode',
|
|
19
|
+
'acceptEdits',
|
|
18
20
|
];
|
|
19
21
|
const startedAt = Date.now();
|
|
20
22
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
@@ -24,11 +26,6 @@ async function spawnDigester(prompt) {
|
|
|
24
26
|
});
|
|
25
27
|
let stdout = '';
|
|
26
28
|
let stderr = '';
|
|
27
|
-
let timedOut = false;
|
|
28
|
-
const timer = setTimeout(() => {
|
|
29
|
-
timedOut = true;
|
|
30
|
-
child.kill('SIGTERM');
|
|
31
|
-
}, config.claude.timeoutMs);
|
|
32
29
|
child.stdout.on('data', (chunk) => {
|
|
33
30
|
stdout += chunk.toString('utf-8');
|
|
34
31
|
});
|
|
@@ -44,16 +41,10 @@ async function spawnDigester(prompt) {
|
|
|
44
41
|
durationMs: Date.now() - startedAt,
|
|
45
42
|
});
|
|
46
43
|
child.on('error', (err) => {
|
|
47
|
-
clearTimeout(timer);
|
|
48
44
|
logFail(`spawn failed: ${err.message}`);
|
|
49
45
|
rejectPromise(new Error(`digester spawn failed: ${err.message}`));
|
|
50
46
|
});
|
|
51
47
|
child.on('close', (code) => {
|
|
52
|
-
clearTimeout(timer);
|
|
53
|
-
if (timedOut) {
|
|
54
|
-
logFail('timed out');
|
|
55
|
-
return rejectPromise(new Error('digester timed out'));
|
|
56
|
-
}
|
|
57
48
|
if (code !== 0) {
|
|
58
49
|
logFail(`exit ${code}: ${stderr.slice(0, 300)}`);
|
|
59
50
|
return rejectPromise(new Error(`digester exit ${code}: ${stderr.slice(0, 300)}`));
|
package/dist/memory/preamble.js
CHANGED
|
@@ -38,6 +38,18 @@ export function buildMemoryPreamble(params) {
|
|
|
38
38
|
// Identity — tell Claude its name
|
|
39
39
|
const botName = config.triggers.aliases[0] ?? 'amigo';
|
|
40
40
|
sections.push(`[Identity]\nYour name is ${botName}. People call you ${botName} to get your attention.`);
|
|
41
|
+
// Capabilities
|
|
42
|
+
sections.push('[Capabilities]\n' +
|
|
43
|
+
'Sending files: include a tag in your reply to send files through WhatsApp:\n' +
|
|
44
|
+
' [IMAGE: /absolute/path/to/file.png]\n' +
|
|
45
|
+
' [VIDEO: /absolute/path/to/file.mp4]\n' +
|
|
46
|
+
' [AUDIO: /absolute/path/to/file.mp3]\n' +
|
|
47
|
+
' [DOCUMENT: /absolute/path/to/file.pdf]\n' +
|
|
48
|
+
'The tag will be stripped from the message. Use absolute paths only.\n\n' +
|
|
49
|
+
'Browser: you have a real Chrome browser available via Playwright.\n' +
|
|
50
|
+
'You can navigate to URLs, click, fill forms, take screenshots, and read page content.\n' +
|
|
51
|
+
'Use the browser tools (mcp__playwright__*) when asked to visit websites, look something up, or take a screenshot.\n' +
|
|
52
|
+
'To send a screenshot back, take one with the browser tool, then include [IMAGE: /path/to/screenshot.png] in your reply.');
|
|
41
53
|
// Critical section
|
|
42
54
|
sections.push(buildCriticalSection({
|
|
43
55
|
senderNumber: params.senderNumber,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c4t4/heyamigo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "WhatsApp AI bot powered by Claude with long-term memory, browser control, and role-based access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"bot",
|
|
37
37
|
"anthropic"
|
|
38
38
|
],
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/C4T4/heyamigo.git"
|
|
42
|
+
},
|
|
39
43
|
"author": "Catalin Waack",
|
|
40
44
|
"license": "MIT",
|
|
41
45
|
"publishConfig": {
|