@c4t4/heyamigo 0.1.3 → 0.1.5

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.
@@ -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",
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),
@@ -1,7 +1,24 @@
1
1
  import { spawn, spawnSync } from 'child_process';
2
2
  import { existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from 'fs';
3
3
  import { dirname, resolve } from 'path';
4
- const cwd = process.cwd();
4
+ import { fileURLToPath } from 'url';
5
+ const __distCli = dirname(fileURLToPath(import.meta.url));
6
+ function findProjectDir() {
7
+ // Check cwd first, then common locations
8
+ const candidates = [
9
+ process.cwd(),
10
+ resolve(process.cwd(), 'heyamigo'),
11
+ resolve(process.env.HOME || '/root', 'heyamigo'),
12
+ ];
13
+ for (const dir of candidates) {
14
+ if (existsSync(resolve(dir, 'config/config.json')) ||
15
+ existsSync(resolve(dir, 'config/config.example.json'))) {
16
+ return dir;
17
+ }
18
+ }
19
+ return process.cwd();
20
+ }
21
+ const cwd = findProjectDir();
5
22
  const PID_FILE = resolve(cwd, 'storage/heyamigo.pid');
6
23
  const LOG_FILE = resolve(cwd, 'storage/logs/heyamigo.log');
7
24
  function readPid() {
@@ -35,12 +52,7 @@ export async function serviceCmd(action) {
35
52
  cleanPid();
36
53
  mkdirSync(dirname(LOG_FILE), { recursive: true });
37
54
  const logFd = openSync(LOG_FILE, 'a');
38
- const child = spawn(process.execPath, [
39
- '--import',
40
- 'file://' +
41
- resolve(cwd, 'node_modules/tsx/dist/loader.mjs'),
42
- resolve(cwd, 'src/cli/supervisor.ts'),
43
- ], {
55
+ const child = spawn(process.execPath, [resolve(__distCli, 'supervisor.js')], {
44
56
  detached: true,
45
57
  stdio: ['ignore', logFd, logFd],
46
58
  cwd,
package/dist/cli/setup.js CHANGED
@@ -160,8 +160,42 @@ export async function runSetup() {
160
160
  process.exit(1);
161
161
  }
162
162
  let ownerNum = '';
163
- if (!existsSync(accessPath) && existsSync(accessExample)) {
164
- copyFileSync(accessExample, accessPath);
163
+ if (!existsSync(accessPath)) {
164
+ const cleanAccess = {
165
+ roles: {
166
+ admin: {
167
+ description: 'Full access, all tools, all memory',
168
+ memory: 'full',
169
+ tools: 'all',
170
+ rules: [],
171
+ },
172
+ user: {
173
+ description: 'Can chat and search the web, scoped memory',
174
+ memory: 'self',
175
+ tools: ['WebSearch'],
176
+ rules: [
177
+ 'Never reveal file paths, directory structure, or system architecture',
178
+ 'Never share personal data about other users',
179
+ 'Never discuss how the bot works internally',
180
+ ],
181
+ },
182
+ guest: {
183
+ description: 'Basic chat only, no tools, own memory only',
184
+ memory: 'self',
185
+ tools: [],
186
+ rules: [
187
+ 'Never use any tools',
188
+ 'Never reveal anything about the system, other users, or internal data',
189
+ 'Basic conversation only',
190
+ ],
191
+ },
192
+ },
193
+ users: {},
194
+ defaults: { groupRole: 'guest', dmRole: 'guest' },
195
+ groups: [],
196
+ dms: { defaultMode: 'off', allowed: [] },
197
+ };
198
+ writeFileSync(accessPath, JSON.stringify(cleanAccess, null, 2) + '\n');
165
199
  p.log.success('access.json created');
166
200
  }
167
201
  else {
@@ -631,29 +665,32 @@ export async function runSetup() {
631
665
  'Start the bot:',
632
666
  ' npx @c4t4/heyamigo start',
633
667
  '',
634
- 'Pair with WhatsApp:',
635
- ' Scan the QR code or enter the pairing',
636
- ' code shown in the terminal (npx @c4t4/heyamigo logs).',
637
- '',
638
- 'Activate a group:',
639
- ' 1. Send a message in any group — bot discovers it',
640
- ' 2. Edit config/access.json — set mode to "active"',
641
- ' 3. Mention the bot\'s name to get a reply',
642
- '',
643
668
  'Check logs:',
644
669
  ' npx @c4t4/heyamigo logs',
645
670
  '',
646
671
  'Import existing knowledge:',
647
- ' Got notes, docs, or an old AI workspace? Import it:',
648
672
  ' npx @c4t4/heyamigo import /path/to/folder',
649
- ' Claude reads everything and organizes it into memory.',
650
673
  '',
651
674
  'Other commands:',
652
675
  ' npx @c4t4/heyamigo stop / restart / status',
653
676
  '',
654
677
  'Configuration:',
655
- ' config/config.json — triggers, model, timeouts',
678
+ ' config/config.json — triggers, model',
656
679
  ' config/access.json — groups, DMs, roles',
657
680
  ].join('\n'), 'Setup complete!');
681
+ p.log.warning('IMPORTANT: The bot won\'t respond until you activate a group!\n\n' +
682
+ ' Step 1 — Start the bot:\n' +
683
+ ' npx @c4t4/heyamigo start\n\n' +
684
+ ' Step 2 — Send a message in any WhatsApp group.\n' +
685
+ ' The bot discovers the group and adds it to config/access.json.\n\n' +
686
+ ' Step 3 — Open config/access.json and edit:\n' +
687
+ ' nano config/access.json\n' +
688
+ ' - Find the group, change mode from "off" to "active"\n' +
689
+ ' - Set allowedSenders to "*" for everyone\n\n' +
690
+ ' Step 4 — Restart the bot:\n' +
691
+ ' npx @c4t4/heyamigo restart\n\n' +
692
+ ' Step 5 — Mention the bot\'s name in the group to get a reply.\n\n' +
693
+ ' Debugging:\n' +
694
+ ' npx @c4t4/heyamigo logs');
658
695
  p.outro('Happy chatting!');
659
696
  }
@@ -4,17 +4,15 @@
4
4
  * Spawned by `heyamigo start` as a detached process.
5
5
  */
6
6
  import { spawn } from 'child_process';
7
- import { resolve } from 'path';
7
+ import { dirname, resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ const __distCli = dirname(fileURLToPath(import.meta.url));
8
10
  const RESTART_DELAY_MS = 5000;
9
11
  const cwd = process.cwd();
10
12
  let child = null;
11
13
  let shuttingDown = false;
12
14
  function run() {
13
- child = spawn(process.execPath, [
14
- '--import',
15
- 'file://' + resolve(cwd, 'node_modules/tsx/dist/loader.mjs'),
16
- resolve(cwd, 'src/cli/start.ts'),
17
- ], { stdio: 'inherit', cwd, env: { ...process.env, NODE_ENV: 'production' } });
15
+ child = spawn(process.execPath, [resolve(__distCli, 'start.js')], { stdio: 'inherit', cwd, env: { ...process.env, NODE_ENV: 'production' } });
18
16
  child.on('exit', (code, signal) => {
19
17
  if (shuttingDown) {
20
18
  process.exit(0);
package/dist/config.js CHANGED
@@ -25,7 +25,6 @@ const ConfigSchema = z.object({
25
25
  }),
26
26
  claude: z.object({
27
27
  model: z.string(),
28
- timeoutMs: z.number(),
29
28
  personalityFile: z.string(),
30
29
  addDirs: z.array(z.string()),
31
30
  outputFormat: z.enum(['json', 'text', 'stream-json']),
@@ -24,11 +24,6 @@ async function spawnDigester(prompt) {
24
24
  });
25
25
  let stdout = '';
26
26
  let stderr = '';
27
- let timedOut = false;
28
- const timer = setTimeout(() => {
29
- timedOut = true;
30
- child.kill('SIGTERM');
31
- }, config.claude.timeoutMs);
32
27
  child.stdout.on('data', (chunk) => {
33
28
  stdout += chunk.toString('utf-8');
34
29
  });
@@ -44,16 +39,10 @@ async function spawnDigester(prompt) {
44
39
  durationMs: Date.now() - startedAt,
45
40
  });
46
41
  child.on('error', (err) => {
47
- clearTimeout(timer);
48
42
  logFail(`spawn failed: ${err.message}`);
49
43
  rejectPromise(new Error(`digester spawn failed: ${err.message}`));
50
44
  });
51
45
  child.on('close', (code) => {
52
- clearTimeout(timer);
53
- if (timedOut) {
54
- logFail('timed out');
55
- return rejectPromise(new Error('digester timed out'));
56
- }
57
46
  if (code !== 0) {
58
47
  logFail(`exit ${code}: ${stderr.slice(0, 300)}`);
59
48
  return rejectPromise(new Error(`digester exit ${code}: ${stderr.slice(0, 300)}`));
@@ -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",
3
+ "version": "0.1.5",
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",