@teamvibe/poller 0.1.17 → 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.
@@ -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, channelId?: string): 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
  */
@@ -10,7 +10,31 @@ const lastUpdateTimes = new Map();
10
10
  /**
11
11
  * Get or clone brain repo. Returns the working directory path.
12
12
  */
13
- export async function getBrainPath(brain, channelId) {
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) {
14
38
  if (!brain?.gitRepoUrl) {
15
39
  if (channelId) {
16
40
  const channelDir = join(config.BRAINS_PATH, `channel_${channelId}`);
@@ -38,9 +62,13 @@ export async function getBrainPath(brain, channelId) {
38
62
  }
39
63
  }
40
64
  logger.info(`Brain ${brain.brainId} cloned successfully`);
65
+ if (workspaceId)
66
+ reportBrainSync(brain.brainId, workspaceId, 'pulled');
41
67
  }
42
68
  else if (config.BRAIN_AUTO_UPDATE) {
43
69
  await updateBrain(brainDir, brain.brainId, brain.branch);
70
+ if (workspaceId)
71
+ reportBrainSync(brain.brainId, workspaceId, 'pulled');
44
72
  }
45
73
  return brainDir;
46
74
  }
@@ -93,7 +121,7 @@ export function getBaseBrainPath() {
93
121
  * Commit and push any changes in a brain repo.
94
122
  * Silently skips if there are no changes or no remote.
95
123
  */
96
- export async function pushBrainChanges(brainDir, brainId) {
124
+ export async function pushBrainChanges(brainDir, brainId, workspaceId) {
97
125
  try {
98
126
  // Check if this is a git repo with a remote
99
127
  try {
@@ -118,6 +146,8 @@ export async function pushBrainChanges(brainDir, brainId) {
118
146
  await execAsync('git commit -m "auto: session update"', { cwd: brainDir });
119
147
  await execAsync('git push', { cwd: brainDir });
120
148
  logger.info(`Brain ${brainId} changes pushed successfully`);
149
+ if (workspaceId)
150
+ reportBrainSync(brainId, workspaceId, 'pushed');
121
151
  }
122
152
  catch (error) {
123
153
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -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 (interactive)
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
@@ -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
- const overwrite = await confirm('Service is already installed. Overwrite?', false);
29
- if (!overwrite) {
30
- console.log('Aborted.');
31
- return;
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
- console.log('Step 1: Poller Token\n');
45
- console.log(' Get your token from the TeamVibe dashboard (Pollers > Setup Instructions).');
46
- console.log(' If you lost it, use "Regenerate Token" from the poller menu.\n');
47
- const token = await prompt('TEAMVIBE_POLLER_TOKEN', existingEnv['TEAMVIBE_POLLER_TOKEN']);
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
- console.log('\nStep 2: Claude Code Authentication\n');
53
- console.log(' The poller needs Claude Code credentials to run AI sessions.');
54
- console.log(' Run `claude setup-token` in a terminal to generate a token.\n');
55
- let claudeOAuthToken = existingEnv['CLAUDE_CODE_OAUTH_TOKEN'] || '';
56
- const autoToken = tryGetClaudeSetupToken();
57
- if (autoToken) {
58
- console.log(' Auto-detected Claude setup token.');
59
- const useAuto = await confirm(' Use the detected token?');
60
- if (useAuto) {
61
- claudeOAuthToken = autoToken;
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 (!claudeOAuthToken) {
65
- claudeOAuthToken = await prompt('CLAUDE_CODE_OAUTH_TOKEN');
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
- console.log('\nStep 4: Detecting paths\n');
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/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, queueMessage.response_context.slack?.channel);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamvibe/poller",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {