@burdenoff/vibe-agent 1.0.0 → 1.0.2
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/README.md +14 -14
- package/dist/app.d.ts +4 -4
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +144 -130
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +342 -332
- package/dist/cli.js.map +1 -1
- package/dist/db/schema.d.ts +15 -15
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +65 -62
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/middleware/ModuleAuth.d.ts +2 -2
- package/dist/middleware/ModuleAuth.d.ts.map +1 -1
- package/dist/middleware/ModuleAuth.js +32 -29
- package/dist/middleware/ModuleAuth.js.map +1 -1
- package/dist/middleware/auth.d.ts +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +4 -4
- package/dist/middleware/auth.js.map +1 -1
- package/dist/migrations/remove-notes-prompts.d.ts.map +1 -1
- package/dist/migrations/remove-notes-prompts.js +26 -26
- package/dist/migrations/remove-notes-prompts.js.map +1 -1
- package/dist/routes/bookmarks.d.ts +1 -1
- package/dist/routes/bookmarks.d.ts.map +1 -1
- package/dist/routes/bookmarks.js +53 -44
- package/dist/routes/bookmarks.js.map +1 -1
- package/dist/routes/config.d.ts +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +29 -27
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/files.d.ts +1 -1
- package/dist/routes/files.d.ts.map +1 -1
- package/dist/routes/files.js +175 -134
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/git.d.ts +1 -1
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +183 -169
- package/dist/routes/git.js.map +1 -1
- package/dist/routes/moduleRegistry.d.ts +3 -3
- package/dist/routes/moduleRegistry.d.ts.map +1 -1
- package/dist/routes/moduleRegistry.js +58 -58
- package/dist/routes/moduleRegistry.js.map +1 -1
- package/dist/routes/notifications.d.ts +1 -1
- package/dist/routes/notifications.d.ts.map +1 -1
- package/dist/routes/notifications.js +69 -64
- package/dist/routes/notifications.js.map +1 -1
- package/dist/routes/port-forward.d.ts +1 -1
- package/dist/routes/port-forward.d.ts.map +1 -1
- package/dist/routes/port-forward.js +59 -50
- package/dist/routes/port-forward.js.map +1 -1
- package/dist/routes/projects.d.ts +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +134 -120
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/ssh.d.ts +1 -1
- package/dist/routes/ssh.d.ts.map +1 -1
- package/dist/routes/ssh.js +47 -47
- package/dist/routes/ssh.js.map +1 -1
- package/dist/routes/tasks.d.ts +1 -1
- package/dist/routes/tasks.d.ts.map +1 -1
- package/dist/routes/tasks.js +53 -49
- package/dist/routes/tasks.js.map +1 -1
- package/dist/routes/tmux.d.ts +1 -1
- package/dist/routes/tmux.d.ts.map +1 -1
- package/dist/routes/tmux.js +337 -241
- package/dist/routes/tmux.js.map +1 -1
- package/dist/routes/tunnel.d.ts +2 -2
- package/dist/routes/tunnel.d.ts.map +1 -1
- package/dist/routes/tunnel.js +115 -74
- package/dist/routes/tunnel.js.map +1 -1
- package/dist/services/ModulePermissions.d.ts +2 -2
- package/dist/services/ModulePermissions.d.ts.map +1 -1
- package/dist/services/ModulePermissions.js +50 -40
- package/dist/services/ModulePermissions.js.map +1 -1
- package/dist/services/ModuleRegistryService.d.ts +10 -10
- package/dist/services/ModuleRegistryService.d.ts.map +1 -1
- package/dist/services/ModuleRegistryService.js +156 -131
- package/dist/services/ModuleRegistryService.js.map +1 -1
- package/dist/services/agent.service.d.ts.map +1 -1
- package/dist/services/agent.service.js +24 -21
- package/dist/services/agent.service.js.map +1 -1
- package/dist/services/bootstrap.d.ts +1 -1
- package/dist/services/bootstrap.d.ts.map +1 -1
- package/dist/services/bootstrap.js +146 -69
- package/dist/services/bootstrap.js.map +1 -1
- package/dist/services/service-manager.d.ts +2 -2
- package/dist/services/service-manager.d.ts.map +1 -1
- package/dist/services/service-manager.js +75 -63
- package/dist/services/service-manager.js.map +1 -1
- package/package.json +2 -2
package/dist/routes/files.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { promises as fs, readFileSync } from
|
|
2
|
-
import path from
|
|
1
|
+
import { promises as fs, readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
3
|
export const fileRoutes = async (fastify) => {
|
|
4
4
|
// Read file
|
|
5
|
-
fastify.post(
|
|
6
|
-
const { path: filePath, projectId, connectionId } = request.body;
|
|
5
|
+
fastify.post("/read", async (request, reply) => {
|
|
6
|
+
const { path: filePath, projectId, connectionId, } = request.body;
|
|
7
7
|
try {
|
|
8
8
|
// Security check - prevent directory traversal
|
|
9
9
|
const normalizedPath = path.normalize(filePath);
|
|
10
|
-
if (normalizedPath.includes(
|
|
11
|
-
return reply
|
|
10
|
+
if (normalizedPath.includes("..")) {
|
|
11
|
+
return reply
|
|
12
|
+
.code(403)
|
|
13
|
+
.send({ error: "Directory traversal not allowed" });
|
|
12
14
|
}
|
|
13
15
|
// Check for sensitive files
|
|
14
16
|
const sensitivePatterns = [
|
|
@@ -18,10 +20,12 @@ export const fileRoutes = async (fastify) => {
|
|
|
18
20
|
/id_rsa/,
|
|
19
21
|
/\.ssh\//,
|
|
20
22
|
/\.git\/config$/,
|
|
21
|
-
/\.npmrc
|
|
23
|
+
/\.npmrc$/,
|
|
22
24
|
];
|
|
23
|
-
if (sensitivePatterns.some(pattern => pattern.test(normalizedPath))) {
|
|
24
|
-
return reply
|
|
25
|
+
if (sensitivePatterns.some((pattern) => pattern.test(normalizedPath))) {
|
|
26
|
+
return reply
|
|
27
|
+
.code(403)
|
|
28
|
+
.send({ error: "Access to sensitive files not allowed" });
|
|
25
29
|
}
|
|
26
30
|
if (connectionId) {
|
|
27
31
|
// Read remote file via SSH
|
|
@@ -29,81 +33,85 @@ export const fileRoutes = async (fastify) => {
|
|
|
29
33
|
}
|
|
30
34
|
else {
|
|
31
35
|
// Read local file
|
|
32
|
-
const content = await fs.readFile(normalizedPath,
|
|
36
|
+
const content = await fs.readFile(normalizedPath, "utf-8");
|
|
33
37
|
// Emit file read event
|
|
34
|
-
fastify.io.emit(
|
|
38
|
+
fastify.io.emit("file:read", {
|
|
35
39
|
path: normalizedPath,
|
|
36
40
|
projectId,
|
|
37
|
-
size: content.length
|
|
41
|
+
size: content.length,
|
|
38
42
|
});
|
|
39
43
|
return {
|
|
40
44
|
content,
|
|
41
45
|
path: normalizedPath,
|
|
42
|
-
size: content.length
|
|
46
|
+
size: content.length,
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
catch (error) {
|
|
47
51
|
const nodeError = error;
|
|
48
|
-
if (nodeError.code ===
|
|
49
|
-
return reply.code(404).send({ error:
|
|
52
|
+
if (nodeError.code === "ENOENT") {
|
|
53
|
+
return reply.code(404).send({ error: "File not found" });
|
|
50
54
|
}
|
|
51
|
-
if (nodeError.code ===
|
|
52
|
-
return reply.code(403).send({ error:
|
|
55
|
+
if (nodeError.code === "EACCES") {
|
|
56
|
+
return reply.code(403).send({ error: "Permission denied" });
|
|
53
57
|
}
|
|
54
58
|
return reply.code(500).send({
|
|
55
|
-
error:
|
|
56
|
-
details: error instanceof Error ? error.message :
|
|
59
|
+
error: "Failed to read file",
|
|
60
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
57
61
|
});
|
|
58
62
|
}
|
|
59
63
|
});
|
|
60
64
|
// Write file
|
|
61
|
-
fastify.post(
|
|
62
|
-
const { path: filePath, content, projectId } = request.body;
|
|
65
|
+
fastify.post("/write", async (request, reply) => {
|
|
66
|
+
const { path: filePath, content, projectId, } = request.body;
|
|
63
67
|
try {
|
|
64
68
|
// Security check - prevent directory traversal
|
|
65
69
|
const normalizedPath = path.normalize(filePath);
|
|
66
|
-
if (normalizedPath.includes(
|
|
67
|
-
return reply
|
|
70
|
+
if (normalizedPath.includes("..")) {
|
|
71
|
+
return reply
|
|
72
|
+
.code(403)
|
|
73
|
+
.send({ error: "Directory traversal not allowed" });
|
|
68
74
|
}
|
|
69
75
|
// Create directory if it doesn't exist
|
|
70
76
|
const dir = path.dirname(normalizedPath);
|
|
71
77
|
await fs.mkdir(dir, { recursive: true });
|
|
72
78
|
// Write file
|
|
73
|
-
await fs.writeFile(normalizedPath, content,
|
|
79
|
+
await fs.writeFile(normalizedPath, content, "utf-8");
|
|
74
80
|
// Get file stats
|
|
75
81
|
const stats = await fs.stat(normalizedPath);
|
|
76
82
|
// Emit file write event
|
|
77
|
-
fastify.io.emit(
|
|
83
|
+
fastify.io.emit("file:write", {
|
|
78
84
|
path: normalizedPath,
|
|
79
85
|
projectId,
|
|
80
|
-
size: stats.size
|
|
86
|
+
size: stats.size,
|
|
81
87
|
});
|
|
82
88
|
return {
|
|
83
89
|
success: true,
|
|
84
90
|
path: normalizedPath,
|
|
85
|
-
size: stats.size
|
|
91
|
+
size: stats.size,
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
94
|
catch (error) {
|
|
89
95
|
const nodeError = error;
|
|
90
|
-
if (nodeError.code ===
|
|
91
|
-
return reply.code(403).send({ error:
|
|
96
|
+
if (nodeError.code === "EACCES") {
|
|
97
|
+
return reply.code(403).send({ error: "Permission denied" });
|
|
92
98
|
}
|
|
93
99
|
return reply.code(500).send({
|
|
94
|
-
error:
|
|
95
|
-
details: error instanceof Error ? error.message :
|
|
100
|
+
error: "Failed to write file",
|
|
101
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
96
102
|
});
|
|
97
103
|
}
|
|
98
104
|
});
|
|
99
105
|
// List directory
|
|
100
|
-
fastify.post(
|
|
106
|
+
fastify.post("/list", async (request, reply) => {
|
|
101
107
|
const { path: dirPath, connectionId } = request.body;
|
|
102
108
|
try {
|
|
103
109
|
// Security check - prevent directory traversal
|
|
104
110
|
const normalizedPath = path.normalize(dirPath);
|
|
105
|
-
if (normalizedPath.includes(
|
|
106
|
-
return reply
|
|
111
|
+
if (normalizedPath.includes("..")) {
|
|
112
|
+
return reply
|
|
113
|
+
.code(403)
|
|
114
|
+
.send({ error: "Directory traversal not allowed" });
|
|
107
115
|
}
|
|
108
116
|
if (connectionId) {
|
|
109
117
|
// List remote directory via SSH
|
|
@@ -111,7 +119,9 @@ export const fileRoutes = async (fastify) => {
|
|
|
111
119
|
}
|
|
112
120
|
else {
|
|
113
121
|
// List local directory
|
|
114
|
-
const entries = await fs.readdir(normalizedPath, {
|
|
122
|
+
const entries = await fs.readdir(normalizedPath, {
|
|
123
|
+
withFileTypes: true,
|
|
124
|
+
});
|
|
115
125
|
const files = await Promise.all(entries.map(async (entry) => {
|
|
116
126
|
const fullPath = path.join(normalizedPath, entry.name);
|
|
117
127
|
let stats;
|
|
@@ -125,94 +135,115 @@ export const fileRoutes = async (fastify) => {
|
|
|
125
135
|
return {
|
|
126
136
|
name: entry.name,
|
|
127
137
|
path: fullPath,
|
|
128
|
-
type: entry.isDirectory()
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
type: entry.isDirectory()
|
|
139
|
+
? "directory"
|
|
140
|
+
: entry.isFile()
|
|
141
|
+
? "file"
|
|
142
|
+
: entry.isSymbolicLink()
|
|
143
|
+
? "symlink"
|
|
144
|
+
: "other",
|
|
131
145
|
size: stats?.size || 0,
|
|
132
146
|
modified: stats?.mtime || null,
|
|
133
|
-
permissions: stats?.mode || null
|
|
147
|
+
permissions: stats?.mode || null,
|
|
134
148
|
};
|
|
135
149
|
}));
|
|
136
150
|
// Sort: directories first, then files
|
|
137
151
|
files.sort((a, b) => {
|
|
138
|
-
if (a.type ===
|
|
152
|
+
if (a.type === "directory" && b.type !== "directory")
|
|
139
153
|
return -1;
|
|
140
|
-
if (a.type !==
|
|
154
|
+
if (a.type !== "directory" && b.type === "directory")
|
|
141
155
|
return 1;
|
|
142
156
|
return a.name.localeCompare(b.name);
|
|
143
157
|
});
|
|
144
158
|
return {
|
|
145
159
|
path: normalizedPath,
|
|
146
160
|
files,
|
|
147
|
-
count: files.length
|
|
161
|
+
count: files.length,
|
|
148
162
|
};
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
catch (error) {
|
|
152
166
|
const nodeError = error;
|
|
153
|
-
if (nodeError.code ===
|
|
154
|
-
return reply.code(404).send({ error:
|
|
167
|
+
if (nodeError.code === "ENOENT") {
|
|
168
|
+
return reply.code(404).send({ error: "Directory not found" });
|
|
155
169
|
}
|
|
156
|
-
if (nodeError.code ===
|
|
157
|
-
return reply.code(400).send({ error:
|
|
170
|
+
if (nodeError.code === "ENOTDIR") {
|
|
171
|
+
return reply.code(400).send({ error: "Path is not a directory" });
|
|
158
172
|
}
|
|
159
|
-
if (nodeError.code ===
|
|
160
|
-
return reply.code(403).send({ error:
|
|
173
|
+
if (nodeError.code === "EACCES") {
|
|
174
|
+
return reply.code(403).send({ error: "Permission denied" });
|
|
161
175
|
}
|
|
162
176
|
return reply.code(500).send({
|
|
163
|
-
error:
|
|
164
|
-
details: error instanceof Error ? error.message :
|
|
177
|
+
error: "Failed to list directory",
|
|
178
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
165
179
|
});
|
|
166
180
|
}
|
|
167
181
|
});
|
|
168
182
|
// Check if path exists
|
|
169
|
-
fastify.post(
|
|
183
|
+
fastify.post("/exists", async (request, reply) => {
|
|
170
184
|
const { path: checkPath } = request.body;
|
|
171
185
|
try {
|
|
172
186
|
// Security check - prevent directory traversal
|
|
173
187
|
const normalizedPath = path.normalize(checkPath);
|
|
174
|
-
if (normalizedPath.includes(
|
|
175
|
-
return reply
|
|
188
|
+
if (normalizedPath.includes("..")) {
|
|
189
|
+
return reply
|
|
190
|
+
.code(403)
|
|
191
|
+
.send({ error: "Directory traversal not allowed" });
|
|
176
192
|
}
|
|
177
193
|
await fs.access(normalizedPath);
|
|
178
194
|
const stats = await fs.stat(normalizedPath);
|
|
179
195
|
return {
|
|
180
196
|
exists: true,
|
|
181
197
|
path: normalizedPath,
|
|
182
|
-
type: stats.isDirectory()
|
|
183
|
-
|
|
198
|
+
type: stats.isDirectory()
|
|
199
|
+
? "directory"
|
|
200
|
+
: stats.isFile()
|
|
201
|
+
? "file"
|
|
202
|
+
: "other",
|
|
184
203
|
size: stats.size,
|
|
185
|
-
modified: stats.mtime
|
|
204
|
+
modified: stats.mtime,
|
|
186
205
|
};
|
|
187
206
|
}
|
|
188
207
|
catch (error) {
|
|
189
208
|
const nodeError = error;
|
|
190
|
-
if (nodeError.code ===
|
|
209
|
+
if (nodeError.code === "ENOENT") {
|
|
191
210
|
return {
|
|
192
211
|
exists: false,
|
|
193
|
-
path: path.normalize(checkPath)
|
|
212
|
+
path: path.normalize(checkPath),
|
|
194
213
|
};
|
|
195
214
|
}
|
|
196
215
|
return reply.code(500).send({
|
|
197
|
-
error:
|
|
198
|
-
details: error instanceof Error ? error.message :
|
|
216
|
+
error: "Failed to check path",
|
|
217
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
199
218
|
});
|
|
200
219
|
}
|
|
201
220
|
});
|
|
202
221
|
// Delete file or directory
|
|
203
|
-
fastify.post(
|
|
222
|
+
fastify.post("/delete", async (request, reply) => {
|
|
204
223
|
const { path: deletePath } = request.body;
|
|
205
224
|
try {
|
|
206
225
|
// Security check - prevent directory traversal
|
|
207
226
|
const normalizedPath = path.normalize(deletePath);
|
|
208
|
-
if (normalizedPath.includes(
|
|
209
|
-
return reply
|
|
227
|
+
if (normalizedPath.includes("..")) {
|
|
228
|
+
return reply
|
|
229
|
+
.code(403)
|
|
230
|
+
.send({ error: "Directory traversal not allowed" });
|
|
210
231
|
}
|
|
211
232
|
// Additional security check for important paths
|
|
212
|
-
const protectedPaths = [
|
|
233
|
+
const protectedPaths = [
|
|
234
|
+
"/",
|
|
235
|
+
"/etc",
|
|
236
|
+
"/usr",
|
|
237
|
+
"/bin",
|
|
238
|
+
"/sbin",
|
|
239
|
+
"/var",
|
|
240
|
+
"/opt",
|
|
241
|
+
];
|
|
213
242
|
if (protectedPaths.includes(normalizedPath) ||
|
|
214
|
-
protectedPaths.some(p => normalizedPath.startsWith(p +
|
|
215
|
-
return reply
|
|
243
|
+
protectedPaths.some((p) => normalizedPath.startsWith(p + "/"))) {
|
|
244
|
+
return reply
|
|
245
|
+
.code(403)
|
|
246
|
+
.send({ error: "Cannot delete system directories" });
|
|
216
247
|
}
|
|
217
248
|
const stats = await fs.stat(normalizedPath);
|
|
218
249
|
if (stats.isDirectory()) {
|
|
@@ -222,49 +253,51 @@ export const fileRoutes = async (fastify) => {
|
|
|
222
253
|
await fs.unlink(normalizedPath);
|
|
223
254
|
}
|
|
224
255
|
// Emit file delete event
|
|
225
|
-
fastify.io.emit(
|
|
256
|
+
fastify.io.emit("file:delete", {
|
|
226
257
|
path: normalizedPath,
|
|
227
|
-
type: stats.isDirectory() ?
|
|
258
|
+
type: stats.isDirectory() ? "directory" : "file",
|
|
228
259
|
});
|
|
229
260
|
return {
|
|
230
261
|
success: true,
|
|
231
|
-
path: normalizedPath
|
|
262
|
+
path: normalizedPath,
|
|
232
263
|
};
|
|
233
264
|
}
|
|
234
265
|
catch (error) {
|
|
235
266
|
const nodeError = error;
|
|
236
|
-
if (nodeError.code ===
|
|
237
|
-
return reply.code(404).send({ error:
|
|
267
|
+
if (nodeError.code === "ENOENT") {
|
|
268
|
+
return reply.code(404).send({ error: "Path not found" });
|
|
238
269
|
}
|
|
239
|
-
if (nodeError.code ===
|
|
240
|
-
return reply.code(403).send({ error:
|
|
270
|
+
if (nodeError.code === "EACCES") {
|
|
271
|
+
return reply.code(403).send({ error: "Permission denied" });
|
|
241
272
|
}
|
|
242
273
|
return reply.code(500).send({
|
|
243
|
-
error:
|
|
244
|
-
details: error instanceof Error ? error.message :
|
|
274
|
+
error: "Failed to delete path",
|
|
275
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
245
276
|
});
|
|
246
277
|
}
|
|
247
278
|
});
|
|
248
279
|
// Read README file
|
|
249
|
-
fastify.post(
|
|
280
|
+
fastify.post("/readme", async (request, reply) => {
|
|
250
281
|
const { path: projectPath, connectionId } = request.body;
|
|
251
282
|
try {
|
|
252
283
|
// Security check - prevent directory traversal
|
|
253
284
|
const normalizedPath = path.normalize(projectPath);
|
|
254
|
-
if (normalizedPath.includes(
|
|
255
|
-
return reply
|
|
285
|
+
if (normalizedPath.includes("..")) {
|
|
286
|
+
return reply
|
|
287
|
+
.code(403)
|
|
288
|
+
.send({ error: "Directory traversal not allowed" });
|
|
256
289
|
}
|
|
257
290
|
// README file variants to check
|
|
258
291
|
const readmeVariants = [
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
292
|
+
"README.md",
|
|
293
|
+
"readme.md",
|
|
294
|
+
"README.MD",
|
|
295
|
+
"README",
|
|
296
|
+
"readme",
|
|
297
|
+
"README.txt",
|
|
298
|
+
"readme.txt",
|
|
299
|
+
"README.rst",
|
|
300
|
+
"readme.rst",
|
|
268
301
|
];
|
|
269
302
|
if (connectionId) {
|
|
270
303
|
// Check remote README
|
|
@@ -275,25 +308,25 @@ export const fileRoutes = async (fastify) => {
|
|
|
275
308
|
for (const variant of readmeVariants) {
|
|
276
309
|
const readmePath = path.join(normalizedPath, variant);
|
|
277
310
|
try {
|
|
278
|
-
const content = await fs.readFile(readmePath,
|
|
311
|
+
const content = await fs.readFile(readmePath, "utf-8");
|
|
279
312
|
return {
|
|
280
313
|
content,
|
|
281
314
|
path: readmePath,
|
|
282
315
|
variant,
|
|
283
|
-
size: content.length
|
|
316
|
+
size: content.length,
|
|
284
317
|
};
|
|
285
318
|
}
|
|
286
319
|
catch {
|
|
287
320
|
// Continue to next variant
|
|
288
321
|
}
|
|
289
322
|
}
|
|
290
|
-
return reply.code(404).send({ error:
|
|
323
|
+
return reply.code(404).send({ error: "No README file found" });
|
|
291
324
|
}
|
|
292
325
|
}
|
|
293
326
|
catch (error) {
|
|
294
327
|
return reply.code(500).send({
|
|
295
|
-
error:
|
|
296
|
-
details: error instanceof Error ? error.message :
|
|
328
|
+
error: "Failed to read README",
|
|
329
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
297
330
|
});
|
|
298
331
|
}
|
|
299
332
|
});
|
|
@@ -302,46 +335,47 @@ export const fileRoutes = async (fastify) => {
|
|
|
302
335
|
async function readRemoteFile(fastify, connectionId, filePath, reply) {
|
|
303
336
|
const connectionConfig = fastify.db.getSSHConnection(connectionId);
|
|
304
337
|
if (!connectionConfig) {
|
|
305
|
-
return reply.code(404).send({ error:
|
|
338
|
+
return reply.code(404).send({ error: "SSH connection not found" });
|
|
306
339
|
}
|
|
307
|
-
const { Client } = await import(
|
|
340
|
+
const { Client } = await import("ssh2");
|
|
308
341
|
const conn = new Client();
|
|
309
342
|
return new Promise((resolve) => {
|
|
310
|
-
conn.on(
|
|
343
|
+
conn.on("ready", () => {
|
|
311
344
|
conn.sftp((err, sftp) => {
|
|
312
345
|
if (err) {
|
|
313
346
|
conn.end();
|
|
314
347
|
return resolve(reply.code(500).send({
|
|
315
|
-
error:
|
|
316
|
-
details: err.message
|
|
348
|
+
error: "Failed to establish SFTP connection",
|
|
349
|
+
details: err.message,
|
|
317
350
|
}));
|
|
318
351
|
}
|
|
319
352
|
sftp.readFile(filePath, (err, buffer) => {
|
|
320
353
|
conn.end();
|
|
321
354
|
if (err) {
|
|
322
355
|
const sshError = err;
|
|
323
|
-
if (sshError.code === 2) {
|
|
324
|
-
|
|
356
|
+
if (sshError.code === 2) {
|
|
357
|
+
// ENOENT
|
|
358
|
+
return resolve(reply.code(404).send({ error: "File not found" }));
|
|
325
359
|
}
|
|
326
360
|
return resolve(reply.code(500).send({
|
|
327
|
-
error:
|
|
328
|
-
details: err.message
|
|
361
|
+
error: "Failed to read remote file",
|
|
362
|
+
details: err.message,
|
|
329
363
|
}));
|
|
330
364
|
}
|
|
331
|
-
const content = buffer.toString(
|
|
365
|
+
const content = buffer.toString("utf-8");
|
|
332
366
|
resolve({
|
|
333
367
|
content,
|
|
334
368
|
path: filePath,
|
|
335
369
|
connectionId,
|
|
336
|
-
size: content.length
|
|
370
|
+
size: content.length,
|
|
337
371
|
});
|
|
338
372
|
});
|
|
339
373
|
});
|
|
340
374
|
});
|
|
341
|
-
conn.on(
|
|
375
|
+
conn.on("error", (err) => {
|
|
342
376
|
resolve(reply.code(500).send({
|
|
343
|
-
error:
|
|
344
|
-
details: err.message
|
|
377
|
+
error: "SSH connection failed",
|
|
378
|
+
details: err.message,
|
|
345
379
|
}));
|
|
346
380
|
});
|
|
347
381
|
// Connect with appropriate auth
|
|
@@ -359,78 +393,85 @@ async function readRemoteFile(fastify, connectionId, filePath, reply) {
|
|
|
359
393
|
conn.connect(connectConfig);
|
|
360
394
|
});
|
|
361
395
|
}
|
|
362
|
-
// Helper function to list remote directory
|
|
396
|
+
// Helper function to list remote directory
|
|
363
397
|
async function listRemoteDirectory(fastify, connectionId, dirPath, reply) {
|
|
364
398
|
const connectionConfig = fastify.db.getSSHConnection(connectionId);
|
|
365
399
|
if (!connectionConfig) {
|
|
366
|
-
return reply.code(404).send({ error:
|
|
400
|
+
return reply.code(404).send({ error: "SSH connection not found" });
|
|
367
401
|
}
|
|
368
|
-
const { Client } = await import(
|
|
402
|
+
const { Client } = await import("ssh2");
|
|
369
403
|
const conn = new Client();
|
|
370
404
|
return new Promise((resolve) => {
|
|
371
|
-
conn.on(
|
|
405
|
+
conn.on("ready", () => {
|
|
372
406
|
conn.exec(`ls -la "${dirPath}"`, (err, stream) => {
|
|
373
407
|
if (err) {
|
|
374
408
|
conn.end();
|
|
375
409
|
return resolve(reply.code(500).send({
|
|
376
|
-
error:
|
|
377
|
-
details: err.message
|
|
410
|
+
error: "Failed to execute remote command",
|
|
411
|
+
details: err.message,
|
|
378
412
|
}));
|
|
379
413
|
}
|
|
380
|
-
let output =
|
|
381
|
-
let errorOutput =
|
|
382
|
-
stream.on(
|
|
414
|
+
let output = "";
|
|
415
|
+
let errorOutput = "";
|
|
416
|
+
stream.on("data", (data) => {
|
|
383
417
|
output += data.toString();
|
|
384
418
|
});
|
|
385
|
-
stream.stderr.on(
|
|
419
|
+
stream.stderr.on("data", (data) => {
|
|
386
420
|
errorOutput += data.toString();
|
|
387
421
|
});
|
|
388
|
-
stream.on(
|
|
422
|
+
stream.on("close", (code) => {
|
|
389
423
|
conn.end();
|
|
390
424
|
if (code !== 0) {
|
|
391
|
-
if (errorOutput.includes(
|
|
392
|
-
return resolve(reply.code(404).send({ error:
|
|
425
|
+
if (errorOutput.includes("No such file or directory")) {
|
|
426
|
+
return resolve(reply.code(404).send({ error: "Directory not found" }));
|
|
393
427
|
}
|
|
394
428
|
return resolve(reply.code(500).send({
|
|
395
|
-
error:
|
|
396
|
-
details: errorOutput
|
|
429
|
+
error: "Failed to list directory",
|
|
430
|
+
details: errorOutput,
|
|
397
431
|
}));
|
|
398
432
|
}
|
|
399
433
|
// Parse ls output
|
|
400
|
-
const lines = output.split(
|
|
401
|
-
const files = lines
|
|
434
|
+
const lines = output.split("\n").filter((line) => line.trim());
|
|
435
|
+
const files = lines
|
|
436
|
+
.slice(1)
|
|
437
|
+
.map((line) => {
|
|
438
|
+
// Skip "total" line
|
|
402
439
|
const parts = line.split(/\s+/);
|
|
403
440
|
if (parts.length >= 9) {
|
|
404
441
|
const permissions = parts[0];
|
|
405
442
|
const size = parseInt(parts[4], 10);
|
|
406
|
-
const name = parts.slice(8).join(
|
|
443
|
+
const name = parts.slice(8).join(" ");
|
|
407
444
|
return {
|
|
408
445
|
name,
|
|
409
446
|
path: path.join(dirPath, name),
|
|
410
|
-
type: permissions.startsWith(
|
|
411
|
-
|
|
447
|
+
type: permissions.startsWith("d")
|
|
448
|
+
? "directory"
|
|
449
|
+
: permissions.startsWith("l")
|
|
450
|
+
? "symlink"
|
|
451
|
+
: "file",
|
|
412
452
|
size,
|
|
413
453
|
permissions: parts[0],
|
|
414
454
|
owner: parts[2],
|
|
415
455
|
group: parts[3],
|
|
416
|
-
modified: `${parts[5]} ${parts[6]} ${parts[7]}
|
|
456
|
+
modified: `${parts[5]} ${parts[6]} ${parts[7]}`,
|
|
417
457
|
};
|
|
418
458
|
}
|
|
419
459
|
return null;
|
|
420
|
-
})
|
|
460
|
+
})
|
|
461
|
+
.filter((f) => f && f.name !== "." && f.name !== "..");
|
|
421
462
|
resolve({
|
|
422
463
|
path: dirPath,
|
|
423
464
|
connectionId,
|
|
424
465
|
files,
|
|
425
|
-
count: files.length
|
|
466
|
+
count: files.length,
|
|
426
467
|
});
|
|
427
468
|
});
|
|
428
469
|
});
|
|
429
470
|
});
|
|
430
|
-
conn.on(
|
|
471
|
+
conn.on("error", (err) => {
|
|
431
472
|
resolve(reply.code(500).send({
|
|
432
|
-
error:
|
|
433
|
-
details: err.message
|
|
473
|
+
error: "SSH connection failed",
|
|
474
|
+
details: err.message,
|
|
434
475
|
}));
|
|
435
476
|
});
|
|
436
477
|
// Connect with appropriate auth
|
|
@@ -452,7 +493,7 @@ async function listRemoteDirectory(fastify, connectionId, dirPath, reply) {
|
|
|
452
493
|
async function findRemoteReadme(fastify, connectionId, dirPath, variants, reply) {
|
|
453
494
|
const connectionConfig = fastify.db.getSSHConnection(connectionId);
|
|
454
495
|
if (!connectionConfig) {
|
|
455
|
-
return reply.code(404).send({ error:
|
|
496
|
+
return reply.code(404).send({ error: "SSH connection not found" });
|
|
456
497
|
}
|
|
457
498
|
// Try each variant
|
|
458
499
|
for (const variant of variants) {
|
|
@@ -466,6 +507,6 @@ async function findRemoteReadme(fastify, connectionId, dirPath, variants, reply)
|
|
|
466
507
|
continue;
|
|
467
508
|
}
|
|
468
509
|
}
|
|
469
|
-
return reply.code(404).send({ error:
|
|
510
|
+
return reply.code(404).send({ error: "No README file found" });
|
|
470
511
|
}
|
|
471
512
|
//# sourceMappingURL=files.js.map
|