@burdenoff/vibe-agent 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 (100) hide show
  1. package/.env.example +8 -0
  2. package/LICENSE +22 -0
  3. package/README.md +290 -0
  4. package/dist/app.d.ts +15 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +445 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +1043 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/db/schema.d.ts +145 -0
  13. package/dist/db/schema.d.ts.map +1 -0
  14. package/dist/db/schema.js +536 -0
  15. package/dist/db/schema.js.map +1 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +61 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/middleware/ModuleAuth.d.ts +61 -0
  21. package/dist/middleware/ModuleAuth.d.ts.map +1 -0
  22. package/dist/middleware/ModuleAuth.js +220 -0
  23. package/dist/middleware/ModuleAuth.js.map +1 -0
  24. package/dist/middleware/auth.d.ts +3 -0
  25. package/dist/middleware/auth.d.ts.map +1 -0
  26. package/dist/middleware/auth.js +11 -0
  27. package/dist/middleware/auth.js.map +1 -0
  28. package/dist/migrations/remove-notes-prompts.d.ts +13 -0
  29. package/dist/migrations/remove-notes-prompts.d.ts.map +1 -0
  30. package/dist/migrations/remove-notes-prompts.js +148 -0
  31. package/dist/migrations/remove-notes-prompts.js.map +1 -0
  32. package/dist/routes/bookmarks.d.ts +3 -0
  33. package/dist/routes/bookmarks.d.ts.map +1 -0
  34. package/dist/routes/bookmarks.js +186 -0
  35. package/dist/routes/bookmarks.js.map +1 -0
  36. package/dist/routes/config.d.ts +3 -0
  37. package/dist/routes/config.d.ts.map +1 -0
  38. package/dist/routes/config.js +108 -0
  39. package/dist/routes/config.js.map +1 -0
  40. package/dist/routes/files.d.ts +3 -0
  41. package/dist/routes/files.d.ts.map +1 -0
  42. package/dist/routes/files.js +471 -0
  43. package/dist/routes/files.js.map +1 -0
  44. package/dist/routes/git.d.ts +3 -0
  45. package/dist/routes/git.d.ts.map +1 -0
  46. package/dist/routes/git.js +498 -0
  47. package/dist/routes/git.js.map +1 -0
  48. package/dist/routes/moduleRegistry.d.ts +41 -0
  49. package/dist/routes/moduleRegistry.d.ts.map +1 -0
  50. package/dist/routes/moduleRegistry.js +356 -0
  51. package/dist/routes/moduleRegistry.js.map +1 -0
  52. package/dist/routes/notifications.d.ts +3 -0
  53. package/dist/routes/notifications.d.ts.map +1 -0
  54. package/dist/routes/notifications.js +250 -0
  55. package/dist/routes/notifications.js.map +1 -0
  56. package/dist/routes/port-forward.d.ts +3 -0
  57. package/dist/routes/port-forward.d.ts.map +1 -0
  58. package/dist/routes/port-forward.js +205 -0
  59. package/dist/routes/port-forward.js.map +1 -0
  60. package/dist/routes/projects.d.ts +3 -0
  61. package/dist/routes/projects.d.ts.map +1 -0
  62. package/dist/routes/projects.js +442 -0
  63. package/dist/routes/projects.js.map +1 -0
  64. package/dist/routes/ssh.d.ts +3 -0
  65. package/dist/routes/ssh.d.ts.map +1 -0
  66. package/dist/routes/ssh.js +192 -0
  67. package/dist/routes/ssh.js.map +1 -0
  68. package/dist/routes/tasks.d.ts +3 -0
  69. package/dist/routes/tasks.d.ts.map +1 -0
  70. package/dist/routes/tasks.js +183 -0
  71. package/dist/routes/tasks.js.map +1 -0
  72. package/dist/routes/tmux.d.ts +3 -0
  73. package/dist/routes/tmux.d.ts.map +1 -0
  74. package/dist/routes/tmux.js +1191 -0
  75. package/dist/routes/tmux.js.map +1 -0
  76. package/dist/routes/tunnel.d.ts +25 -0
  77. package/dist/routes/tunnel.d.ts.map +1 -0
  78. package/dist/routes/tunnel.js +449 -0
  79. package/dist/routes/tunnel.js.map +1 -0
  80. package/dist/services/ModulePermissions.d.ts +100 -0
  81. package/dist/services/ModulePermissions.d.ts.map +1 -0
  82. package/dist/services/ModulePermissions.js +312 -0
  83. package/dist/services/ModulePermissions.js.map +1 -0
  84. package/dist/services/ModuleRegistryService.d.ts +152 -0
  85. package/dist/services/ModuleRegistryService.d.ts.map +1 -0
  86. package/dist/services/ModuleRegistryService.js +522 -0
  87. package/dist/services/ModuleRegistryService.js.map +1 -0
  88. package/dist/services/agent.service.d.ts +19 -0
  89. package/dist/services/agent.service.d.ts.map +1 -0
  90. package/dist/services/agent.service.js +88 -0
  91. package/dist/services/agent.service.js.map +1 -0
  92. package/dist/services/bootstrap.d.ts +22 -0
  93. package/dist/services/bootstrap.d.ts.map +1 -0
  94. package/dist/services/bootstrap.js +206 -0
  95. package/dist/services/bootstrap.js.map +1 -0
  96. package/dist/services/service-manager.d.ts +50 -0
  97. package/dist/services/service-manager.d.ts.map +1 -0
  98. package/dist/services/service-manager.js +382 -0
  99. package/dist/services/service-manager.js.map +1 -0
  100. package/package.json +107 -0
@@ -0,0 +1,205 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { Client } from 'ssh2';
3
+ import { createServer } from 'net';
4
+ import { readFileSync } from 'fs';
5
+ // Store active connections and servers
6
+ const activeConnections = new Map();
7
+ export const portForwardRoutes = async (fastify) => {
8
+ // Get all port forwards
9
+ fastify.get('/', async (_request, _reply) => {
10
+ const portForwards = fastify.db.getAllPortForwards();
11
+ return { portForwards };
12
+ });
13
+ // Create new port forward
14
+ fastify.post('/', async (request, reply) => {
15
+ const { localPort, remoteHost, remotePort, connectionId } = request.body;
16
+ try {
17
+ // Check if port is already in use
18
+ const existing = fastify.db.getAllPortForwards()
19
+ .find(pf => pf.localPort === localPort && pf.status === 'active');
20
+ if (existing) {
21
+ return reply.code(409).send({ error: 'Local port is already in use' });
22
+ }
23
+ // Get SSH connection config
24
+ const connectionConfig = fastify.db.getSSHConnection(connectionId);
25
+ if (!connectionConfig) {
26
+ return reply.code(404).send({ error: 'SSH connection not found' });
27
+ }
28
+ const portForward = fastify.db.createPortForward({
29
+ id: uuidv4(),
30
+ localPort,
31
+ remoteHost,
32
+ remotePort,
33
+ serverName: connectionConfig.serverName, // Keep for backward compatibility
34
+ connectionId,
35
+ status: 'inactive'
36
+ });
37
+ return { portForward };
38
+ }
39
+ catch (error) {
40
+ return reply.code(500).send({
41
+ error: 'Failed to create port forward',
42
+ details: error instanceof Error ? error.message : 'Unknown error'
43
+ });
44
+ }
45
+ });
46
+ // Start port forwarding
47
+ fastify.post('/:id/start', async (request, reply) => {
48
+ const { id } = request.params;
49
+ try {
50
+ const portForward = fastify.db.getPortForward(id);
51
+ if (!portForward) {
52
+ return reply.code(404).send({ error: 'Port forward not found' });
53
+ }
54
+ if (portForward.status === 'active') {
55
+ return reply.code(400).send({ error: 'Port forward is already active' });
56
+ }
57
+ // Get SSH connection config
58
+ const connectionConfig = portForward.connectionId
59
+ ? fastify.db.getSSHConnection(portForward.connectionId)
60
+ : fastify.db.getSSHConnectionByName(portForward.serverName);
61
+ if (!connectionConfig) {
62
+ return reply.code(404).send({ error: 'SSH connection not found' });
63
+ }
64
+ const sshClient = new Client();
65
+ // Create local server
66
+ const server = createServer((localSocket) => {
67
+ sshClient.forwardOut('localhost', portForward.localPort, portForward.remoteHost, portForward.remotePort, (err, stream) => {
68
+ if (err) {
69
+ localSocket.end();
70
+ console.error('Forward error:', err);
71
+ return;
72
+ }
73
+ localSocket.pipe(stream).pipe(localSocket);
74
+ localSocket.on('close', () => {
75
+ stream.end();
76
+ });
77
+ stream.on('close', () => {
78
+ localSocket.end();
79
+ });
80
+ });
81
+ });
82
+ return new Promise((resolve) => {
83
+ sshClient.on('ready', () => {
84
+ server.listen(portForward.localPort, () => {
85
+ // Store active connection
86
+ activeConnections.set(id, { client: sshClient, server });
87
+ // Update database
88
+ fastify.db.updatePortForward(id, {
89
+ status: 'active',
90
+ connectionId: id
91
+ });
92
+ // Emit event
93
+ fastify.io.emit('portforward:started', { id, localPort: portForward.localPort });
94
+ resolve({
95
+ success: true,
96
+ message: `Port forwarding started on localhost:${portForward.localPort}`
97
+ });
98
+ });
99
+ server.on('error', (err) => {
100
+ sshClient.end();
101
+ resolve(reply.code(500).send({
102
+ error: 'Failed to start local server',
103
+ details: err.message
104
+ }));
105
+ });
106
+ });
107
+ sshClient.on('error', (err) => {
108
+ resolve(reply.code(500).send({
109
+ error: 'SSH connection failed',
110
+ details: err.message
111
+ }));
112
+ });
113
+ // Connect with appropriate auth method
114
+ const connectConfig = {
115
+ host: connectionConfig.host,
116
+ port: connectionConfig.port,
117
+ username: connectionConfig.username,
118
+ };
119
+ if (connectionConfig.privateKeyPath) {
120
+ connectConfig.privateKey = readFileSync(connectionConfig.privateKeyPath);
121
+ }
122
+ else if (connectionConfig.password) {
123
+ connectConfig.password = connectionConfig.password;
124
+ }
125
+ sshClient.connect(connectConfig);
126
+ });
127
+ }
128
+ catch (error) {
129
+ return reply.code(500).send({
130
+ error: 'Failed to start port forward',
131
+ details: error instanceof Error ? error.message : 'Unknown error'
132
+ });
133
+ }
134
+ });
135
+ // Stop port forwarding
136
+ fastify.post('/:id/stop', async (request, reply) => {
137
+ const { id } = request.params;
138
+ try {
139
+ const portForward = fastify.db.getPortForward(id);
140
+ if (!portForward) {
141
+ return reply.code(404).send({ error: 'Port forward not found' });
142
+ }
143
+ if (portForward.status !== 'active') {
144
+ return reply.code(400).send({ error: 'Port forward is not active' });
145
+ }
146
+ // Get active connection
147
+ const active = activeConnections.get(id);
148
+ if (active) {
149
+ active.server.close();
150
+ active.client.end();
151
+ activeConnections.delete(id);
152
+ }
153
+ // Update database
154
+ fastify.db.updatePortForward(id, {
155
+ status: 'inactive',
156
+ connectionId: undefined
157
+ });
158
+ // Emit event
159
+ fastify.io.emit('portforward:stopped', { id, localPort: portForward.localPort });
160
+ return { success: true };
161
+ }
162
+ catch (error) {
163
+ return reply.code(500).send({
164
+ error: 'Failed to stop port forward',
165
+ details: error instanceof Error ? error.message : 'Unknown error'
166
+ });
167
+ }
168
+ });
169
+ // Delete port forward
170
+ fastify.delete('/:id', async (request, reply) => {
171
+ const { id } = request.params;
172
+ try {
173
+ const portForward = fastify.db.getPortForward(id);
174
+ if (!portForward) {
175
+ return reply.code(404).send({ error: 'Port forward not found' });
176
+ }
177
+ // Stop if active
178
+ if (portForward.status === 'active') {
179
+ const active = activeConnections.get(id);
180
+ if (active) {
181
+ active.server.close();
182
+ active.client.end();
183
+ activeConnections.delete(id);
184
+ }
185
+ }
186
+ fastify.db.deletePortForward(id);
187
+ return { success: true };
188
+ }
189
+ catch (error) {
190
+ return reply.code(500).send({
191
+ error: 'Failed to delete port forward',
192
+ details: error instanceof Error ? error.message : 'Unknown error'
193
+ });
194
+ }
195
+ });
196
+ // Cleanup on server close
197
+ fastify.addHook('onClose', async () => {
198
+ for (const [, { client, server }] of activeConnections) {
199
+ server.close();
200
+ client.end();
201
+ }
202
+ activeConnections.clear();
203
+ });
204
+ };
205
+ //# sourceMappingURL=port-forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-forward.js","sourceRoot":"","sources":["../../src/routes/port-forward.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { FastifyPluginAsync } from 'fastify';
2
+ export declare const projectRoutes: FastifyPluginAsync;
3
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/routes/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgK7C,eAAO,MAAM,aAAa,EAAE,kBA8K3B,CAAC"}
@@ -0,0 +1,442 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ // Security function to check if a path is allowed for scanning
4
+ function isAllowedPath(scanPath) {
5
+ const normalizedPath = path.resolve(scanPath);
6
+ // List of forbidden paths (system directories)
7
+ const forbiddenPaths = [
8
+ '/System',
9
+ '/usr',
10
+ '/bin',
11
+ '/sbin',
12
+ '/etc',
13
+ '/var/root',
14
+ '/private/var/root',
15
+ '/Library/Application Support',
16
+ '/private/var/db',
17
+ '/private/var/install',
18
+ '/private/var/tmp',
19
+ '/tmp',
20
+ '/Windows',
21
+ '/Program Files',
22
+ '/Program Files (x86)',
23
+ 'C:\\Windows',
24
+ 'C:\\Program Files',
25
+ 'C:\\Program Files (x86)',
26
+ ];
27
+ // Check if the path starts with any forbidden directory
28
+ for (const forbiddenPath of forbiddenPaths) {
29
+ if (normalizedPath.startsWith(forbiddenPath)) {
30
+ return false;
31
+ }
32
+ }
33
+ // Only allow paths under common user directories
34
+ const allowedPrefixes = [
35
+ '/Users',
36
+ '/home',
37
+ '/opt',
38
+ process.env.HOME || '',
39
+ 'C:\\Users',
40
+ 'D:\\',
41
+ 'E:\\',
42
+ ].filter(Boolean);
43
+ return allowedPrefixes.some(prefix => normalizedPath.startsWith(prefix));
44
+ }
45
+ // Function to parse .gitignore patterns
46
+ function parseGitignore(gitignoreContent) {
47
+ return gitignoreContent
48
+ .split('\n')
49
+ .map(line => line.trim())
50
+ .filter(line => line && !line.startsWith('#'))
51
+ .map(line => {
52
+ // Remove leading slash
53
+ if (line.startsWith('/')) {
54
+ line = line.slice(1);
55
+ }
56
+ return line;
57
+ });
58
+ }
59
+ // Function to check if a path should be ignored based on .gitignore patterns
60
+ function shouldIgnoreByGitignore(relativePath, patterns, _basePath) {
61
+ for (const pattern of patterns) {
62
+ // Simple pattern matching - could be enhanced with proper glob matching
63
+ if (pattern.endsWith('/')) {
64
+ // Directory pattern
65
+ const dirPattern = pattern.slice(0, -1);
66
+ if (relativePath === dirPattern || relativePath.startsWith(dirPattern + '/')) {
67
+ return true;
68
+ }
69
+ }
70
+ else if (pattern.includes('*')) {
71
+ // Wildcard pattern - basic implementation
72
+ const regexPattern = pattern
73
+ .replace(/\./g, '\\.')
74
+ .replace(/\*/g, '.*');
75
+ const regex = new RegExp(`^${regexPattern}$`);
76
+ if (regex.test(relativePath)) {
77
+ return true;
78
+ }
79
+ }
80
+ else {
81
+ // Exact match or starts with pattern
82
+ if (relativePath === pattern || relativePath.startsWith(pattern + '/')) {
83
+ return true;
84
+ }
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ // Function to collect .gitignore patterns from all parent directories
90
+ async function collectGitignorePatterns(startPath, targetPath) {
91
+ const patterns = new Map();
92
+ let currentPath = targetPath;
93
+ while (currentPath.startsWith(startPath)) {
94
+ try {
95
+ const gitignorePath = path.join(currentPath, '.gitignore');
96
+ const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
97
+ const pathPatterns = parseGitignore(gitignoreContent);
98
+ if (pathPatterns.length > 0) {
99
+ patterns.set(currentPath, pathPatterns);
100
+ }
101
+ }
102
+ catch (error) {
103
+ // .gitignore doesn't exist in this directory, continue
104
+ }
105
+ const parentPath = path.dirname(currentPath);
106
+ if (parentPath === currentPath)
107
+ break; // Reached root
108
+ currentPath = parentPath;
109
+ }
110
+ return patterns;
111
+ }
112
+ export const projectRoutes = async (fastify) => {
113
+ // Scan local projects
114
+ fastify.post('/scan', async (request, reply) => {
115
+ const { recursive = false, scanPath, respectGitignore = true } = request.body;
116
+ try {
117
+ console.log('Scanning projects...', { recursive, scanPath, respectGitignore });
118
+ // Require scanPath to be provided - no default scanning
119
+ if (!scanPath) {
120
+ return reply.code(400).send({
121
+ error: 'scanPath is required',
122
+ success: false
123
+ });
124
+ }
125
+ // Security check - prevent directory traversal
126
+ const normalizedPath = path.normalize(scanPath);
127
+ if (normalizedPath.includes('..')) {
128
+ return reply.code(403).send({
129
+ error: 'Directory traversal not allowed',
130
+ success: false
131
+ });
132
+ }
133
+ // Additional security checks
134
+ if (!isAllowedPath(normalizedPath)) {
135
+ return reply.code(403).send({
136
+ error: 'Access to this path is not allowed',
137
+ success: false
138
+ });
139
+ }
140
+ const projects = await scanForProjects(normalizedPath, recursive, respectGitignore);
141
+ // Emit project scan event
142
+ fastify.io.emit('projects:scanned', {
143
+ path: normalizedPath,
144
+ count: projects.length,
145
+ recursive
146
+ });
147
+ return {
148
+ projects,
149
+ success: true,
150
+ count: projects.length
151
+ };
152
+ }
153
+ catch (error) {
154
+ console.error('Error scanning projects:', error);
155
+ return reply.code(500).send({
156
+ error: 'Failed to scan projects',
157
+ details: error instanceof Error ? error.message : 'Unknown error',
158
+ success: false
159
+ });
160
+ }
161
+ });
162
+ // Scan remote projects
163
+ fastify.post('/scan-remote', async (request, reply) => {
164
+ const { recursive = false, scanPath, respectGitignore = true } = request.body;
165
+ try {
166
+ console.log('Scanning remote projects...', { recursive, scanPath, respectGitignore });
167
+ // Require scanPath to be provided - no default scanning
168
+ if (!scanPath) {
169
+ return reply.code(400).send({
170
+ error: 'scanPath is required for remote scanning',
171
+ success: false
172
+ });
173
+ }
174
+ // Security check - prevent directory traversal
175
+ const normalizedPath = path.normalize(scanPath);
176
+ if (normalizedPath.includes('..')) {
177
+ return reply.code(403).send({
178
+ error: 'Directory traversal not allowed',
179
+ success: false
180
+ });
181
+ }
182
+ // Additional security checks
183
+ if (!isAllowedPath(normalizedPath)) {
184
+ return reply.code(403).send({
185
+ error: 'Access to this path is not allowed',
186
+ success: false
187
+ });
188
+ }
189
+ // For now, return empty array as this requires SSH setup
190
+ // TODO: Implement remote scanning via SSH
191
+ const projects = [];
192
+ // Emit remote project scan event
193
+ fastify.io.emit('projects:remote-scanned', {
194
+ path: normalizedPath,
195
+ count: projects.length,
196
+ recursive
197
+ });
198
+ return {
199
+ projects,
200
+ success: true,
201
+ count: projects.length,
202
+ message: 'Remote scanning not yet implemented'
203
+ };
204
+ }
205
+ catch (error) {
206
+ console.error('Error scanning remote projects:', error);
207
+ return reply.code(500).send({
208
+ error: 'Failed to scan remote projects',
209
+ details: error instanceof Error ? error.message : 'Unknown error',
210
+ success: false
211
+ });
212
+ }
213
+ });
214
+ // Get all projects (mock for now)
215
+ fastify.get('/', async (request, reply) => {
216
+ const { includeNested } = request.query;
217
+ try {
218
+ // For now, return empty array as this would come from database
219
+ const projects = [];
220
+ return projects;
221
+ }
222
+ catch (error) {
223
+ console.error('Error getting projects:', error);
224
+ return reply.code(500).send({
225
+ error: 'Failed to get projects',
226
+ details: error instanceof Error ? error.message : 'Unknown error'
227
+ });
228
+ }
229
+ });
230
+ // Create project (mock for now)
231
+ fastify.post('/', async (request, reply) => {
232
+ const projectData = request.body;
233
+ try {
234
+ // For now, just return success
235
+ const id = `project-${Date.now()}`;
236
+ return {
237
+ id,
238
+ success: true
239
+ };
240
+ }
241
+ catch (error) {
242
+ console.error('Error creating project:', error);
243
+ return reply.code(500).send({
244
+ error: 'Failed to create project',
245
+ details: error instanceof Error ? error.message : 'Unknown error'
246
+ });
247
+ }
248
+ });
249
+ // Update project (mock for now)
250
+ fastify.put('/:id', async (request, reply) => {
251
+ const { id } = request.params;
252
+ const updates = request.body;
253
+ try {
254
+ // For now, just return success
255
+ return {
256
+ success: true,
257
+ changes: 1
258
+ };
259
+ }
260
+ catch (error) {
261
+ console.error('Error updating project:', error);
262
+ return reply.code(500).send({
263
+ error: 'Failed to update project',
264
+ details: error instanceof Error ? error.message : 'Unknown error'
265
+ });
266
+ }
267
+ });
268
+ };
269
+ // Helper function to scan for projects
270
+ async function scanForProjects(basePath, recursive, respectGitignore = true) {
271
+ const projects = [];
272
+ // Collect .gitignore patterns if respectGitignore is enabled
273
+ let gitignorePatterns = new Map();
274
+ if (respectGitignore) {
275
+ gitignorePatterns = await collectGitignorePatterns(basePath, basePath);
276
+ }
277
+ try {
278
+ const entries = await fs.readdir(basePath, { withFileTypes: true });
279
+ for (const entry of entries) {
280
+ if (!entry.isDirectory())
281
+ continue;
282
+ const fullPath = path.join(basePath, entry.name);
283
+ // Skip hidden directories and node_modules
284
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
285
+ continue;
286
+ }
287
+ // Check if directory should be ignored by .gitignore
288
+ if (respectGitignore && gitignorePatterns.size > 0) {
289
+ let shouldIgnore = false;
290
+ // Check against all collected .gitignore patterns
291
+ for (const [patternBasePath, patterns] of Array.from(gitignorePatterns)) {
292
+ if (fullPath.startsWith(patternBasePath)) {
293
+ const relativePath = path.relative(patternBasePath, fullPath);
294
+ if (shouldIgnoreByGitignore(relativePath, patterns, patternBasePath)) {
295
+ shouldIgnore = true;
296
+ break;
297
+ }
298
+ }
299
+ }
300
+ if (shouldIgnore) {
301
+ console.log(`Skipping directory ${entry.name} due to .gitignore`);
302
+ continue;
303
+ }
304
+ }
305
+ // Check if this directory is a project
306
+ const project = await detectProject(fullPath, entry.name);
307
+ if (project) {
308
+ projects.push(project);
309
+ }
310
+ // If recursive, scan subdirectories
311
+ if (recursive) {
312
+ try {
313
+ const subProjects = await scanForProjects(fullPath, true, respectGitignore);
314
+ projects.push(...subProjects);
315
+ }
316
+ catch (error) {
317
+ // Ignore errors in subdirectories
318
+ console.warn(`Failed to scan subdirectory ${fullPath}:`, error);
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch (error) {
324
+ console.error(`Failed to scan directory ${basePath}:`, error);
325
+ }
326
+ return projects;
327
+ }
328
+ // Helper function to detect if a directory is a project
329
+ async function detectProject(dirPath, dirName) {
330
+ try {
331
+ const files = await fs.readdir(dirPath);
332
+ // Check for common project indicators
333
+ let hasPackageJson = files.includes('package.json');
334
+ const hasPyproject = files.includes('pyproject.toml');
335
+ const hasRequirements = files.includes('requirements.txt');
336
+ const hasCargoToml = files.includes('Cargo.toml');
337
+ const hasGitDir = files.includes('.git');
338
+ const hasDockerfile = files.includes('Dockerfile');
339
+ const hasMakefile = files.includes('Makefile');
340
+ // If it doesn't look like a project, skip it
341
+ if (!hasPackageJson && !hasPyproject && !hasRequirements && !hasCargoToml && !hasGitDir) {
342
+ return null;
343
+ }
344
+ let projectType = 'unknown';
345
+ let vitePort;
346
+ // Determine project type
347
+ if (hasPackageJson) {
348
+ try {
349
+ const packageJsonPath = path.join(dirPath, 'package.json');
350
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
351
+ // Check if the file is empty or invalid
352
+ if (!packageJsonContent.trim()) {
353
+ console.warn(`Empty package.json file found at ${packageJsonPath}`);
354
+ hasPackageJson = false;
355
+ }
356
+ else {
357
+ const packageJson = JSON.parse(packageJsonContent);
358
+ if (packageJson.dependencies?.vite || packageJson.devDependencies?.vite) {
359
+ projectType = 'vite';
360
+ // Try to detect port from vite config or scripts
361
+ vitePort = extractVitePort(packageJson);
362
+ }
363
+ else if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
364
+ projectType = 'react';
365
+ }
366
+ else if (packageJson.dependencies?.next || packageJson.devDependencies?.next) {
367
+ projectType = 'nextjs';
368
+ }
369
+ else if (packageJson.dependencies?.express) {
370
+ projectType = 'express';
371
+ }
372
+ else if (packageJson.dependencies?.fastify) {
373
+ projectType = 'fastify';
374
+ }
375
+ else {
376
+ projectType = 'nodejs';
377
+ }
378
+ }
379
+ }
380
+ catch (error) {
381
+ console.warn(`Failed to parse package.json in ${dirPath}:`, error);
382
+ projectType = 'nodejs';
383
+ }
384
+ }
385
+ else if (hasPyproject || hasRequirements) {
386
+ projectType = 'python';
387
+ }
388
+ else if (hasCargoToml) {
389
+ projectType = 'rust';
390
+ }
391
+ // Get directory stats
392
+ const stats = await fs.stat(dirPath);
393
+ // Create project object
394
+ const project = {
395
+ id: `local-${Buffer.from(dirPath).toString('base64').slice(0, 8)}-${Date.now()}`,
396
+ name: dirName,
397
+ path: dirPath,
398
+ type: projectType,
399
+ lastUpdated: stats.mtime.getTime(),
400
+ lastFileChange: stats.mtime.getTime(),
401
+ status: 'INACTIVE',
402
+ isRemote: false,
403
+ tags: [projectType]
404
+ };
405
+ if (vitePort) {
406
+ project.vitePort = vitePort;
407
+ }
408
+ // Check if it's a git repository
409
+ if (hasGitDir) {
410
+ try {
411
+ const gitConfigPath = path.join(dirPath, '.git', 'config');
412
+ const gitConfig = await fs.readFile(gitConfigPath, 'utf-8');
413
+ const remoteMatch = gitConfig.match(/url = (.+)/);
414
+ if (remoteMatch) {
415
+ project.repository = remoteMatch[1].trim();
416
+ }
417
+ }
418
+ catch (error) {
419
+ // Ignore git config errors
420
+ }
421
+ }
422
+ return project;
423
+ }
424
+ catch (error) {
425
+ console.error(`Failed to detect project in ${dirPath}:`, error);
426
+ return null;
427
+ }
428
+ }
429
+ // Helper function to extract Vite port from package.json
430
+ function extractVitePort(packageJson) {
431
+ // Check dev script for port
432
+ const devScript = packageJson.scripts?.dev;
433
+ if (devScript && typeof devScript === 'string') {
434
+ const portMatch = devScript.match(/--port[=\s]+(\d+)/);
435
+ if (portMatch) {
436
+ return parseInt(portMatch[1], 10);
437
+ }
438
+ }
439
+ // Default Vite port
440
+ return undefined;
441
+ }
442
+ //# sourceMappingURL=projects.js.map