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.
Files changed (69) hide show
  1. package/dist/commands/add.d.ts +3 -0
  2. package/dist/commands/add.d.ts.map +1 -0
  3. package/dist/commands/add.js +125 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/capture-prompts.d.ts +3 -0
  6. package/dist/commands/capture-prompts.d.ts.map +1 -0
  7. package/dist/commands/capture-prompts.js +137 -0
  8. package/dist/commands/capture-prompts.js.map +1 -0
  9. package/dist/commands/commit.d.ts +3 -0
  10. package/dist/commands/commit.d.ts.map +1 -0
  11. package/dist/commands/commit.js +132 -0
  12. package/dist/commands/commit.js.map +1 -0
  13. package/dist/commands/init.d.ts +3 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +113 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/install.d.ts +3 -0
  18. package/dist/commands/install.d.ts.map +1 -0
  19. package/dist/commands/install.js +182 -0
  20. package/dist/commands/install.js.map +1 -0
  21. package/dist/commands/pull.d.ts +3 -0
  22. package/dist/commands/pull.d.ts.map +1 -0
  23. package/dist/commands/pull.js +99 -0
  24. package/dist/commands/pull.js.map +1 -0
  25. package/dist/commands/push.d.ts +3 -0
  26. package/dist/commands/push.d.ts.map +1 -0
  27. package/dist/commands/push.js +142 -0
  28. package/dist/commands/push.js.map +1 -0
  29. package/dist/commands/session.d.ts +3 -0
  30. package/dist/commands/session.d.ts.map +1 -0
  31. package/dist/commands/session.js +179 -0
  32. package/dist/commands/session.js.map +1 -0
  33. package/dist/commands/set.d.ts +3 -0
  34. package/dist/commands/set.d.ts.map +1 -0
  35. package/dist/commands/set.js +134 -0
  36. package/dist/commands/set.js.map +1 -0
  37. package/dist/commands/status.d.ts +3 -0
  38. package/dist/commands/status.d.ts.map +1 -0
  39. package/dist/commands/status.js +79 -0
  40. package/dist/commands/status.js.map +1 -0
  41. package/dist/index.d.ts +3 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +44 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lib/ai-capture.d.ts +21 -0
  46. package/dist/lib/ai-capture.d.ts.map +1 -0
  47. package/dist/lib/ai-capture.js +242 -0
  48. package/dist/lib/ai-capture.js.map +1 -0
  49. package/dist/lib/cloud-service.d.ts +26 -0
  50. package/dist/lib/cloud-service.d.ts.map +1 -0
  51. package/dist/lib/cloud-service.js +200 -0
  52. package/dist/lib/cloud-service.js.map +1 -0
  53. package/dist/lib/cursor-conversation-capture.d.ts +10 -0
  54. package/dist/lib/cursor-conversation-capture.d.ts.map +1 -0
  55. package/dist/lib/cursor-conversation-capture.js +117 -0
  56. package/dist/lib/cursor-conversation-capture.js.map +1 -0
  57. package/dist/lib/prompt-capture-manager.d.ts +33 -0
  58. package/dist/lib/prompt-capture-manager.d.ts.map +1 -0
  59. package/dist/lib/prompt-capture-manager.js +178 -0
  60. package/dist/lib/prompt-capture-manager.js.map +1 -0
  61. package/dist/lib/vibehub-manager.d.ts +172 -0
  62. package/dist/lib/vibehub-manager.d.ts.map +1 -0
  63. package/dist/lib/vibehub-manager.js +855 -0
  64. package/dist/lib/vibehub-manager.js.map +1 -0
  65. package/dist/src/commands/set.d.ts +3 -0
  66. package/dist/src/commands/set.d.ts.map +1 -0
  67. package/dist/src/commands/set.js +125 -0
  68. package/dist/src/commands/set.js.map +1 -0
  69. 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