@c4t4/heyamigo 0.8.0 → 0.8.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.
package/dist/ai/claude.js CHANGED
@@ -3,7 +3,7 @@ import { resolve } from 'path';
3
3
  import { config } from '../config.js';
4
4
  import { logger } from '../logger.js';
5
5
  import { logPrompt } from '../promptlog.js';
6
- import { runClaude, TIMEOUT_MS } from './spawn.js';
6
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from './spawn.js';
7
7
  let cachedSystemPrompt = null;
8
8
  function systemPrompt() {
9
9
  if (cachedSystemPrompt !== null)
@@ -25,10 +25,15 @@ export function reloadSystemPrompt() {
25
25
  cachedSystemPrompt = null;
26
26
  }
27
27
  function buildArgs(params) {
28
+ // stream-json gives per-event visibility into the agent loop (system init,
29
+ // assistant messages, tool_use, tool_result, final result). We parse the
30
+ // final 'result' event for the return shape, and log event types for
31
+ // diagnostic purposes.
28
32
  const args = [
29
33
  '-p',
30
34
  '--output-format',
31
- config.claude.outputFormat,
35
+ 'stream-json',
36
+ '--verbose',
32
37
  '--model',
33
38
  config.claude.model,
34
39
  '--permission-mode',
@@ -57,35 +62,32 @@ function buildArgs(params) {
57
62
  export async function askClaude(params) {
58
63
  const args = buildArgs(params);
59
64
  logger.debug({ resume: !!params.sessionId, inputChars: params.input.length }, 'spawning claude');
60
- const { stdout, durationMs } = await runClaude({
65
+ const { stdout, stderr, durationMs } = await runClaude({
61
66
  args,
62
67
  input: params.input,
63
68
  timeoutMs: TIMEOUT_MS.main,
64
69
  caller: 'worker',
65
70
  });
66
71
  const startedAt = Date.now() - durationMs;
67
- let parsed;
68
- try {
69
- parsed = JSON.parse(stdout);
70
- }
71
- catch (err) {
72
- throw new Error(`failed to parse claude output: ${err.message}\nstdout: ${stdout.slice(0, 500)}`);
72
+ const parsed = parseStreamJson(stdout);
73
+ if (!parsed) {
74
+ throw new Error(`claude stream-json produced no result event; stdout: ${stdout.slice(0, 500)}`);
73
75
  }
74
- if (parsed.is_error || parsed.subtype !== 'success') {
75
- throw new Error(`claude returned error (subtype=${parsed.subtype}): ${parsed.result ?? ''}`);
76
+ if (parsed.isError || parsed.subtype !== 'success') {
77
+ throw new Error(`claude returned error (subtype=${parsed.subtype}): ${parsed.result}`);
76
78
  }
77
- if (!parsed.result || !parsed.session_id) {
79
+ if (!parsed.result || !parsed.sessionId) {
78
80
  throw new Error(`claude output missing result or session_id: ${stdout.slice(0, 200)}`);
79
81
  }
80
82
  const result = {
81
83
  reply: parsed.result,
82
- sessionId: parsed.session_id,
84
+ sessionId: parsed.sessionId,
83
85
  usage: {
84
86
  inputTokens: parsed.usage?.input_tokens ?? 0,
85
87
  cacheReadTokens: parsed.usage?.cache_read_input_tokens ?? 0,
86
88
  cacheCreationTokens: parsed.usage?.cache_creation_input_tokens ?? 0,
87
89
  outputTokens: parsed.usage?.output_tokens ?? 0,
88
- numTurns: parsed.num_turns ?? 0,
90
+ numTurns: parsed.numTurns ?? 0,
89
91
  },
90
92
  };
91
93
  void logPrompt({
@@ -97,6 +99,8 @@ export async function askClaude(params) {
97
99
  sessionId: result.sessionId,
98
100
  usage: result.usage,
99
101
  durationMs,
102
+ stderr,
103
+ eventTypes: parsed.eventTypes,
100
104
  });
101
105
  return result;
102
106
  }
package/dist/ai/spawn.js CHANGED
@@ -21,6 +21,60 @@ export class ClaudeSpawnError extends Error {
21
21
  this.name = 'ClaudeSpawnError';
22
22
  }
23
23
  }
24
+ // Parse Claude CLI's --output-format stream-json output. Each line is a JSON
25
+ // event; the final event with type === 'result' carries the completion
26
+ // summary (same shape as the old single-json output format). Returns null if
27
+ // no result event is found — caller should treat that as an error.
28
+ export function parseStreamJson(stdout) {
29
+ const events = [];
30
+ const eventTypes = [];
31
+ const lines = stdout.split(/\r?\n/);
32
+ for (const line of lines) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed)
35
+ continue;
36
+ try {
37
+ const parsed = JSON.parse(trimmed);
38
+ events.push(parsed);
39
+ if (typeof parsed.type === 'string')
40
+ eventTypes.push(parsed.type);
41
+ }
42
+ catch {
43
+ // Ignore malformed lines — Claude CLI occasionally emits preamble or
44
+ // debug lines that aren't JSON; the structured events we need are
45
+ // always well-formed.
46
+ }
47
+ }
48
+ // Find the final result event. Walk from end to handle any stray events
49
+ // after 'result' (shouldn't happen but be defensive).
50
+ let resultEvent = null;
51
+ for (let i = events.length - 1; i >= 0; i--) {
52
+ if (events[i].type === 'result') {
53
+ resultEvent = events[i];
54
+ break;
55
+ }
56
+ }
57
+ if (!resultEvent)
58
+ return null;
59
+ return {
60
+ result: typeof resultEvent.result === 'string' ? resultEvent.result : '',
61
+ sessionId: typeof resultEvent.session_id === 'string'
62
+ ? resultEvent.session_id
63
+ : null,
64
+ usage: resultEvent.usage && typeof resultEvent.usage === 'object'
65
+ ? resultEvent.usage
66
+ : undefined,
67
+ isError: !!resultEvent.is_error,
68
+ subtype: typeof resultEvent.subtype === 'string'
69
+ ? resultEvent.subtype
70
+ : undefined,
71
+ numTurns: typeof resultEvent.num_turns === 'number'
72
+ ? resultEvent.num_turns
73
+ : undefined,
74
+ eventTypes,
75
+ events,
76
+ };
77
+ }
24
78
  // Kill the process group of a detached child. Playwright MCP and any Chromium
25
79
  // children sit under the claude subprocess; without process-group kill they
26
80
  // linger after we SIGTERM the parent and accumulate on the host.
@@ -54,6 +108,15 @@ export async function runClaude(opts) {
54
108
  // detached:true puts the child in its own process group, so killGroup
55
109
  // can SIGTERM the whole tree (Playwright MCP, Chromium, etc.) at once.
56
110
  detached: true,
111
+ // ANTHROPIC_LOG=debug surfaces the SDK's HTTP layer to stderr:
112
+ // request URLs, status codes, retries, rate-limit notices. We
113
+ // capture stderr and put a truncated copy into the promptlog so
114
+ // we can diagnose API hangs/rate-limits post-mortem instead of
115
+ // staring at "Claude subprocess is idle, why?".
116
+ env: {
117
+ ...process.env,
118
+ ANTHROPIC_LOG: process.env.ANTHROPIC_LOG ?? 'debug',
119
+ },
57
120
  });
58
121
  let stdout = '';
59
122
  let stderr = '';
@@ -106,7 +169,7 @@ export async function runClaude(opts) {
106
169
  logFail(`exit ${code}: ${stderr.slice(0, 500)}`);
107
170
  return rejectPromise(new ClaudeSpawnError(caller, `claude exited with code ${code}: ${stderr.slice(0, 500)}`));
108
171
  }
109
- resolvePromise({ stdout, durationMs });
172
+ resolvePromise({ stdout, stderr, durationMs });
110
173
  });
111
174
  child.stdin.write(input);
112
175
  child.stdin.end();
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, readdirSync, writeFileSync, } from 'fs';
2
2
  import { dirname, resolve } from 'path';
3
3
  import { mkdirSync } from 'fs';
4
- import { runClaude, TIMEOUT_MS } from '../ai/spawn.js';
4
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from '../ai/spawn.js';
5
5
  import { config } from '../config.js';
6
6
  import { logger } from '../logger.js';
7
7
  import { logPrompt } from '../promptlog.js';
@@ -224,28 +224,26 @@ async function spawnGenerator(prompt) {
224
224
  const args = [
225
225
  '-p',
226
226
  '--output-format',
227
- 'json',
227
+ 'stream-json',
228
+ '--verbose',
228
229
  '--model',
229
230
  config.claude.model,
230
231
  '--permission-mode',
231
232
  'acceptEdits',
232
233
  ];
233
- const { stdout, durationMs } = await runClaude({
234
+ const { stdout, stderr, durationMs } = await runClaude({
234
235
  args,
235
236
  input: prompt,
236
237
  timeoutMs: TIMEOUT_MS.background,
237
238
  caller: 'compressed',
238
239
  });
239
240
  const startedAt = Date.now() - durationMs;
240
- let parsed;
241
- try {
242
- parsed = JSON.parse(stdout);
243
- }
244
- catch (err) {
245
- throw new Error(`compressed parse failed: ${err.message}`);
241
+ const parsed = parseStreamJson(stdout);
242
+ if (!parsed) {
243
+ throw new Error(`compressed stream-json produced no result event: ${stdout.slice(0, 200)}`);
246
244
  }
247
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
248
- throw new Error(`compressed bad output: ${parsed.result ?? stdout.slice(0, 200)}`);
245
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
246
+ throw new Error(`compressed bad output: ${parsed.result || stdout.slice(0, 200)}`);
249
247
  }
250
248
  const output = parsed.result.trim();
251
249
  void logPrompt({
@@ -255,6 +253,8 @@ async function spawnGenerator(prompt) {
255
253
  input: prompt,
256
254
  output,
257
255
  durationMs,
256
+ stderr,
257
+ eventTypes: parsed.eventTypes,
258
258
  });
259
259
  return output;
260
260
  }
@@ -1,4 +1,4 @@
1
- import { runClaude, TIMEOUT_MS } from '../ai/spawn.js';
1
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from '../ai/spawn.js';
2
2
  import { config } from '../config.js';
3
3
  import { logger } from '../logger.js';
4
4
  import { logPrompt } from '../promptlog.js';
@@ -13,28 +13,26 @@ async function spawnDigester(prompt) {
13
13
  const args = [
14
14
  '-p',
15
15
  '--output-format',
16
- 'json',
16
+ 'stream-json',
17
+ '--verbose',
17
18
  '--model',
18
19
  config.claude.model,
19
20
  '--permission-mode',
20
21
  'acceptEdits',
21
22
  ];
22
- const { stdout, durationMs } = await runClaude({
23
+ const { stdout, stderr, durationMs } = await runClaude({
23
24
  args,
24
25
  input: prompt,
25
26
  timeoutMs: TIMEOUT_MS.background,
26
27
  caller: 'digester',
27
28
  });
28
29
  const startedAt = Date.now() - durationMs;
29
- let parsed;
30
- try {
31
- parsed = JSON.parse(stdout);
32
- }
33
- catch (err) {
34
- throw new Error(`digester parse failed: ${err.message}`);
30
+ const parsed = parseStreamJson(stdout);
31
+ if (!parsed) {
32
+ throw new Error(`digester stream-json produced no result event: ${stdout.slice(0, 200)}`);
35
33
  }
36
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
37
- throw new Error(`digester bad output: ${parsed.result ?? stdout.slice(0, 200)}`);
34
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
35
+ throw new Error(`digester bad output: ${parsed.result || stdout.slice(0, 200)}`);
38
36
  }
39
37
  const output = parsed.result.trim();
40
38
  void logPrompt({
@@ -44,6 +42,8 @@ async function spawnDigester(prompt) {
44
42
  input: prompt,
45
43
  output,
46
44
  durationMs,
45
+ stderr,
46
+ eventTypes: parsed.eventTypes,
47
47
  });
48
48
  return output;
49
49
  }
@@ -1,4 +1,4 @@
1
- import { runClaude, TIMEOUT_MS } from '../ai/spawn.js';
1
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from '../ai/spawn.js';
2
2
  import { config } from '../config.js';
3
3
  import { initiate } from '../gateway/outgoing.js';
4
4
  import { logger } from '../logger.js';
@@ -18,28 +18,26 @@ async function spawnComposer(prompt) {
18
18
  const args = [
19
19
  '-p',
20
20
  '--output-format',
21
- 'json',
21
+ 'stream-json',
22
+ '--verbose',
22
23
  '--model',
23
24
  config.claude.model,
24
25
  '--permission-mode',
25
26
  'acceptEdits',
26
27
  ];
27
- const { stdout, durationMs } = await runClaude({
28
+ const { stdout, stderr, durationMs } = await runClaude({
28
29
  args,
29
30
  input: prompt,
30
31
  timeoutMs: TIMEOUT_MS.background,
31
32
  caller: 'journal-nudger',
32
33
  });
33
34
  const startedAt = Date.now() - durationMs;
34
- let parsed;
35
- try {
36
- parsed = JSON.parse(stdout);
37
- }
38
- catch (err) {
39
- throw new Error(`nudger parse failed: ${err.message}`);
35
+ const parsed = parseStreamJson(stdout);
36
+ if (!parsed) {
37
+ throw new Error(`nudger stream-json produced no result event: ${stdout.slice(0, 200)}`);
40
38
  }
41
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
42
- throw new Error(`nudger bad output: ${parsed.result ?? stdout.slice(0, 200)}`);
39
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
40
+ throw new Error(`nudger bad output: ${parsed.result || stdout.slice(0, 200)}`);
43
41
  }
44
42
  const output = parsed.result.trim();
45
43
  void logPrompt({
@@ -49,6 +47,8 @@ async function spawnComposer(prompt) {
49
47
  input: prompt,
50
48
  output,
51
49
  durationMs,
50
+ stderr,
51
+ eventTypes: parsed.eventTypes,
52
52
  });
53
53
  return output;
54
54
  }
@@ -1,4 +1,4 @@
1
- import { runClaude, TIMEOUT_MS } from '../ai/spawn.js';
1
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from '../ai/spawn.js';
2
2
  import { config } from '../config.js';
3
3
  import { logger } from '../logger.js';
4
4
  import { logPrompt } from '../promptlog.js';
@@ -14,28 +14,26 @@ async function spawnObserver(prompt) {
14
14
  const args = [
15
15
  '-p',
16
16
  '--output-format',
17
- 'json',
17
+ 'stream-json',
18
+ '--verbose',
18
19
  '--model',
19
20
  config.claude.model,
20
21
  '--permission-mode',
21
22
  'acceptEdits',
22
23
  ];
23
- const { stdout, durationMs } = await runClaude({
24
+ const { stdout, stderr, durationMs } = await runClaude({
24
25
  args,
25
26
  input: prompt,
26
27
  timeoutMs: TIMEOUT_MS.background,
27
28
  caller: 'journal-observer',
28
29
  });
29
30
  const startedAt = Date.now() - durationMs;
30
- let parsed;
31
- try {
32
- parsed = JSON.parse(stdout);
33
- }
34
- catch (err) {
35
- throw new Error(`journal observer parse failed: ${err.message}`);
31
+ const parsed = parseStreamJson(stdout);
32
+ if (!parsed) {
33
+ throw new Error(`journal observer stream-json produced no result event: ${stdout.slice(0, 200)}`);
36
34
  }
37
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
38
- throw new Error(`journal observer bad output: ${parsed.result ?? stdout.slice(0, 200)}`);
35
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
36
+ throw new Error(`journal observer bad output: ${parsed.result || stdout.slice(0, 200)}`);
39
37
  }
40
38
  const output = parsed.result.trim();
41
39
  void logPrompt({
@@ -45,6 +43,8 @@ async function spawnObserver(prompt) {
45
43
  input: prompt,
46
44
  output,
47
45
  durationMs,
46
+ stderr,
47
+ eventTypes: parsed.eventTypes,
48
48
  });
49
49
  return output;
50
50
  }
package/dist/promptlog.js CHANGED
@@ -1,6 +1,21 @@
1
1
  import { appendFile, mkdir, readdir, unlink } from 'fs/promises';
2
2
  import { resolve } from 'path';
3
3
  import { config } from './config.js';
4
+ // Hard caps on fields that can grow unbounded. Prevents promptlog entries
5
+ // from exploding when a run produces huge stdout/stderr. prunePrompts
6
+ // still handles multi-day retention separately.
7
+ const STDOUT_MAX_BYTES = 100_000;
8
+ const STDERR_MAX_BYTES = 50_000;
9
+ const INPUT_MAX_BYTES = 200_000;
10
+ function truncateWithMarker(s, maxBytes) {
11
+ if (!s)
12
+ return s;
13
+ // Rough byte size via length — fine for mostly-ASCII prompt payloads.
14
+ if (s.length <= maxBytes)
15
+ return s;
16
+ const extra = s.length - maxBytes;
17
+ return s.slice(0, maxBytes) + `\n… [truncated ${extra} bytes]`;
18
+ }
4
19
  let dirReady = false;
5
20
  function promptsDir() {
6
21
  return resolve(process.cwd(), 'storage/prompts');
@@ -18,7 +33,17 @@ function logFilePath() {
18
33
  export async function logPrompt(entry) {
19
34
  try {
20
35
  await ensureDir();
21
- await appendFile(logFilePath(), JSON.stringify(entry) + '\n', 'utf-8');
36
+ const capped = {
37
+ ...entry,
38
+ input: truncateWithMarker(entry.input, INPUT_MAX_BYTES),
39
+ };
40
+ if (entry.output !== undefined) {
41
+ capped.output = truncateWithMarker(entry.output, STDOUT_MAX_BYTES);
42
+ }
43
+ if (entry.stderr !== undefined) {
44
+ capped.stderr = truncateWithMarker(entry.stderr, STDERR_MAX_BYTES);
45
+ }
46
+ await appendFile(logFilePath(), JSON.stringify(capped) + '\n', 'utf-8');
22
47
  }
23
48
  catch {
24
49
  // logging must never break the main flow
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { dirname, resolve } from 'path';
3
- import { runClaude, TIMEOUT_MS } from '../ai/spawn.js';
3
+ import { parseStreamJson, runClaude, TIMEOUT_MS } from '../ai/spawn.js';
4
4
  import { config } from '../config.js';
5
5
  import fastq from 'fastq';
6
6
  import { initiate } from '../gateway/outgoing.js';
@@ -113,7 +113,8 @@ function buildArgs(task) {
113
113
  const args = [
114
114
  '-p',
115
115
  '--output-format',
116
- 'json',
116
+ 'stream-json',
117
+ '--verbose',
117
118
  '--model',
118
119
  config.claude.model,
119
120
  '--permission-mode',
@@ -135,22 +136,19 @@ function buildArgs(task) {
135
136
  }
136
137
  async function spawnClaudeForTask(task, prompt) {
137
138
  const args = buildArgs(task);
138
- const { stdout, durationMs } = await runClaude({
139
+ const { stdout, stderr, durationMs } = await runClaude({
139
140
  args,
140
141
  input: prompt,
141
142
  timeoutMs: TIMEOUT_MS.async,
142
143
  caller: 'async-task',
143
144
  });
144
145
  const startedAt = Date.now() - durationMs;
145
- let parsed;
146
- try {
147
- parsed = JSON.parse(stdout);
148
- }
149
- catch (err) {
150
- throw new Error(`async task parse failed: ${err.message}`);
146
+ const parsed = parseStreamJson(stdout);
147
+ if (!parsed) {
148
+ throw new Error(`async task stream-json produced no result event: ${stdout.slice(0, 200)}`);
151
149
  }
152
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
153
- throw new Error(`async task bad output: ${parsed.result ?? stdout.slice(0, 200)}`);
150
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
151
+ throw new Error(`async task bad output: ${parsed.result || stdout.slice(0, 200)}`);
154
152
  }
155
153
  const output = parsed.result.trim();
156
154
  void logPrompt({
@@ -160,6 +158,8 @@ async function spawnClaudeForTask(task, prompt) {
160
158
  input: prompt,
161
159
  output,
162
160
  durationMs,
161
+ stderr,
162
+ eventTypes: parsed.eventTypes,
163
163
  });
164
164
  return output;
165
165
  }
@@ -398,7 +398,8 @@ function buildBrowserArgs(task, sessionId) {
398
398
  const args = [
399
399
  '-p',
400
400
  '--output-format',
401
- 'json',
401
+ 'stream-json',
402
+ '--verbose',
402
403
  '--model',
403
404
  config.claude.model,
404
405
  '--permission-mode',
@@ -435,6 +436,7 @@ async function runBrowserTask(task) {
435
436
  const startedAtMs = Date.now();
436
437
  const elapsedLog = () => `${Math.round((Date.now() - task.startedAt * 1000) / 1000)}s`;
437
438
  let stdout;
439
+ let stderr;
438
440
  let durationMs;
439
441
  try {
440
442
  const result = await runClaude({
@@ -444,6 +446,7 @@ async function runBrowserTask(task) {
444
446
  caller: 'browser-task',
445
447
  });
446
448
  stdout = result.stdout;
449
+ stderr = result.stderr;
447
450
  durationMs = result.durationMs;
448
451
  }
449
452
  catch (err) {
@@ -454,20 +457,17 @@ async function runBrowserTask(task) {
454
457
  });
455
458
  return;
456
459
  }
457
- let parsed;
458
- try {
459
- parsed = JSON.parse(stdout);
460
- }
461
- catch (err) {
462
- logger.error({ err, id: task.id }, 'browser task: failed to parse claude output');
460
+ const parsed = parseStreamJson(stdout);
461
+ if (!parsed) {
462
+ logger.error({ id: task.id }, 'browser task stream-json produced no result event');
463
463
  await initiate({
464
464
  jid: task.jid,
465
465
  text: `Heads up: the browser task "${truncate(task.description, 80)}" returned an unparseable response.`,
466
466
  });
467
467
  return;
468
468
  }
469
- if (parsed.is_error || parsed.subtype !== 'success' || !parsed.result) {
470
- logger.error({ parsed, id: task.id }, 'browser task bad output');
469
+ if (parsed.isError || parsed.subtype !== 'success' || !parsed.result) {
470
+ logger.error({ id: task.id, subtype: parsed.subtype, isError: parsed.isError }, 'browser task bad output');
471
471
  await initiate({
472
472
  jid: task.jid,
473
473
  text: `Heads up: the browser task "${truncate(task.description, 80)}" returned an error.`,
@@ -476,7 +476,7 @@ async function runBrowserTask(task) {
476
476
  }
477
477
  // Persist the session id. On first call Claude returns the new sessionId;
478
478
  // on resume it may return the same or a rotated one.
479
- const returnedSessionId = parsed.session_id ?? null;
479
+ const returnedSessionId = parsed.sessionId;
480
480
  if (returnedSessionId) {
481
481
  const now = Math.floor(Date.now() / 1000);
482
482
  saveBrowserSession({
@@ -494,6 +494,8 @@ async function runBrowserTask(task) {
494
494
  output: parsed.result,
495
495
  sessionId: returnedSessionId ?? undefined,
496
496
  durationMs,
497
+ stderr,
498
+ eventTypes: parsed.eventTypes,
497
499
  });
498
500
  // Route markers the same way the general async lane does.
499
501
  const { extractFlags } = await import('../memory/digest-flag.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4t4/heyamigo",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",