vibehub-cli 1.0.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/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +125 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/capture-prompts.d.ts +3 -0
- package/dist/commands/capture-prompts.d.ts.map +1 -0
- package/dist/commands/capture-prompts.js +137 -0
- package/dist/commands/capture-prompts.js.map +1 -0
- package/dist/commands/commit.d.ts +3 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +132 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +113 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +182 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +99 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +142 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +179 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/set.d.ts +3 -0
- package/dist/commands/set.d.ts.map +1 -0
- package/dist/commands/set.js +134 -0
- package/dist/commands/set.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +79 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ai-capture.d.ts +21 -0
- package/dist/lib/ai-capture.d.ts.map +1 -0
- package/dist/lib/ai-capture.js +242 -0
- package/dist/lib/ai-capture.js.map +1 -0
- package/dist/lib/cloud-service.d.ts +26 -0
- package/dist/lib/cloud-service.d.ts.map +1 -0
- package/dist/lib/cloud-service.js +200 -0
- package/dist/lib/cloud-service.js.map +1 -0
- package/dist/lib/cursor-conversation-capture.d.ts +10 -0
- package/dist/lib/cursor-conversation-capture.d.ts.map +1 -0
- package/dist/lib/cursor-conversation-capture.js +117 -0
- package/dist/lib/cursor-conversation-capture.js.map +1 -0
- package/dist/lib/prompt-capture-manager.d.ts +33 -0
- package/dist/lib/prompt-capture-manager.d.ts.map +1 -0
- package/dist/lib/prompt-capture-manager.js +178 -0
- package/dist/lib/prompt-capture-manager.js.map +1 -0
- package/dist/lib/vibehub-manager.d.ts +172 -0
- package/dist/lib/vibehub-manager.d.ts.map +1 -0
- package/dist/lib/vibehub-manager.js +855 -0
- package/dist/lib/vibehub-manager.js.map +1 -0
- package/dist/src/commands/set.d.ts +3 -0
- package/dist/src/commands/set.d.ts.map +1 -0
- package/dist/src/commands/set.js +125 -0
- package/dist/src/commands/set.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
export class VibeHubManager {
|
|
5
|
+
constructor(repoPath) {
|
|
6
|
+
this.fileWatchers = new Map();
|
|
7
|
+
this.repoPath = repoPath;
|
|
8
|
+
this.configPath = path.join(repoPath, '.vibehub', 'vibehub.json');
|
|
9
|
+
this.sessionsPath = path.join(repoPath, '.vibehub', 'sessions');
|
|
10
|
+
this.commitsPath = path.join(repoPath, '.vibehub', 'commits');
|
|
11
|
+
}
|
|
12
|
+
async isInitialized() {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(this.configPath);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async init(config) {
|
|
22
|
+
const vibehubDir = path.join(this.repoPath, '.vibehub');
|
|
23
|
+
// Create .vibehub directory structure matching backend expectations
|
|
24
|
+
await fs.ensureDir(vibehubDir);
|
|
25
|
+
await fs.ensureDir(path.join(vibehubDir, 'workspace'));
|
|
26
|
+
await fs.ensureDir(path.join(vibehubDir, 'sessions'));
|
|
27
|
+
await fs.ensureDir(path.join(vibehubDir, 'config'));
|
|
28
|
+
await fs.ensureDir(path.join(vibehubDir, 'exports'));
|
|
29
|
+
await fs.ensureDir(this.commitsPath);
|
|
30
|
+
// Create vibehub.json configuration (matches backend structure)
|
|
31
|
+
const projectId = `${config.author.split('@')[0]}-${config.projectName}-${Date.now()}`;
|
|
32
|
+
const vibehubConfig = {
|
|
33
|
+
projectId: projectId,
|
|
34
|
+
projectName: config.projectName,
|
|
35
|
+
description: config.description || '',
|
|
36
|
+
localPath: this.repoPath,
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
lastUpdated: new Date().toISOString(),
|
|
39
|
+
version: '1.0.0',
|
|
40
|
+
author: {
|
|
41
|
+
name: config.author.split('@')[0],
|
|
42
|
+
email: config.author
|
|
43
|
+
},
|
|
44
|
+
config: {
|
|
45
|
+
autoSync: config.enableAutoSync,
|
|
46
|
+
capturePrompts: true,
|
|
47
|
+
workspaceTracking: true
|
|
48
|
+
},
|
|
49
|
+
workspace: {
|
|
50
|
+
cursorWorkspaceId: null, // Will be updated after detection
|
|
51
|
+
cursorWorkspacePath: null,
|
|
52
|
+
isActive: false
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
await fs.writeJson(this.configPath, vibehubConfig, { spaces: 2 });
|
|
56
|
+
// Try to detect and map Cursor workspace
|
|
57
|
+
await this.detectAndMapCursorWorkspace();
|
|
58
|
+
// Create initial commit
|
|
59
|
+
await this.createInitialCommit(config.author);
|
|
60
|
+
}
|
|
61
|
+
async getConfig() {
|
|
62
|
+
return await fs.readJson(this.configPath);
|
|
63
|
+
}
|
|
64
|
+
async updateConfig(updates) {
|
|
65
|
+
const config = await this.getConfig();
|
|
66
|
+
const updatedConfig = { ...config, ...updates };
|
|
67
|
+
await fs.writeJson(this.configPath, updatedConfig, { spaces: 2 });
|
|
68
|
+
}
|
|
69
|
+
async ensureAuthorInfo(email) {
|
|
70
|
+
const config = await this.getConfig();
|
|
71
|
+
// If author info is missing, add it
|
|
72
|
+
if (!config.author) {
|
|
73
|
+
await this.updateConfig({
|
|
74
|
+
author: {
|
|
75
|
+
name: email.split('@')[0],
|
|
76
|
+
email: email
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async createSession(session) {
|
|
82
|
+
const sessionId = crypto.randomUUID();
|
|
83
|
+
const fullSession = {
|
|
84
|
+
...session,
|
|
85
|
+
id: sessionId,
|
|
86
|
+
timestamp: new Date().toISOString()
|
|
87
|
+
};
|
|
88
|
+
const sessionFile = path.join(this.sessionsPath, `${sessionId}.json`);
|
|
89
|
+
await fs.writeJson(sessionFile, fullSession, { spaces: 2 });
|
|
90
|
+
return sessionId;
|
|
91
|
+
}
|
|
92
|
+
async getSession(sessionId) {
|
|
93
|
+
try {
|
|
94
|
+
const sessionFile = path.join(this.sessionsPath, `${sessionId}.json`);
|
|
95
|
+
return await fs.readJson(sessionFile);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async getAllSessions() {
|
|
102
|
+
try {
|
|
103
|
+
const files = await fs.readdir(this.sessionsPath);
|
|
104
|
+
const sessions = [];
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
if (file.endsWith('.json')) {
|
|
107
|
+
const sessionData = await fs.readJson(path.join(this.sessionsPath, file));
|
|
108
|
+
// Handle prompts.json file which contains an array of prompts
|
|
109
|
+
if (file === 'prompts.json' && sessionData.prompts) {
|
|
110
|
+
for (const prompt of sessionData.prompts) {
|
|
111
|
+
sessions.push({
|
|
112
|
+
id: `prompt-${prompt.id}`,
|
|
113
|
+
timestamp: prompt.timestamp_iso || new Date(prompt.timestamp).toISOString(),
|
|
114
|
+
prompt: prompt.text,
|
|
115
|
+
response: '',
|
|
116
|
+
files: [],
|
|
117
|
+
metadata: prompt.metadata || {}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Handle regular session files
|
|
123
|
+
sessions.push(sessionData);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return sessions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async createCommit(commit) {
|
|
134
|
+
const commitId = crypto.randomUUID();
|
|
135
|
+
const fullCommit = {
|
|
136
|
+
...commit,
|
|
137
|
+
id: commitId,
|
|
138
|
+
timestamp: new Date().toISOString()
|
|
139
|
+
};
|
|
140
|
+
const commitFile = path.join(this.commitsPath, `${commitId}.json`);
|
|
141
|
+
await fs.writeJson(commitFile, fullCommit, { spaces: 2 });
|
|
142
|
+
return commitId;
|
|
143
|
+
}
|
|
144
|
+
async getCommit(commitId) {
|
|
145
|
+
try {
|
|
146
|
+
const commitFile = path.join(this.commitsPath, `${commitId}.json`);
|
|
147
|
+
return await fs.readJson(commitFile);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async getAllCommits() {
|
|
154
|
+
try {
|
|
155
|
+
const files = await fs.readdir(this.commitsPath);
|
|
156
|
+
const commits = [];
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
if (file.endsWith('.json')) {
|
|
159
|
+
const commit = await fs.readJson(path.join(this.commitsPath, file));
|
|
160
|
+
commits.push(commit);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return commits.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async createInitialCommit(author) {
|
|
170
|
+
await this.createCommit({
|
|
171
|
+
message: 'Initial VibeHub repository',
|
|
172
|
+
author,
|
|
173
|
+
files: [],
|
|
174
|
+
changes: {}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async updateGitignore() {
|
|
178
|
+
const gitignorePath = path.join(this.repoPath, '.gitignore');
|
|
179
|
+
const vibehubEntry = '\n# VibeHub\n.vibehub/\n';
|
|
180
|
+
try {
|
|
181
|
+
const existingContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
182
|
+
if (!existingContent.includes('.vibehub/')) {
|
|
183
|
+
await fs.appendFile(gitignorePath, vibehubEntry);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// .gitignore doesn't exist, create it
|
|
188
|
+
await fs.writeFile(gitignorePath, vibehubEntry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async getStatus() {
|
|
192
|
+
const initialized = await this.isInitialized();
|
|
193
|
+
if (!initialized) {
|
|
194
|
+
return {
|
|
195
|
+
initialized: false,
|
|
196
|
+
sessionCount: 0,
|
|
197
|
+
commitCount: 0
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const config = await this.getConfig();
|
|
201
|
+
const sessions = await this.getAllSessions();
|
|
202
|
+
const commits = await this.getAllCommits();
|
|
203
|
+
let lastActivity;
|
|
204
|
+
if (sessions.length > 0 || commits.length > 0) {
|
|
205
|
+
const lastSession = sessions[0];
|
|
206
|
+
const lastCommit = commits[0];
|
|
207
|
+
if (lastSession && lastCommit) {
|
|
208
|
+
lastActivity = new Date(Math.max(new Date(lastSession.timestamp).getTime(), new Date(lastCommit.timestamp).getTime())).toISOString();
|
|
209
|
+
}
|
|
210
|
+
else if (lastSession) {
|
|
211
|
+
lastActivity = lastSession.timestamp;
|
|
212
|
+
}
|
|
213
|
+
else if (lastCommit) {
|
|
214
|
+
lastActivity = lastCommit.timestamp;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
initialized: true,
|
|
219
|
+
config,
|
|
220
|
+
sessionCount: sessions.length,
|
|
221
|
+
commitCount: commits.length,
|
|
222
|
+
lastActivity
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Detect and map Cursor workspace to this project
|
|
227
|
+
*/
|
|
228
|
+
async detectAndMapCursorWorkspace() {
|
|
229
|
+
try {
|
|
230
|
+
const cursorWorkspacePath = this.getCursorWorkspaceStoragePath();
|
|
231
|
+
if (!cursorWorkspacePath) {
|
|
232
|
+
console.log('⚠️ Cursor workspace storage not found');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Find workspace folders that contain this project path
|
|
236
|
+
const workspaceFolders = await fs.readdir(cursorWorkspacePath);
|
|
237
|
+
for (const folder of workspaceFolders) {
|
|
238
|
+
const workspaceJsonPath = path.join(cursorWorkspacePath, folder, 'workspace.json');
|
|
239
|
+
try {
|
|
240
|
+
const workspaceJson = await fs.readJson(workspaceJsonPath);
|
|
241
|
+
const workspacePath = workspaceJson.folder;
|
|
242
|
+
// Handle both file:// URLs and regular paths
|
|
243
|
+
let normalizedWorkspacePath = workspacePath;
|
|
244
|
+
if (workspacePath.startsWith('file://')) {
|
|
245
|
+
normalizedWorkspacePath = workspacePath.replace('file://', '');
|
|
246
|
+
}
|
|
247
|
+
if (normalizedWorkspacePath === this.repoPath) {
|
|
248
|
+
// Found the matching workspace!
|
|
249
|
+
await this.createWorkspaceMapping(folder, workspaceJson);
|
|
250
|
+
console.log(`✅ Mapped Cursor workspace: ${folder}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// Skip folders without workspace.json
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
console.log('⚠️ No matching Cursor workspace found for this project');
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
console.log('⚠️ Could not detect Cursor workspace:', error.message);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get Cursor workspace storage path based on OS
|
|
267
|
+
*/
|
|
268
|
+
getCursorWorkspaceStoragePath() {
|
|
269
|
+
const platform = process.platform;
|
|
270
|
+
switch (platform) {
|
|
271
|
+
case 'darwin': // macOS
|
|
272
|
+
return path.join(process.env.HOME || '', 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage');
|
|
273
|
+
case 'win32': // Windows
|
|
274
|
+
return path.join(process.env.APPDATA || '', 'Cursor', 'User', 'workspaceStorage');
|
|
275
|
+
case 'linux': // Linux
|
|
276
|
+
return path.join(process.env.HOME || '', '.config', 'Cursor', 'User', 'workspaceStorage');
|
|
277
|
+
default:
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Create workspace mapping file
|
|
283
|
+
*/
|
|
284
|
+
async createWorkspaceMapping(workspaceId, workspaceJson) {
|
|
285
|
+
const workspaceDir = path.join(this.repoPath, '.vibehub', 'workspace');
|
|
286
|
+
const mappingPath = path.join(workspaceDir, `${workspaceId}.json`);
|
|
287
|
+
// Get actual file system timestamps for the workspace folder
|
|
288
|
+
const workspaceFolderPath = path.join(this.getCursorWorkspaceStoragePath(), workspaceId);
|
|
289
|
+
const stats = await fs.stat(workspaceFolderPath);
|
|
290
|
+
// Use birthtime (creation time) for createdAt, current time for lastAccessed
|
|
291
|
+
const createdAt = stats.birthtime || stats.ctime;
|
|
292
|
+
const lastAccessed = new Date();
|
|
293
|
+
const mapping = {
|
|
294
|
+
workspaceId: workspaceId,
|
|
295
|
+
localPath: this.repoPath,
|
|
296
|
+
cursorWorkspacePath: workspaceFolderPath,
|
|
297
|
+
createdAt: createdAt.toISOString(),
|
|
298
|
+
createdAtTimestamp: createdAt.getTime(),
|
|
299
|
+
lastAccessed: lastAccessed.toISOString(),
|
|
300
|
+
isActive: true,
|
|
301
|
+
metadata: {
|
|
302
|
+
projectName: path.basename(this.repoPath),
|
|
303
|
+
workspaceType: 'cursor',
|
|
304
|
+
gitRemote: await this.getGitRemote(),
|
|
305
|
+
branch: await this.getGitBranch()
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
await fs.writeJson(mappingPath, mapping, { spaces: 2 });
|
|
309
|
+
// Update the main vibehub.json with workspace information
|
|
310
|
+
await this.updateVibehubConfigWithWorkspace(workspaceId, mapping);
|
|
311
|
+
// Extract prompts and generations from the workspace
|
|
312
|
+
await this.extractPromptsAndGenerations(workspaceId);
|
|
313
|
+
// Start real-time file watching for auto-sync
|
|
314
|
+
await this.startWorkspaceWatching(workspaceId);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get git remote URL
|
|
318
|
+
*/
|
|
319
|
+
async getGitRemote() {
|
|
320
|
+
try {
|
|
321
|
+
const { execSync } = await import('child_process');
|
|
322
|
+
const remote = execSync('git config --get remote.origin.url', { cwd: this.repoPath, encoding: 'utf8' });
|
|
323
|
+
return remote.trim();
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get current git branch
|
|
331
|
+
*/
|
|
332
|
+
async getGitBranch() {
|
|
333
|
+
try {
|
|
334
|
+
const { execSync } = await import('child_process');
|
|
335
|
+
const branch = execSync('git branch --show-current', { cwd: this.repoPath, encoding: 'utf8' });
|
|
336
|
+
return branch.trim();
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Extract prompts and generations from Cursor workspace
|
|
344
|
+
*/
|
|
345
|
+
async extractPromptsAndGenerations(workspaceId) {
|
|
346
|
+
try {
|
|
347
|
+
console.log('📝 Extracting prompts and generations...');
|
|
348
|
+
const workspacePath = path.join(this.getCursorWorkspaceStoragePath(), workspaceId);
|
|
349
|
+
const stateDbPath = path.join(workspacePath, 'state.vscdb');
|
|
350
|
+
if (!await fs.pathExists(stateDbPath)) {
|
|
351
|
+
console.log('⚠️ No state.vscdb found in workspace');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
// Extract prompts and generations using better-sqlite3
|
|
355
|
+
const Database = (await import('better-sqlite3')).default;
|
|
356
|
+
const db = new Database(stateDbPath, { readonly: true });
|
|
357
|
+
// Extract prompts
|
|
358
|
+
const promptsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.prompts'").get();
|
|
359
|
+
const generationsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.generations'").get();
|
|
360
|
+
db.close();
|
|
361
|
+
if (!promptsResult && !generationsResult) {
|
|
362
|
+
console.log('⚠️ No prompts or generations found in workspace');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Parse and store prompts
|
|
366
|
+
if (promptsResult) {
|
|
367
|
+
await this.storePrompts(workspaceId, JSON.parse(promptsResult.value));
|
|
368
|
+
}
|
|
369
|
+
// Parse and store generations
|
|
370
|
+
if (generationsResult) {
|
|
371
|
+
await this.storeGenerations(workspaceId, JSON.parse(generationsResult.value));
|
|
372
|
+
}
|
|
373
|
+
// Generate CSV with prompts and timestamps
|
|
374
|
+
await this.generatePromptsCsv(workspaceId);
|
|
375
|
+
console.log('✅ Successfully extracted prompts and generations');
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.log('⚠️ Could not extract prompts/generations:', error.message);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Store prompts in sessions directory
|
|
383
|
+
*/
|
|
384
|
+
async storePrompts(workspaceId, prompts) {
|
|
385
|
+
const sessionsDir = path.join(this.repoPath, '.vibehub', 'sessions');
|
|
386
|
+
const promptsFile = path.join(sessionsDir, 'prompts.json');
|
|
387
|
+
// Load existing prompts or create new structure
|
|
388
|
+
let promptsData;
|
|
389
|
+
try {
|
|
390
|
+
promptsData = await fs.readJson(promptsFile);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
promptsData = {
|
|
394
|
+
version: "1.0",
|
|
395
|
+
created_at: new Date().toISOString(),
|
|
396
|
+
last_updated: new Date().toISOString(),
|
|
397
|
+
total_prompts: 0,
|
|
398
|
+
prompts: []
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
// Add new prompts
|
|
402
|
+
for (const prompt of prompts) {
|
|
403
|
+
if (prompt.text && !promptsData.prompts.some((p) => p.text === prompt.text)) {
|
|
404
|
+
promptsData.prompts.push({
|
|
405
|
+
id: promptsData.prompts.length + 1, // Use integer ID
|
|
406
|
+
text: prompt.text,
|
|
407
|
+
timestamp: prompt.unixMs || Date.now(),
|
|
408
|
+
timestamp_iso: new Date(prompt.unixMs || Date.now()).toISOString(),
|
|
409
|
+
source: 'cursor-database',
|
|
410
|
+
workspace_id: workspaceId,
|
|
411
|
+
metadata: {
|
|
412
|
+
commandType: prompt.commandType,
|
|
413
|
+
index: prompt.index
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
promptsData.total_prompts = promptsData.prompts.length;
|
|
419
|
+
promptsData.last_updated = new Date().toISOString();
|
|
420
|
+
await fs.writeJson(promptsFile, promptsData, { spaces: 2 });
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Store generations in exports directory
|
|
424
|
+
*/
|
|
425
|
+
async storeGenerations(workspaceId, generations) {
|
|
426
|
+
const exportsDir = path.join(this.repoPath, '.vibehub', 'workspace', 'exports', `workspace-${workspaceId}`);
|
|
427
|
+
await fs.ensureDir(exportsDir);
|
|
428
|
+
const generationsFile = path.join(exportsDir, 'generations.jsonl');
|
|
429
|
+
const generationsData = generations.map((gen) => ({
|
|
430
|
+
id: gen.id || `gen_${gen.unixMs || Date.now()}`,
|
|
431
|
+
textDescription: gen.textDescription || gen.text || '',
|
|
432
|
+
unixMs: gen.unixMs || Date.now(),
|
|
433
|
+
timestamp: new Date(gen.unixMs || Date.now()).toISOString(),
|
|
434
|
+
type: gen.type || 'generation',
|
|
435
|
+
metadata: gen
|
|
436
|
+
}));
|
|
437
|
+
await fs.writeJson(generationsFile, generationsData, { spaces: 2 });
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Generate CSV with prompts and timestamps
|
|
441
|
+
*/
|
|
442
|
+
async generatePromptsCsv(workspaceId) {
|
|
443
|
+
try {
|
|
444
|
+
const sessionsDir = path.join(this.repoPath, '.vibehub', 'sessions');
|
|
445
|
+
const promptsFile = path.join(sessionsDir, 'prompts.json');
|
|
446
|
+
const exportsDir = path.join(this.repoPath, '.vibehub', 'workspace', 'exports', `workspace-${workspaceId}`);
|
|
447
|
+
await fs.ensureDir(exportsDir);
|
|
448
|
+
// Load prompts
|
|
449
|
+
const promptsData = await fs.readJson(promptsFile);
|
|
450
|
+
const workspacePrompts = promptsData.prompts.filter((p) => p.workspace_id === workspaceId);
|
|
451
|
+
// Create CSV content with prompt ID
|
|
452
|
+
const csvRows = ['prompt_id,timestamp,prompt_text,workspace_id'];
|
|
453
|
+
for (const prompt of workspacePrompts) {
|
|
454
|
+
const timestamp = new Date(prompt.timestamp).toISOString();
|
|
455
|
+
const promptText = prompt.text.replace(/"/g, '""').replace(/\n/g, ' ');
|
|
456
|
+
csvRows.push(`"${prompt.id}","${timestamp}","${promptText}","${workspaceId}"`);
|
|
457
|
+
}
|
|
458
|
+
const csvContent = csvRows.join('\n');
|
|
459
|
+
const csvFile = path.join(exportsDir, 'prompts_with_timestamps.csv');
|
|
460
|
+
await fs.writeFile(csvFile, csvContent);
|
|
461
|
+
console.log(`📊 Generated CSV with ${workspacePrompts.length} prompts`);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
console.log('⚠️ Could not generate CSV:', error.message);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Update vibehub.json with workspace information
|
|
469
|
+
*/
|
|
470
|
+
async updateVibehubConfigWithWorkspace(workspaceId, mapping) {
|
|
471
|
+
try {
|
|
472
|
+
const vibehubConfig = await fs.readJson(this.configPath);
|
|
473
|
+
vibehubConfig.workspace = {
|
|
474
|
+
cursorWorkspaceId: workspaceId,
|
|
475
|
+
cursorWorkspacePath: mapping.cursorWorkspacePath,
|
|
476
|
+
isActive: mapping.isActive,
|
|
477
|
+
createdAt: mapping.createdAt,
|
|
478
|
+
lastAccessed: mapping.lastAccessed
|
|
479
|
+
};
|
|
480
|
+
vibehubConfig.lastUpdated = new Date().toISOString();
|
|
481
|
+
await fs.writeJson(this.configPath, vibehubConfig, { spaces: 2 });
|
|
482
|
+
console.log(`✅ Updated vibehub.json with workspace: ${workspaceId}`);
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
console.log('⚠️ Could not update vibehub.json:', error.message);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Start real-time file watching for auto-sync (background service)
|
|
490
|
+
*/
|
|
491
|
+
async startWorkspaceWatching(workspaceId) {
|
|
492
|
+
try {
|
|
493
|
+
const workspacePath = path.join(this.getCursorWorkspaceStoragePath(), workspaceId);
|
|
494
|
+
const stateDbPath = path.join(workspacePath, 'state.vscdb');
|
|
495
|
+
if (!await fs.pathExists(stateDbPath)) {
|
|
496
|
+
console.log('⚠️ No state.vscdb found for watching');
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Auto-sync is now integrated into 'vibe add' command
|
|
500
|
+
console.log(`👁️ Auto-sync enabled for workspace: ${workspaceId}`);
|
|
501
|
+
console.log(`📝 New prompts will be synced when you run 'vibe add'`);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
console.log('⚠️ Could not setup auto-sync:', error.message);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Create a background watcher script
|
|
509
|
+
*/
|
|
510
|
+
async createBackgroundWatcherScript(workspaceId, stateDbPath) {
|
|
511
|
+
const scriptDir = path.join(this.repoPath, '.vibehub', 'scripts');
|
|
512
|
+
await fs.ensureDir(scriptDir);
|
|
513
|
+
const scriptPath = path.join(scriptDir, 'watcher.js');
|
|
514
|
+
const scriptContent = `
|
|
515
|
+
const chokidar = require('chokidar');
|
|
516
|
+
const fs = require('fs-extra');
|
|
517
|
+
const path = require('path');
|
|
518
|
+
const Database = require('better-sqlite3');
|
|
519
|
+
|
|
520
|
+
const workspaceId = '${workspaceId}';
|
|
521
|
+
const stateDbPath = '${stateDbPath}';
|
|
522
|
+
const repoPath = '${this.repoPath}';
|
|
523
|
+
|
|
524
|
+
async function logToFile(message) {
|
|
525
|
+
try {
|
|
526
|
+
const logDir = path.join(repoPath, '.vibehub', 'logs');
|
|
527
|
+
await fs.ensureDir(logDir);
|
|
528
|
+
const logFile = path.join(logDir, 'vibehub-sync.log');
|
|
529
|
+
const timestamp = new Date().toISOString();
|
|
530
|
+
await fs.appendFile(logFile, \`[\${timestamp}] \${message}\\n\`);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
// Silently fail if logging fails
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function syncNewData() {
|
|
537
|
+
try {
|
|
538
|
+
if (!await fs.pathExists(stateDbPath)) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Load existing prompts
|
|
543
|
+
const sessionsDir = path.join(repoPath, '.vibehub', 'sessions');
|
|
544
|
+
const promptsFile = path.join(sessionsDir, 'prompts.json');
|
|
545
|
+
let existingPromptsData;
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
existingPromptsData = await fs.readJson(promptsFile);
|
|
549
|
+
} catch {
|
|
550
|
+
existingPromptsData = {
|
|
551
|
+
version: "1.0",
|
|
552
|
+
created_at: new Date().toISOString(),
|
|
553
|
+
last_updated: new Date().toISOString(),
|
|
554
|
+
total_prompts: 0,
|
|
555
|
+
prompts: []
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Extract new prompts
|
|
560
|
+
const db = new Database(stateDbPath, { readonly: true });
|
|
561
|
+
const promptsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.prompts'").get();
|
|
562
|
+
const generationsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.generations'").get();
|
|
563
|
+
db.close();
|
|
564
|
+
|
|
565
|
+
let newPromptsCount = 0;
|
|
566
|
+
|
|
567
|
+
// Process new prompts
|
|
568
|
+
if (promptsResult) {
|
|
569
|
+
const prompts = JSON.parse(promptsResult.value);
|
|
570
|
+
for (const prompt of prompts) {
|
|
571
|
+
if (prompt.text) {
|
|
572
|
+
const existingPrompt = existingPromptsData.prompts.find(p => p.text === prompt.text);
|
|
573
|
+
if (!existingPrompt) {
|
|
574
|
+
existingPromptsData.prompts.push({
|
|
575
|
+
id: existingPromptsData.prompts.length + 1,
|
|
576
|
+
text: prompt.text,
|
|
577
|
+
timestamp: prompt.unixMs || Date.now(),
|
|
578
|
+
timestamp_iso: new Date(prompt.unixMs || Date.now()).toISOString(),
|
|
579
|
+
source: 'cursor-database',
|
|
580
|
+
workspace_id: workspaceId,
|
|
581
|
+
metadata: {
|
|
582
|
+
commandType: prompt.commandType,
|
|
583
|
+
index: prompt.index
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
newPromptsCount++;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Update prompts file
|
|
593
|
+
if (newPromptsCount > 0) {
|
|
594
|
+
existingPromptsData.total_prompts = existingPromptsData.prompts.length;
|
|
595
|
+
existingPromptsData.last_updated = new Date().toISOString();
|
|
596
|
+
await fs.writeJson(promptsFile, existingPromptsData, { spaces: 2 });
|
|
597
|
+
|
|
598
|
+
// Regenerate CSV
|
|
599
|
+
await generatePromptsCsv();
|
|
600
|
+
|
|
601
|
+
await logToFile(\`📝 Added \${newPromptsCount} new prompts\`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
} catch (error) {
|
|
605
|
+
await logToFile(\`⚠️ Sync failed: \${error.message}\`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function generatePromptsCsv() {
|
|
610
|
+
try {
|
|
611
|
+
const sessionsDir = path.join(repoPath, '.vibehub', 'sessions');
|
|
612
|
+
const promptsFile = path.join(sessionsDir, 'prompts.json');
|
|
613
|
+
const exportsDir = path.join(repoPath, '.vibehub', 'workspace', 'exports', \`workspace-\${workspaceId}\`);
|
|
614
|
+
|
|
615
|
+
await fs.ensureDir(exportsDir);
|
|
616
|
+
|
|
617
|
+
const promptsData = await fs.readJson(promptsFile);
|
|
618
|
+
const workspacePrompts = promptsData.prompts.filter(p => p.workspace_id === workspaceId);
|
|
619
|
+
|
|
620
|
+
const csvRows = ['prompt_id,timestamp,prompt_text,workspace_id'];
|
|
621
|
+
|
|
622
|
+
for (const prompt of workspacePrompts) {
|
|
623
|
+
const timestamp = new Date(prompt.timestamp).toISOString();
|
|
624
|
+
const promptText = prompt.text.replace(/"/g, '""').replace(/\\n/g, ' ');
|
|
625
|
+
csvRows.push(\`"\${prompt.id}","\${timestamp}","\${promptText}","\${workspaceId}"\`);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const csvContent = csvRows.join('\\n');
|
|
629
|
+
const csvFile = path.join(exportsDir, 'prompts_with_timestamps.csv');
|
|
630
|
+
await fs.writeFile(csvFile, csvContent);
|
|
631
|
+
|
|
632
|
+
} catch (error) {
|
|
633
|
+
await logToFile(\`⚠️ Could not generate CSV: \${error.message}\`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Start watching
|
|
638
|
+
const watcher = chokidar.watch(stateDbPath, {
|
|
639
|
+
persistent: true,
|
|
640
|
+
ignoreInitial: true,
|
|
641
|
+
awaitWriteFinish: {
|
|
642
|
+
stabilityThreshold: 2000,
|
|
643
|
+
pollInterval: 100
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
let debounceTimer;
|
|
648
|
+
|
|
649
|
+
watcher.on('change', async (path) => {
|
|
650
|
+
const logMessage = \`🔄 Detected change in \${path}\`;
|
|
651
|
+
await logToFile(logMessage);
|
|
652
|
+
|
|
653
|
+
clearTimeout(debounceTimer);
|
|
654
|
+
debounceTimer = setTimeout(async () => {
|
|
655
|
+
try {
|
|
656
|
+
await logToFile('🔄 Syncing new prompts and generations...');
|
|
657
|
+
await syncNewData();
|
|
658
|
+
await logToFile('✅ Auto-sync completed');
|
|
659
|
+
} catch (error) {
|
|
660
|
+
await logToFile(\`⚠️ Auto-sync failed: \${error.message}\`);
|
|
661
|
+
}
|
|
662
|
+
}, 1000);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
watcher.on('error', async (error) => {
|
|
666
|
+
await logToFile(\`⚠️ File watcher error: \${error.message}\`);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
logToFile('👁️ Background watcher started').catch(console.error);
|
|
670
|
+
`;
|
|
671
|
+
await fs.writeFile(scriptPath, scriptContent);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Start the background process
|
|
675
|
+
*/
|
|
676
|
+
async startBackgroundProcess(workspaceId) {
|
|
677
|
+
try {
|
|
678
|
+
const { spawn } = await import('child_process');
|
|
679
|
+
const scriptPath = path.join(this.repoPath, '.vibehub', 'scripts', 'watcher.js');
|
|
680
|
+
// Start the watcher in a detached process
|
|
681
|
+
const child = spawn('node', [scriptPath], {
|
|
682
|
+
detached: true,
|
|
683
|
+
stdio: 'ignore'
|
|
684
|
+
});
|
|
685
|
+
// Store the process ID for cleanup
|
|
686
|
+
const pidFile = path.join(this.repoPath, '.vibehub', 'watcher.pid');
|
|
687
|
+
if (child.pid) {
|
|
688
|
+
await fs.writeFile(pidFile, child.pid.toString());
|
|
689
|
+
}
|
|
690
|
+
// Unref the child process so it doesn't keep the parent alive
|
|
691
|
+
child.unref();
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
console.log('⚠️ Could not start background process:', error.message);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Log messages to a file for background operations
|
|
699
|
+
*/
|
|
700
|
+
async logToFile(message) {
|
|
701
|
+
try {
|
|
702
|
+
const logDir = path.join(this.repoPath, '.vibehub', 'logs');
|
|
703
|
+
await fs.ensureDir(logDir);
|
|
704
|
+
const logFile = path.join(logDir, 'vibehub-sync.log');
|
|
705
|
+
const timestamp = new Date().toISOString();
|
|
706
|
+
await fs.appendFile(logFile, `[${timestamp}] ${message}\n`);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
// Silently fail if logging fails
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Sync new data from Cursor workspace
|
|
714
|
+
*/
|
|
715
|
+
async syncNewData(workspaceId) {
|
|
716
|
+
try {
|
|
717
|
+
const workspacePath = path.join(this.getCursorWorkspaceStoragePath(), workspaceId);
|
|
718
|
+
const stateDbPath = path.join(workspacePath, 'state.vscdb');
|
|
719
|
+
if (!await fs.pathExists(stateDbPath)) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Load existing prompts to check for new ones
|
|
723
|
+
const sessionsDir = path.join(this.repoPath, '.vibehub', 'sessions');
|
|
724
|
+
const promptsFile = path.join(sessionsDir, 'prompts.json');
|
|
725
|
+
let existingPromptsData;
|
|
726
|
+
try {
|
|
727
|
+
existingPromptsData = await fs.readJson(promptsFile);
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
existingPromptsData = {
|
|
731
|
+
version: "1.0",
|
|
732
|
+
created_at: new Date().toISOString(),
|
|
733
|
+
last_updated: new Date().toISOString(),
|
|
734
|
+
total_prompts: 0,
|
|
735
|
+
prompts: []
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const existingPromptTexts = new Set(existingPromptsData.prompts.map((p) => p.text));
|
|
739
|
+
// Extract new prompts
|
|
740
|
+
const Database = (await import('better-sqlite3')).default;
|
|
741
|
+
const db = new Database(stateDbPath, { readonly: true });
|
|
742
|
+
const promptsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.prompts'").get();
|
|
743
|
+
const generationsResult = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiService.generations'").get();
|
|
744
|
+
db.close();
|
|
745
|
+
let newPromptsCount = 0;
|
|
746
|
+
let newGenerationsCount = 0;
|
|
747
|
+
// Process new prompts
|
|
748
|
+
if (promptsResult) {
|
|
749
|
+
const prompts = JSON.parse(promptsResult.value);
|
|
750
|
+
for (const prompt of prompts) {
|
|
751
|
+
if (prompt.text) {
|
|
752
|
+
// Check if this prompt already exists by text content
|
|
753
|
+
const existingPrompt = existingPromptsData.prompts.find((p) => p.text === prompt.text);
|
|
754
|
+
if (!existingPrompt) {
|
|
755
|
+
existingPromptsData.prompts.push({
|
|
756
|
+
id: existingPromptsData.prompts.length + 1, // Use integer ID
|
|
757
|
+
text: prompt.text,
|
|
758
|
+
timestamp: prompt.unixMs || Date.now(),
|
|
759
|
+
timestamp_iso: new Date(prompt.unixMs || Date.now()).toISOString(),
|
|
760
|
+
source: 'cursor-database',
|
|
761
|
+
workspace_id: workspaceId,
|
|
762
|
+
metadata: {
|
|
763
|
+
commandType: prompt.commandType,
|
|
764
|
+
index: prompt.index
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
newPromptsCount++;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// Update prompts file
|
|
773
|
+
if (newPromptsCount > 0) {
|
|
774
|
+
existingPromptsData.total_prompts = existingPromptsData.prompts.length;
|
|
775
|
+
existingPromptsData.last_updated = new Date().toISOString();
|
|
776
|
+
await fs.writeJson(promptsFile, existingPromptsData, { spaces: 2 });
|
|
777
|
+
// Regenerate CSV with new prompts
|
|
778
|
+
await this.generatePromptsCsv(workspaceId);
|
|
779
|
+
console.log(`📝 Added ${newPromptsCount} new prompts`);
|
|
780
|
+
}
|
|
781
|
+
// Process new generations
|
|
782
|
+
if (generationsResult) {
|
|
783
|
+
const generations = JSON.parse(generationsResult.value);
|
|
784
|
+
const exportsDir = path.join(this.repoPath, '.vibehub', 'workspace', 'exports', `workspace-${workspaceId}`);
|
|
785
|
+
await fs.ensureDir(exportsDir);
|
|
786
|
+
const generationsFile = path.join(exportsDir, 'generations.jsonl');
|
|
787
|
+
const generationsData = generations.map((gen) => ({
|
|
788
|
+
id: gen.id || `gen_${gen.unixMs || Date.now()}`,
|
|
789
|
+
textDescription: gen.textDescription || gen.text || '',
|
|
790
|
+
unixMs: gen.unixMs || Date.now(),
|
|
791
|
+
timestamp: new Date(gen.unixMs || Date.now()).toISOString(),
|
|
792
|
+
type: gen.type || 'generation',
|
|
793
|
+
metadata: gen
|
|
794
|
+
}));
|
|
795
|
+
await fs.writeJson(generationsFile, generationsData, { spaces: 2 });
|
|
796
|
+
newGenerationsCount = generations.length;
|
|
797
|
+
console.log(`🤖 Updated ${newGenerationsCount} generations`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
console.log('⚠️ Sync failed:', error.message);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Stop file watching for a workspace
|
|
806
|
+
*/
|
|
807
|
+
async stopWorkspaceWatching(workspaceId) {
|
|
808
|
+
if (this.fileWatchers && this.fileWatchers.has(workspaceId)) {
|
|
809
|
+
const watcher = this.fileWatchers.get(workspaceId);
|
|
810
|
+
await watcher?.close();
|
|
811
|
+
this.fileWatchers.delete(workspaceId);
|
|
812
|
+
console.log(`👁️ Stopped watching workspace: ${workspaceId}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Manual sync method
|
|
817
|
+
*/
|
|
818
|
+
async syncWorkspace(workspaceId) {
|
|
819
|
+
try {
|
|
820
|
+
console.log('🔄 Syncing workspace data...');
|
|
821
|
+
await this.syncNewData(workspaceId);
|
|
822
|
+
console.log('✅ Sync completed');
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
console.log('⚠️ Sync failed:', error.message);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Stop all file watchers
|
|
830
|
+
*/
|
|
831
|
+
async stopAllWatching() {
|
|
832
|
+
try {
|
|
833
|
+
const pidFile = path.join(this.repoPath, '.vibehub', 'watcher.pid');
|
|
834
|
+
if (await fs.pathExists(pidFile)) {
|
|
835
|
+
const pid = await fs.readFile(pidFile, 'utf-8');
|
|
836
|
+
const { exec } = await import('child_process');
|
|
837
|
+
// Kill the background process
|
|
838
|
+
exec(`kill ${pid.trim()}`, (error) => {
|
|
839
|
+
if (error) {
|
|
840
|
+
console.log('⚠️ Could not stop background watcher:', error.message);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
console.log('👁️ Stopped background file watcher');
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
// Remove the PID file
|
|
847
|
+
await fs.remove(pidFile);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
console.log('⚠️ Could not stop background watcher:', error.message);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
//# sourceMappingURL=vibehub-manager.js.map
|