@teamvibe/poller 0.1.16 → 0.1.18
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/auth-provider.js +6 -3
- package/dist/brain-manager.d.ts +2 -5
- package/dist/brain-manager.js +42 -5
- package/dist/cli/commands.js +2 -1
- package/dist/cli/install.js +87 -25
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/poller.js +2 -2
- package/package.json +1 -1
package/dist/auth-provider.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { POLLER_VERSION } from './config.js';
|
|
1
2
|
import { logger } from './logger.js';
|
|
2
3
|
let currentCredentials = null;
|
|
3
4
|
let currentConfig = null;
|
|
@@ -5,13 +6,15 @@ let refreshTimer = null;
|
|
|
5
6
|
let heartbeatTimer = null;
|
|
6
7
|
const REFRESH_INTERVAL_MS = 50 * 60 * 1000; // 50 minutes (before 1hr expiry)
|
|
7
8
|
const HEARTBEAT_INTERVAL_MS = 60 * 1000; // 1 minute
|
|
8
|
-
async function sendHeartbeat(apiUrl, token) {
|
|
9
|
+
async function sendHeartbeat(apiUrl, token, version) {
|
|
9
10
|
try {
|
|
10
11
|
const response = await fetch(`${apiUrl}/heartbeat`, {
|
|
11
12
|
method: 'POST',
|
|
12
13
|
headers: {
|
|
13
14
|
Authorization: `Bearer ${token}`,
|
|
15
|
+
'Content-Type': 'application/json',
|
|
14
16
|
},
|
|
17
|
+
body: JSON.stringify({ version }),
|
|
15
18
|
});
|
|
16
19
|
if (!response.ok) {
|
|
17
20
|
logger.warn(`Heartbeat failed (${response.status})`);
|
|
@@ -44,8 +47,8 @@ export async function initAuth(apiUrl, token) {
|
|
|
44
47
|
logger.info(` SQS Queue: ${auth.config.sqsQueueUrl}`);
|
|
45
48
|
logger.info(` Sessions Table: ${auth.config.sessionsTable}`);
|
|
46
49
|
// Send initial heartbeat and schedule periodic heartbeats
|
|
47
|
-
sendHeartbeat(apiUrl, token);
|
|
48
|
-
heartbeatTimer = setInterval(() => sendHeartbeat(apiUrl, token), HEARTBEAT_INTERVAL_MS);
|
|
50
|
+
sendHeartbeat(apiUrl, token, POLLER_VERSION);
|
|
51
|
+
heartbeatTimer = setInterval(() => sendHeartbeat(apiUrl, token, POLLER_VERSION), HEARTBEAT_INTERVAL_MS);
|
|
49
52
|
// Schedule credential refresh
|
|
50
53
|
refreshTimer = setInterval(async () => {
|
|
51
54
|
try {
|
package/dist/brain-manager.d.ts
CHANGED
|
@@ -3,10 +3,7 @@ interface BrainConfig {
|
|
|
3
3
|
gitRepoUrl: string;
|
|
4
4
|
branch: string;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
* Get or clone brain repo. Returns the working directory path.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getBrainPath(brain?: BrainConfig): Promise<string>;
|
|
6
|
+
export declare function getBrainPath(brain?: BrainConfig, channelId?: string, workspaceId?: string): Promise<string>;
|
|
10
7
|
/**
|
|
11
8
|
* Ensure base brain repo is cloned and up to date.
|
|
12
9
|
* Uses same cooldown pattern as channel brains.
|
|
@@ -20,7 +17,7 @@ export declare function getBaseBrainPath(): string;
|
|
|
20
17
|
* Commit and push any changes in a brain repo.
|
|
21
18
|
* Silently skips if there are no changes or no remote.
|
|
22
19
|
*/
|
|
23
|
-
export declare function pushBrainChanges(brainDir: string, brainId: string): Promise<void>;
|
|
20
|
+
export declare function pushBrainChanges(brainDir: string, brainId: string, workspaceId?: string): Promise<void>;
|
|
24
21
|
/**
|
|
25
22
|
* Ensure base paths exist
|
|
26
23
|
*/
|
package/dist/brain-manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
3
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { config } from './config.js';
|
|
6
6
|
import { logger } from './logger.js';
|
|
@@ -10,9 +10,41 @@ const lastUpdateTimes = new Map();
|
|
|
10
10
|
/**
|
|
11
11
|
* Get or clone brain repo. Returns the working directory path.
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Report a brain sync event (pull/push) to the TeamVibe API.
|
|
15
|
+
* Fire-and-forget — errors are logged but not thrown.
|
|
16
|
+
*/
|
|
17
|
+
async function reportBrainSync(brainId, workspaceId, event) {
|
|
18
|
+
if (!config.TEAMVIBE_API_URL || !config.TEAMVIBE_POLLER_TOKEN)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${config.TEAMVIBE_API_URL}/brain-sync`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${config.TEAMVIBE_POLLER_TOKEN}`,
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({ brainId, workspaceId, event }),
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
logger.warn(`Brain sync report failed (${response.status})`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.warn(`Brain sync report error: ${error instanceof Error ? error.message : error}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function getBrainPath(brain, channelId, workspaceId) {
|
|
38
|
+
if (!brain?.gitRepoUrl) {
|
|
39
|
+
if (channelId) {
|
|
40
|
+
const channelDir = join(config.BRAINS_PATH, `channel_${channelId}`);
|
|
41
|
+
if (!existsSync(channelDir)) {
|
|
42
|
+
mkdirSync(channelDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
return channelDir;
|
|
45
|
+
}
|
|
15
46
|
return config.DEFAULT_BRAIN_PATH;
|
|
47
|
+
}
|
|
16
48
|
const brainDir = join(config.BRAINS_PATH, brain.brainId);
|
|
17
49
|
if (!existsSync(brainDir)) {
|
|
18
50
|
logger.info(`Cloning brain ${brain.brainId} from ${brain.gitRepoUrl} (branch: ${brain.branch})...`);
|
|
@@ -30,9 +62,13 @@ export async function getBrainPath(brain) {
|
|
|
30
62
|
}
|
|
31
63
|
}
|
|
32
64
|
logger.info(`Brain ${brain.brainId} cloned successfully`);
|
|
65
|
+
if (workspaceId)
|
|
66
|
+
reportBrainSync(brain.brainId, workspaceId, 'pulled');
|
|
33
67
|
}
|
|
34
68
|
else if (config.BRAIN_AUTO_UPDATE) {
|
|
35
69
|
await updateBrain(brainDir, brain.brainId, brain.branch);
|
|
70
|
+
if (workspaceId)
|
|
71
|
+
reportBrainSync(brain.brainId, workspaceId, 'pulled');
|
|
36
72
|
}
|
|
37
73
|
return brainDir;
|
|
38
74
|
}
|
|
@@ -85,7 +121,7 @@ export function getBaseBrainPath() {
|
|
|
85
121
|
* Commit and push any changes in a brain repo.
|
|
86
122
|
* Silently skips if there are no changes or no remote.
|
|
87
123
|
*/
|
|
88
|
-
export async function pushBrainChanges(brainDir, brainId) {
|
|
124
|
+
export async function pushBrainChanges(brainDir, brainId, workspaceId) {
|
|
89
125
|
try {
|
|
90
126
|
// Check if this is a git repo with a remote
|
|
91
127
|
try {
|
|
@@ -110,6 +146,8 @@ export async function pushBrainChanges(brainDir, brainId) {
|
|
|
110
146
|
await execAsync('git commit -m "auto: session update"', { cwd: brainDir });
|
|
111
147
|
await execAsync('git push', { cwd: brainDir });
|
|
112
148
|
logger.info(`Brain ${brainId} changes pushed successfully`);
|
|
149
|
+
if (workspaceId)
|
|
150
|
+
reportBrainSync(brainId, workspaceId, 'pushed');
|
|
113
151
|
}
|
|
114
152
|
catch (error) {
|
|
115
153
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -120,7 +158,6 @@ export async function pushBrainChanges(brainDir, brainId) {
|
|
|
120
158
|
* Ensure base paths exist
|
|
121
159
|
*/
|
|
122
160
|
export async function ensureDirectories() {
|
|
123
|
-
const { mkdirSync } = await import('fs');
|
|
124
161
|
if (!existsSync(config.BRAINS_PATH)) {
|
|
125
162
|
mkdirSync(config.BRAINS_PATH, { recursive: true });
|
|
126
163
|
}
|
package/dist/cli/commands.js
CHANGED
|
@@ -10,7 +10,8 @@ Usage: poller [command]
|
|
|
10
10
|
|
|
11
11
|
Commands:
|
|
12
12
|
(no command) Start the poller (default)
|
|
13
|
-
install Install as a macOS launchd service
|
|
13
|
+
install Install as a macOS launchd service
|
|
14
|
+
Flags: --token <token> --claude-token <token> [--api-url <url>] [--max-concurrent <n>]
|
|
14
15
|
uninstall Remove the launchd service
|
|
15
16
|
update Update to the latest version and restart
|
|
16
17
|
start Start the installed service
|
package/dist/cli/install.js
CHANGED
|
@@ -17,18 +17,37 @@ function tryGetClaudeSetupToken() {
|
|
|
17
17
|
}
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
|
+
function parseFlags(args) {
|
|
21
|
+
const flags = {};
|
|
22
|
+
for (let i = 0; i < args.length; i++) {
|
|
23
|
+
const arg = args[i];
|
|
24
|
+
const next = args[i + 1];
|
|
25
|
+
if (arg.startsWith('--') && next && !next.startsWith('--')) {
|
|
26
|
+
flags[arg.slice(2)] = next;
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return flags;
|
|
31
|
+
}
|
|
20
32
|
export async function install() {
|
|
21
33
|
if (process.platform !== 'darwin') {
|
|
22
34
|
console.error('Error: Service installation is only supported on macOS.');
|
|
23
35
|
process.exit(1);
|
|
24
36
|
}
|
|
37
|
+
const flags = parseFlags(process.argv.slice(3));
|
|
38
|
+
const nonInteractive = !!(flags['token']);
|
|
25
39
|
console.log('TeamVibe Poller - Service Installation\n');
|
|
26
40
|
// Check if already installed
|
|
27
41
|
if (fs.existsSync(PLIST_PATH)) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
if (nonInteractive) {
|
|
43
|
+
console.log('Service already installed, overwriting...');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const overwrite = await confirm('Service is already installed. Overwrite?', false);
|
|
47
|
+
if (!overwrite) {
|
|
48
|
+
console.log('Aborted.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
32
51
|
}
|
|
33
52
|
// Unload existing service
|
|
34
53
|
try {
|
|
@@ -41,34 +60,70 @@ export async function install() {
|
|
|
41
60
|
// Load existing .env values as defaults
|
|
42
61
|
const existingEnv = loadExistingEnv();
|
|
43
62
|
// Collect configuration
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
let token;
|
|
64
|
+
if (flags['token']) {
|
|
65
|
+
token = flags['token'];
|
|
66
|
+
console.log(`Using provided token: ${token.slice(0, 8)}...`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log('Step 1: Poller Token\n');
|
|
70
|
+
console.log(' Get your token from the TeamVibe dashboard (Pollers > Setup Instructions).');
|
|
71
|
+
console.log(' If you lost it, use "Regenerate Token" from the poller menu.\n');
|
|
72
|
+
token = await prompt('TEAMVIBE_POLLER_TOKEN', existingEnv['TEAMVIBE_POLLER_TOKEN']);
|
|
73
|
+
}
|
|
48
74
|
if (!token) {
|
|
49
75
|
console.error('\nError: TEAMVIBE_POLLER_TOKEN is required.');
|
|
50
76
|
process.exit(1);
|
|
51
77
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
let claudeOAuthToken = flags['claude-token'] || existingEnv['CLAUDE_CODE_OAUTH_TOKEN'] || '';
|
|
79
|
+
if (!claudeOAuthToken) {
|
|
80
|
+
if (nonInteractive) {
|
|
81
|
+
console.log('\nDetecting Claude Code token...');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log('\nStep 2: Claude Code Authentication\n');
|
|
85
|
+
console.log(' The poller needs Claude Code credentials to run AI sessions.');
|
|
86
|
+
console.log(' Run `claude setup-token` in a terminal to generate a token.\n');
|
|
87
|
+
}
|
|
88
|
+
const autoToken = tryGetClaudeSetupToken();
|
|
89
|
+
if (autoToken) {
|
|
90
|
+
if (nonInteractive) {
|
|
91
|
+
claudeOAuthToken = autoToken;
|
|
92
|
+
console.log('Auto-detected Claude setup token.');
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(' Auto-detected Claude setup token.');
|
|
96
|
+
const useAuto = await confirm(' Use the detected token?');
|
|
97
|
+
if (useAuto) {
|
|
98
|
+
claudeOAuthToken = autoToken;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!claudeOAuthToken && !nonInteractive) {
|
|
103
|
+
claudeOAuthToken = await prompt('CLAUDE_CODE_OAUTH_TOKEN');
|
|
62
104
|
}
|
|
63
105
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
106
|
+
else if (flags['claude-token']) {
|
|
107
|
+
console.log('Using provided Claude Code token.');
|
|
108
|
+
}
|
|
109
|
+
let apiUrl;
|
|
110
|
+
let maxConcurrent;
|
|
111
|
+
if (nonInteractive) {
|
|
112
|
+
apiUrl = flags['api-url'] || existingEnv['TEAMVIBE_API_URL'] || 'https://poller.api.teamvibe.ai';
|
|
113
|
+
maxConcurrent = flags['max-concurrent'] || existingEnv['MAX_CONCURRENT_SESSIONS'] || '5';
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log('\nStep 3: Optional Settings\n');
|
|
117
|
+
apiUrl = await prompt('TEAMVIBE_API_URL', existingEnv['TEAMVIBE_API_URL'] || 'https://poller.api.teamvibe.ai');
|
|
118
|
+
maxConcurrent = await prompt('MAX_CONCURRENT_SESSIONS', existingEnv['MAX_CONCURRENT_SESSIONS'] || '5');
|
|
66
119
|
}
|
|
67
|
-
console.log('\nStep 3: Optional Settings\n');
|
|
68
|
-
const apiUrl = await prompt('TEAMVIBE_API_URL', existingEnv['TEAMVIBE_API_URL'] || 'https://poller.api.teamvibe.ai');
|
|
69
|
-
const maxConcurrent = await prompt('MAX_CONCURRENT_SESSIONS', existingEnv['MAX_CONCURRENT_SESSIONS'] || '5');
|
|
70
120
|
// Resolve claude CLI path
|
|
71
|
-
|
|
121
|
+
if (!nonInteractive) {
|
|
122
|
+
console.log('\nStep 4: Detecting paths\n');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log('\nDetecting paths...');
|
|
126
|
+
}
|
|
72
127
|
let claudePath = '';
|
|
73
128
|
try {
|
|
74
129
|
claudePath = execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
@@ -77,6 +132,10 @@ export async function install() {
|
|
|
77
132
|
// Not found
|
|
78
133
|
}
|
|
79
134
|
if (!claudePath) {
|
|
135
|
+
if (nonInteractive) {
|
|
136
|
+
console.error('Error: claude CLI not found in PATH.');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
80
139
|
claudePath = await prompt('Path to claude CLI (not found in PATH)');
|
|
81
140
|
if (!claudePath) {
|
|
82
141
|
console.error('Error: claude CLI path is required.');
|
|
@@ -106,6 +165,9 @@ export async function install() {
|
|
|
106
165
|
catch {
|
|
107
166
|
console.error('\n Warning: Could not install globally. The service may not start after reboot.');
|
|
108
167
|
console.error(' Run `npm install -g @teamvibe/poller` manually, then re-run `poller install`.');
|
|
168
|
+
if (nonInteractive) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
109
171
|
const cont = await confirm(' Continue anyway?', false);
|
|
110
172
|
if (!cont)
|
|
111
173
|
return;
|
|
@@ -128,7 +190,7 @@ export async function install() {
|
|
|
128
190
|
envLines.push(`CLAUDE_CODE_OAUTH_TOKEN=${claudeOAuthToken}`);
|
|
129
191
|
}
|
|
130
192
|
let writeEnv = true;
|
|
131
|
-
if (fs.existsSync(envPath)) {
|
|
193
|
+
if (fs.existsSync(envPath) && !nonInteractive) {
|
|
132
194
|
writeEnv = await confirm('\n.env file already exists. Overwrite?', false);
|
|
133
195
|
if (!writeEnv) {
|
|
134
196
|
console.log('Keeping existing .env file.');
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { config as dotenvConfig } from 'dotenv';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
export const POLLER_VERSION = pkg.version;
|
|
3
7
|
dotenvConfig();
|
|
4
8
|
const DEFAULT_DATA_DIR = `${process.env['HOME']}/.teamvibe`;
|
|
5
9
|
const configSchema = z.object({
|
package/dist/poller.js
CHANGED
|
@@ -82,7 +82,7 @@ async function processMessage(received) {
|
|
|
82
82
|
// Get brain path for this channel
|
|
83
83
|
let kbPath;
|
|
84
84
|
try {
|
|
85
|
-
kbPath = await getBrainPath(queueMessage.teamvibe.brain);
|
|
85
|
+
kbPath = await getBrainPath(queueMessage.teamvibe.brain, queueMessage.response_context.slack?.channel, queueMessage.teamvibe.workspaceId);
|
|
86
86
|
}
|
|
87
87
|
catch (error) {
|
|
88
88
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -139,7 +139,7 @@ async function processMessage(received) {
|
|
|
139
139
|
sessionLog.info('Claude Code completed successfully');
|
|
140
140
|
// Push any changes in the channel brain repo
|
|
141
141
|
if (queueMessage.teamvibe.brain?.brainId) {
|
|
142
|
-
await pushBrainChanges(kbPath, queueMessage.teamvibe.brain.brainId);
|
|
142
|
+
await pushBrainChanges(kbPath, queueMessage.teamvibe.brain.brainId, queueMessage.teamvibe.workspaceId);
|
|
143
143
|
}
|
|
144
144
|
if (lockToken) {
|
|
145
145
|
const lastMessageTs = queueMessage.response_context.slack?.message_ts;
|