@teamvibe/poller 0.1.41 → 0.1.43

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/cli/reset.js CHANGED
@@ -7,28 +7,6 @@ const DEFAULT_DATA_DIR = `${process.env['HOME']}/.teamvibe`;
7
7
  function getBaseBrainPath() {
8
8
  return process.env['BASE_BRAIN_PATH'] || `${DEFAULT_DATA_DIR}/base-brain`;
9
9
  }
10
- function cleanClaudeSessionLocks() {
11
- const baseBrainPath = getBaseBrainPath();
12
- let cleaned = 0;
13
- // Claude Code stores session locks in the config dir (which is base-brain for poller)
14
- // Look for .lock files in tasks/ and projects/ subdirectories
15
- const lockDirs = [
16
- path.join(baseBrainPath, 'tasks'),
17
- path.join(baseBrainPath, 'projects'),
18
- ];
19
- for (const dir of lockDirs) {
20
- if (!fs.existsSync(dir))
21
- continue;
22
- cleanLocksRecursive(dir);
23
- }
24
- // Also check ~/.claude for lock files (default Claude config dir)
25
- const claudeDir = `${process.env['HOME']}/.claude`;
26
- if (fs.existsSync(claudeDir)) {
27
- cleaned += cleanLocksRecursive(path.join(claudeDir, 'tasks'));
28
- cleaned += cleanLocksRecursive(path.join(claudeDir, 'projects'));
29
- }
30
- return cleaned;
31
- }
32
10
  function cleanLocksRecursive(dir) {
33
11
  if (!fs.existsSync(dir))
34
12
  return 0;
@@ -47,6 +25,25 @@ function cleanLocksRecursive(dir) {
47
25
  }
48
26
  return cleaned;
49
27
  }
28
+ function cleanClaudeSessionLocks() {
29
+ const baseBrainPath = getBaseBrainPath();
30
+ let cleaned = 0;
31
+ const lockDirs = [
32
+ path.join(baseBrainPath, 'tasks'),
33
+ path.join(baseBrainPath, 'projects'),
34
+ ];
35
+ for (const dir of lockDirs) {
36
+ if (!fs.existsSync(dir))
37
+ continue;
38
+ cleaned += cleanLocksRecursive(dir);
39
+ }
40
+ const claudeDir = `${process.env['HOME']}/.claude`;
41
+ if (fs.existsSync(claudeDir)) {
42
+ cleaned += cleanLocksRecursive(path.join(claudeDir, 'tasks'));
43
+ cleaned += cleanLocksRecursive(path.join(claudeDir, 'projects'));
44
+ }
45
+ return cleaned;
46
+ }
50
47
  function killClaudeProcesses() {
51
48
  try {
52
49
  const output = execSync('pgrep -f "claude.*--print" 2>/dev/null || true', {
@@ -70,6 +67,77 @@ function killClaudeProcesses() {
70
67
  return 0;
71
68
  }
72
69
  }
70
+ /** Read poller token and API URL from the launchd plist or env */
71
+ function getPollerAuth() {
72
+ // Try env vars first
73
+ const apiUrl = process.env['TEAMVIBE_API_URL'];
74
+ const token = process.env['TEAMVIBE_POLLER_TOKEN'];
75
+ if (apiUrl && token)
76
+ return { apiUrl, token };
77
+ // Try reading from launchd plist
78
+ if (!fs.existsSync(PLIST_PATH))
79
+ return null;
80
+ try {
81
+ const plistContent = fs.readFileSync(PLIST_PATH, 'utf-8');
82
+ const envVars = {};
83
+ // Parse EnvironmentVariables from plist XML
84
+ const envMatch = plistContent.match(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/);
85
+ if (envMatch?.[1]) {
86
+ const keyValuePairs = envMatch[1].matchAll(/<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g);
87
+ for (const match of keyValuePairs) {
88
+ if (match[1] && match[2] !== undefined) {
89
+ envVars[match[1]] = match[2];
90
+ }
91
+ }
92
+ }
93
+ const plistApiUrl = envVars['TEAMVIBE_API_URL'];
94
+ const plistToken = envVars['TEAMVIBE_POLLER_TOKEN'];
95
+ if (plistApiUrl && plistToken)
96
+ return { apiUrl: plistApiUrl, token: plistToken };
97
+ }
98
+ catch {
99
+ // Ignore plist parse errors
100
+ }
101
+ return null;
102
+ }
103
+ async function purgeQueue() {
104
+ const auth = getPollerAuth();
105
+ if (!auth) {
106
+ console.log(' Cannot purge queue: no auth credentials found');
107
+ return false;
108
+ }
109
+ try {
110
+ // Authenticate to get SQS queue URL and AWS credentials
111
+ const response = await fetch(`${auth.apiUrl}/auth`, {
112
+ method: 'POST',
113
+ headers: {
114
+ Authorization: `Bearer ${auth.token}`,
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ });
118
+ if (!response.ok) {
119
+ console.log(` Auth failed (${response.status}), cannot purge queue`);
120
+ return false;
121
+ }
122
+ const authData = (await response.json());
123
+ const { SQSClient, PurgeQueueCommand } = await import('@aws-sdk/client-sqs');
124
+ const sqsClient = new SQSClient({
125
+ region: authData.config.region,
126
+ credentials: {
127
+ accessKeyId: authData.credentials.accessKeyId,
128
+ secretAccessKey: authData.credentials.secretAccessKey,
129
+ sessionToken: authData.credentials.sessionToken,
130
+ },
131
+ });
132
+ await sqsClient.send(new PurgeQueueCommand({ QueueUrl: authData.config.sqsQueueUrl }));
133
+ console.log(` Purged queue: ${authData.config.sqsQueueUrl}`);
134
+ return true;
135
+ }
136
+ catch (error) {
137
+ console.log(` Failed to purge queue: ${error instanceof Error ? error.message : error}`);
138
+ return false;
139
+ }
140
+ }
73
141
  export async function reset() {
74
142
  const isServiceInstalled = fs.existsSync(PLIST_PATH);
75
143
  console.log('Resetting TeamVibe Poller...\n');
@@ -94,7 +162,6 @@ export async function reset() {
94
162
  }
95
163
  else {
96
164
  console.log(` Killed ${killed} process(es)`);
97
- // Give processes time to exit and release locks
98
165
  await new Promise((resolve) => setTimeout(resolve, 2000));
99
166
  }
100
167
  // Step 3: Clean session lock files
@@ -106,13 +173,16 @@ export async function reset() {
106
173
  else {
107
174
  console.log(` Cleaned ${cleaned} lock file(s)`);
108
175
  }
109
- // Step 4: Restart service if it was installed
176
+ // Step 4: Purge SQS queue to clear stuck messages
177
+ console.log('\n4. Purging SQS queue...');
178
+ await purgeQueue();
179
+ // Step 5: Restart service if it was installed
110
180
  if (isServiceInstalled) {
111
- console.log('\n4. Restarting service...');
181
+ console.log('\n5. Restarting service...');
112
182
  start();
113
183
  }
114
184
  else {
115
- console.log('\n4. No service to restart');
185
+ console.log('\n5. No service to restart');
116
186
  }
117
187
  console.log('\nReset complete!');
118
188
  }
package/dist/poller.js CHANGED
@@ -176,8 +176,15 @@ async function processMessage(received) {
176
176
  }
177
177
  if (hasSlackContext) {
178
178
  try {
179
- await sendSlackError(queueMessage, error instanceof Error ? error.message : 'Unknown error');
180
- await addReaction(queueMessage, 'x');
179
+ const errMsg = error instanceof Error ? error.message : 'Unknown error';
180
+ // Don't leak internal infra errors to Slack users
181
+ const isInternalError = errMsg.includes('ReceiptHandle') ||
182
+ errMsg.includes('ConditionalCheckFailedException') ||
183
+ errMsg.includes('visibility timeout');
184
+ if (!isInternalError) {
185
+ await sendSlackError(queueMessage, errMsg);
186
+ await addReaction(queueMessage, 'x');
187
+ }
181
188
  }
182
189
  catch (slackError) {
183
190
  sessionLog.error(`Failed to send Slack error: ${slackError}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamvibe/poller",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {