vibeteam 0.2.0
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/LICENSE +21 -0
- package/README.md +126 -0
- package/bin/cli.js +832 -0
- package/bin/vibeteam +71 -0
- package/data/.gitkeep +0 -0
- package/dist/server/server/CommitWatcher.js +307 -0
- package/dist/server/server/GitStatusManager.js +349 -0
- package/dist/server/server/ProjectsManager.js +439 -0
- package/dist/server/server/index.js +5994 -0
- package/dist/server/shared/defaults.js +28 -0
- package/dist/server/shared/types.js +33 -0
- package/hooks/install.sh +77 -0
- package/hooks/vibeteam-hook.sh +384 -0
- package/package.json +48 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/index-BNFGxstP.js +4793 -0
- package/public/assets/index-F7oFKwZy.css +1 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +14 -0
- package/public/index.html +21 -0
- package/public/logo.svg +22 -0
- package/public/models/.gitkeep +0 -0
- package/public/models/models/.gitkeep +0 -0
- package/public/site.webmanifest +25 -0
package/bin/vibeteam
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# VibTeam CLI Wrapper
|
|
4
|
+
#
|
|
5
|
+
# This wrapper ensures PATH is set up correctly on macOS before running
|
|
6
|
+
# the Node.js CLI. On Linux and properly-configured macOS, it simply
|
|
7
|
+
# passes through to Node.js.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
# =============================================================================
|
|
11
|
+
# macOS PATH Fix
|
|
12
|
+
# On macOS, the shell environment may not include Homebrew paths.
|
|
13
|
+
# Add them defensively so node, jq, tmux etc. can be found.
|
|
14
|
+
# =============================================================================
|
|
15
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
16
|
+
# Apple Silicon Homebrew
|
|
17
|
+
[ -d "/opt/homebrew/bin" ] && export PATH="/opt/homebrew/bin:$PATH"
|
|
18
|
+
# Intel Homebrew
|
|
19
|
+
[ -d "/usr/local/bin" ] && export PATH="/usr/local/bin:$PATH"
|
|
20
|
+
# User local bin (Claude CLI location)
|
|
21
|
+
[ -d "$HOME/.local/bin" ] && export PATH="$HOME/.local/bin:$PATH"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# Find Node.js
|
|
26
|
+
# =============================================================================
|
|
27
|
+
NODE=""
|
|
28
|
+
|
|
29
|
+
# Check PATH first - this respects nvm, fnm, volta, etc.
|
|
30
|
+
NODE=$(command -v node 2>/dev/null)
|
|
31
|
+
|
|
32
|
+
# If not in PATH, check known locations (helps on macOS with limited PATH)
|
|
33
|
+
if [ -z "$NODE" ]; then
|
|
34
|
+
for loc in \
|
|
35
|
+
"/opt/homebrew/bin/node" \
|
|
36
|
+
"/usr/local/bin/node" \
|
|
37
|
+
"$HOME/.local/bin/node" \
|
|
38
|
+
"/usr/bin/node"
|
|
39
|
+
do
|
|
40
|
+
if [ -x "$loc" ]; then
|
|
41
|
+
NODE="$loc"
|
|
42
|
+
break
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# If still not found, give a helpful error
|
|
48
|
+
if [ -z "$NODE" ]; then
|
|
49
|
+
echo "Error: Node.js not found"
|
|
50
|
+
echo ""
|
|
51
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
52
|
+
echo "On macOS, install Node.js via Homebrew:"
|
|
53
|
+
echo " brew install node"
|
|
54
|
+
echo ""
|
|
55
|
+
echo "If already installed, ensure Homebrew is in your PATH."
|
|
56
|
+
echo "Add this to your ~/.zshrc:"
|
|
57
|
+
echo " eval \"\$(/opt/homebrew/bin/brew shellenv)\""
|
|
58
|
+
else
|
|
59
|
+
echo "Install Node.js using your package manager:"
|
|
60
|
+
echo " Ubuntu/Debian: sudo apt install nodejs"
|
|
61
|
+
echo " Fedora: sudo dnf install nodejs"
|
|
62
|
+
echo " Arch: sudo pacman -S nodejs"
|
|
63
|
+
fi
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# =============================================================================
|
|
68
|
+
# Run the actual CLI
|
|
69
|
+
# =============================================================================
|
|
70
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
71
|
+
exec "$NODE" "$SCRIPT_DIR/cli.js" "$@"
|
package/data/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommitWatcher - Monitor git repositories for new commits on specified branches
|
|
3
|
+
*
|
|
4
|
+
* Similar pattern to GitStatusManager, but focused on detecting new commits
|
|
5
|
+
* to trigger Lead agents.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// CommitWatcher
|
|
14
|
+
// ============================================================================
|
|
15
|
+
export class CommitWatcher {
|
|
16
|
+
/** Map of projectId -> tracking info */
|
|
17
|
+
trackedProjects = new Map();
|
|
18
|
+
|
|
19
|
+
/** Polling interval reference */
|
|
20
|
+
pollInterval = null;
|
|
21
|
+
|
|
22
|
+
/** Callback when new commit is detected */
|
|
23
|
+
onNewCommit = null;
|
|
24
|
+
|
|
25
|
+
// Configuration
|
|
26
|
+
POLL_INTERVAL_MS = 10000; // Check every 10 seconds
|
|
27
|
+
EXEC_TIMEOUT_MS = 5000; // Timeout for git commands
|
|
28
|
+
FETCH_INTERVAL_MS = 30000; // Fetch from remote every 30 seconds
|
|
29
|
+
|
|
30
|
+
lastFetchTime = 0;
|
|
31
|
+
|
|
32
|
+
constructor() {}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set callback for new commit detection
|
|
36
|
+
* @param {Function} handler - (projectId, commitInfo) => void
|
|
37
|
+
*/
|
|
38
|
+
setCommitHandler(handler) {
|
|
39
|
+
this.onNewCommit = handler;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start tracking a project's branch for new commits
|
|
44
|
+
* @param {string} projectId - Project identifier
|
|
45
|
+
* @param {string} projectPath - Path to git repository
|
|
46
|
+
* @param {string} branch - Branch to watch (default: 'main')
|
|
47
|
+
*/
|
|
48
|
+
track(projectId, projectPath, branch = 'main') {
|
|
49
|
+
this.trackedProjects.set(projectId, {
|
|
50
|
+
path: projectPath,
|
|
51
|
+
branch,
|
|
52
|
+
lastCommit: null, // Will be set on first poll
|
|
53
|
+
lastChecked: null,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Immediately check to get baseline
|
|
57
|
+
this.checkProject(projectId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stop tracking a project
|
|
62
|
+
* @param {string} projectId
|
|
63
|
+
*/
|
|
64
|
+
untrack(projectId) {
|
|
65
|
+
this.trackedProjects.delete(projectId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if a project is being tracked
|
|
70
|
+
* @param {string} projectId
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
isTracking(projectId) {
|
|
74
|
+
return this.trackedProjects.has(projectId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get tracking info for a project
|
|
79
|
+
* @param {string} projectId
|
|
80
|
+
* @returns {Object|null}
|
|
81
|
+
*/
|
|
82
|
+
getTrackingInfo(projectId) {
|
|
83
|
+
return this.trackedProjects.get(projectId) || null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Start polling for new commits
|
|
88
|
+
*/
|
|
89
|
+
start() {
|
|
90
|
+
if (this.pollInterval) return;
|
|
91
|
+
|
|
92
|
+
this.pollInterval = setInterval(() => {
|
|
93
|
+
this.pollAll();
|
|
94
|
+
}, this.POLL_INTERVAL_MS);
|
|
95
|
+
|
|
96
|
+
// Initial poll
|
|
97
|
+
this.pollAll();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stop polling
|
|
102
|
+
*/
|
|
103
|
+
stop() {
|
|
104
|
+
if (this.pollInterval) {
|
|
105
|
+
clearInterval(this.pollInterval);
|
|
106
|
+
this.pollInterval = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Poll all tracked projects for new commits
|
|
112
|
+
*/
|
|
113
|
+
async pollAll() {
|
|
114
|
+
// Periodically fetch from remotes
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now - this.lastFetchTime > this.FETCH_INTERVAL_MS) {
|
|
117
|
+
this.lastFetchTime = now;
|
|
118
|
+
// Fetch in background for all tracked projects
|
|
119
|
+
for (const [projectId, info] of this.trackedProjects) {
|
|
120
|
+
this.execGit(['fetch', '--quiet'], info.path).catch(() => {}); // Silent fail
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check each project
|
|
125
|
+
const promises = Array.from(this.trackedProjects.keys()).map(
|
|
126
|
+
(projectId) => this.checkProject(projectId)
|
|
127
|
+
);
|
|
128
|
+
await Promise.all(promises);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check a single project for new commits
|
|
133
|
+
* @param {string} projectId
|
|
134
|
+
*/
|
|
135
|
+
async checkProject(projectId) {
|
|
136
|
+
const info = this.trackedProjects.get(projectId);
|
|
137
|
+
if (!info) return;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// Get the latest commit on the tracked branch (from remote)
|
|
141
|
+
const remoteRef = `origin/${info.branch}`;
|
|
142
|
+
const currentSha = await this.getLatestCommit(info.path, remoteRef);
|
|
143
|
+
|
|
144
|
+
info.lastChecked = Date.now();
|
|
145
|
+
|
|
146
|
+
if (!currentSha) return;
|
|
147
|
+
|
|
148
|
+
// First time - just record baseline
|
|
149
|
+
if (!info.lastCommit) {
|
|
150
|
+
info.lastCommit = currentSha;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check if commit changed
|
|
155
|
+
if (currentSha !== info.lastCommit) {
|
|
156
|
+
const previousCommit = info.lastCommit;
|
|
157
|
+
info.lastCommit = currentSha;
|
|
158
|
+
|
|
159
|
+
// Get commit details
|
|
160
|
+
const commitInfo = await this.getCommitInfo(info.path, currentSha);
|
|
161
|
+
|
|
162
|
+
if (commitInfo && this.onNewCommit) {
|
|
163
|
+
this.onNewCommit(projectId, commitInfo);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
// Silent fail - project might not have remote configured
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the latest commit SHA for a ref
|
|
173
|
+
* @param {string} path - Repository path
|
|
174
|
+
* @param {string} ref - Git ref (e.g., 'origin/main')
|
|
175
|
+
* @returns {Promise<string|null>}
|
|
176
|
+
*/
|
|
177
|
+
async getLatestCommit(path, ref) {
|
|
178
|
+
try {
|
|
179
|
+
const result = await this.execGit(
|
|
180
|
+
['log', '-1', '--format=%H', ref],
|
|
181
|
+
path
|
|
182
|
+
);
|
|
183
|
+
return result.trim() || null;
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get detailed commit information
|
|
191
|
+
* @param {string} path - Repository path
|
|
192
|
+
* @param {string} sha - Commit SHA
|
|
193
|
+
* @returns {Promise<Object|null>}
|
|
194
|
+
*/
|
|
195
|
+
async getCommitInfo(path, sha) {
|
|
196
|
+
try {
|
|
197
|
+
// Get commit details
|
|
198
|
+
const format = '%H|||%h|||%s|||%an|||%at';
|
|
199
|
+
const logResult = await this.execGit(
|
|
200
|
+
['show', '--format=' + format, '--stat', '--no-patch', sha],
|
|
201
|
+
path
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const lines = logResult.trim().split('\n');
|
|
205
|
+
if (lines.length === 0) return null;
|
|
206
|
+
|
|
207
|
+
const [fullSha, shortSha, message, author, timestamp] = lines[0].split('|||');
|
|
208
|
+
|
|
209
|
+
// Get diff stats
|
|
210
|
+
const diffResult = await this.execGit(
|
|
211
|
+
['diff', '--shortstat', `${sha}^..${sha}`],
|
|
212
|
+
path
|
|
213
|
+
).catch(() => '');
|
|
214
|
+
|
|
215
|
+
let additions = 0;
|
|
216
|
+
let deletions = 0;
|
|
217
|
+
const addMatch = diffResult.match(/(\d+) insertion/);
|
|
218
|
+
const delMatch = diffResult.match(/(\d+) deletion/);
|
|
219
|
+
if (addMatch) additions = parseInt(addMatch[1], 10);
|
|
220
|
+
if (delMatch) deletions = parseInt(delMatch[1], 10);
|
|
221
|
+
|
|
222
|
+
// Get list of changed files
|
|
223
|
+
const filesResult = await this.execGit(
|
|
224
|
+
['diff', '--name-only', `${sha}^..${sha}`],
|
|
225
|
+
path
|
|
226
|
+
).catch(() => '');
|
|
227
|
+
|
|
228
|
+
const filesChanged = filesResult.trim().split('\n').filter(Boolean);
|
|
229
|
+
|
|
230
|
+
// Get the branch this commit is on
|
|
231
|
+
const branchResult = await this.execGit(
|
|
232
|
+
['branch', '-r', '--contains', sha],
|
|
233
|
+
path
|
|
234
|
+
).catch(() => '');
|
|
235
|
+
|
|
236
|
+
// Extract branch name (first one if multiple)
|
|
237
|
+
let branch = 'main';
|
|
238
|
+
const branchMatch = branchResult.match(/origin\/(\S+)/);
|
|
239
|
+
if (branchMatch) {
|
|
240
|
+
branch = branchMatch[1];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
sha: fullSha,
|
|
245
|
+
shortSha,
|
|
246
|
+
message,
|
|
247
|
+
author,
|
|
248
|
+
timestamp: parseInt(timestamp, 10) * 1000, // Convert to ms
|
|
249
|
+
branch,
|
|
250
|
+
filesChanged,
|
|
251
|
+
additions,
|
|
252
|
+
deletions,
|
|
253
|
+
};
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Force check a project (useful after manual trigger)
|
|
261
|
+
* @param {string} projectId
|
|
262
|
+
* @returns {Promise<Object|null>} - Returns commit info if found
|
|
263
|
+
*/
|
|
264
|
+
async forceCheck(projectId) {
|
|
265
|
+
const info = this.trackedProjects.get(projectId);
|
|
266
|
+
if (!info) return null;
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// Fetch first
|
|
270
|
+
await this.execGit(['fetch', '--quiet'], info.path);
|
|
271
|
+
|
|
272
|
+
// Get latest commit
|
|
273
|
+
const remoteRef = `origin/${info.branch}`;
|
|
274
|
+
const currentSha = await this.getLatestCommit(info.path, remoteRef);
|
|
275
|
+
|
|
276
|
+
if (!currentSha) return null;
|
|
277
|
+
|
|
278
|
+
return this.getCommitInfo(info.path, currentSha);
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get commit info for a specific SHA in a project
|
|
286
|
+
* @param {string} path - Repository path
|
|
287
|
+
* @param {string} sha - Commit SHA
|
|
288
|
+
* @returns {Promise<Object|null>}
|
|
289
|
+
*/
|
|
290
|
+
async getCommitInfoForPath(path, sha) {
|
|
291
|
+
return this.getCommitInfo(path, sha);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Execute a git command in a directory
|
|
296
|
+
* @param {string[]} args - Git command arguments
|
|
297
|
+
* @param {string} cwd - Working directory
|
|
298
|
+
* @returns {Promise<string>}
|
|
299
|
+
*/
|
|
300
|
+
async execGit(args, cwd) {
|
|
301
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
302
|
+
cwd,
|
|
303
|
+
timeout: this.EXEC_TIMEOUT_MS,
|
|
304
|
+
});
|
|
305
|
+
return stdout;
|
|
306
|
+
}
|
|
307
|
+
}
|