nstantpage-agent 0.8.17 → 0.8.18
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/localServer.d.ts +10 -0
- package/dist/localServer.js +200 -0
- package/dist/tunnel.js +1 -1
- package/package.json +1 -1
package/dist/localServer.d.ts
CHANGED
|
@@ -129,6 +129,16 @@ export declare class LocalServer {
|
|
|
129
129
|
private handleTree;
|
|
130
130
|
private handleFileContent;
|
|
131
131
|
private handleSaveFile;
|
|
132
|
+
private static readonly BROWSE_SKIP_DIRS;
|
|
133
|
+
private static readonly BROWSE_BINARY_EXTS;
|
|
134
|
+
private collectFilesRecursive;
|
|
135
|
+
private handleBrowseListFiles;
|
|
136
|
+
private handleBrowseFile;
|
|
137
|
+
private handleBrowseTree;
|
|
138
|
+
private handleBrowseSave;
|
|
139
|
+
private handleBrowseSearch;
|
|
140
|
+
private handleBrowseDelete;
|
|
141
|
+
private matchGlob;
|
|
132
142
|
private handleSetDevPort;
|
|
133
143
|
private handleHealth;
|
|
134
144
|
/**
|
package/dist/localServer.js
CHANGED
|
@@ -406,6 +406,13 @@ export class LocalServer {
|
|
|
406
406
|
'/live/save-file': this.handleSaveFile,
|
|
407
407
|
'/live/set-dev-port': this.handleSetDevPort,
|
|
408
408
|
'/health': this.handleHealth,
|
|
409
|
+
// ── /browse/* endpoints (same as statusServer, but accessible through tunnel) ──
|
|
410
|
+
'/browse/list-files': this.handleBrowseListFiles,
|
|
411
|
+
'/browse/file': this.handleBrowseFile,
|
|
412
|
+
'/browse/tree': this.handleBrowseTree,
|
|
413
|
+
'/browse/save': this.handleBrowseSave,
|
|
414
|
+
'/browse/search': this.handleBrowseSearch,
|
|
415
|
+
'/browse/delete': this.handleBrowseDelete,
|
|
409
416
|
};
|
|
410
417
|
if (handlers[path])
|
|
411
418
|
return handlers[path];
|
|
@@ -1226,6 +1233,199 @@ export class LocalServer {
|
|
|
1226
1233
|
this.json(res, { error: error.message }, 500);
|
|
1227
1234
|
}
|
|
1228
1235
|
}
|
|
1236
|
+
// ─── /browse/* — Tunnel-accessible file operations ──────────
|
|
1237
|
+
// These mirror the statusServer's /browse/* endpoints but use the
|
|
1238
|
+
// project directory from this LocalServer instance. The backend's
|
|
1239
|
+
// DiskFileService routes through the gateway → tunnel → here.
|
|
1240
|
+
static BROWSE_SKIP_DIRS = new Set([
|
|
1241
|
+
'node_modules', 'dist', '.git', '.vite-cache', '.next',
|
|
1242
|
+
'__pycache__', '.turbo', '.cache', 'build', '.svelte-kit',
|
|
1243
|
+
]);
|
|
1244
|
+
static BROWSE_BINARY_EXTS = new Set([
|
|
1245
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg', '.bmp',
|
|
1246
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
1247
|
+
'.zip', '.tar', '.gz', '.rar', '.7z',
|
|
1248
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx',
|
|
1249
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mov',
|
|
1250
|
+
'.exe', '.dll', '.so', '.dylib', '.o',
|
|
1251
|
+
]);
|
|
1252
|
+
collectFilesRecursive(baseDir, currentDir, out) {
|
|
1253
|
+
let entries;
|
|
1254
|
+
try {
|
|
1255
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1256
|
+
}
|
|
1257
|
+
catch {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
for (const entry of entries) {
|
|
1261
|
+
if (entry.name.startsWith('.') && entry.name !== '.env')
|
|
1262
|
+
continue;
|
|
1263
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1264
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
1265
|
+
if (entry.isDirectory()) {
|
|
1266
|
+
if (LocalServer.BROWSE_SKIP_DIRS.has(entry.name))
|
|
1267
|
+
continue;
|
|
1268
|
+
this.collectFilesRecursive(baseDir, fullPath, out);
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1272
|
+
if (LocalServer.BROWSE_BINARY_EXTS.has(ext)) {
|
|
1273
|
+
out.push({ filePath: relativePath, totalLines: 0 });
|
|
1274
|
+
}
|
|
1275
|
+
else {
|
|
1276
|
+
try {
|
|
1277
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
1278
|
+
out.push({ filePath: relativePath, totalLines: content.split('\n').length });
|
|
1279
|
+
}
|
|
1280
|
+
catch {
|
|
1281
|
+
out.push({ filePath: relativePath, totalLines: 0 });
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
async handleBrowseListFiles(_req, res) {
|
|
1288
|
+
const dir = this.options.projectDir;
|
|
1289
|
+
try {
|
|
1290
|
+
const files = [];
|
|
1291
|
+
this.collectFilesRecursive(dir, dir, files);
|
|
1292
|
+
this.json(res, { files });
|
|
1293
|
+
}
|
|
1294
|
+
catch (error) {
|
|
1295
|
+
this.json(res, { error: error.message }, 500);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async handleBrowseFile(_req, res, _body, url) {
|
|
1299
|
+
const filePath = url.searchParams.get('path');
|
|
1300
|
+
if (!filePath) {
|
|
1301
|
+
this.json(res, { error: 'path parameter required' }, 400);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const dir = this.options.projectDir;
|
|
1305
|
+
const fullPath = path.resolve(dir, filePath);
|
|
1306
|
+
if (!fullPath.startsWith(path.resolve(dir))) {
|
|
1307
|
+
this.json(res, { error: 'Path traversal not allowed' }, 403);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
try {
|
|
1311
|
+
if (!fs.existsSync(fullPath)) {
|
|
1312
|
+
this.json(res, { error: 'File not found' }, 404);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
1316
|
+
this.json(res, { content });
|
|
1317
|
+
}
|
|
1318
|
+
catch (error) {
|
|
1319
|
+
this.json(res, { error: error.message }, 500);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
async handleBrowseTree(_req, res) {
|
|
1323
|
+
try {
|
|
1324
|
+
const tree = await this.fileManager.getFileTree();
|
|
1325
|
+
this.json(res, tree);
|
|
1326
|
+
}
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
this.json(res, { error: error.message }, 500);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async handleBrowseSave(_req, res, body) {
|
|
1332
|
+
try {
|
|
1333
|
+
const { filePath, content } = JSON.parse(body);
|
|
1334
|
+
if (!filePath || content === undefined) {
|
|
1335
|
+
this.json(res, { error: 'filePath and content required' }, 400);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const dir = this.options.projectDir;
|
|
1339
|
+
const fullPath = path.resolve(dir, filePath);
|
|
1340
|
+
if (!fullPath.startsWith(path.resolve(dir))) {
|
|
1341
|
+
this.json(res, { error: 'Path traversal not allowed' }, 403);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
const parentDir = path.dirname(fullPath);
|
|
1345
|
+
if (!fs.existsSync(parentDir))
|
|
1346
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
1347
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
1348
|
+
this.json(res, { success: true });
|
|
1349
|
+
}
|
|
1350
|
+
catch (error) {
|
|
1351
|
+
this.json(res, { error: error.message }, 500);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
async handleBrowseSearch(_req, res, body) {
|
|
1355
|
+
try {
|
|
1356
|
+
const { query, isRegex, filePattern } = JSON.parse(body);
|
|
1357
|
+
if (!query) {
|
|
1358
|
+
this.json(res, { error: 'query required' }, 400);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const dir = this.options.projectDir;
|
|
1362
|
+
const files = [];
|
|
1363
|
+
this.collectFilesRecursive(dir, dir, files);
|
|
1364
|
+
const regex = isRegex ? new RegExp(query, 'gi') : null;
|
|
1365
|
+
const results = [];
|
|
1366
|
+
const maxResults = 100;
|
|
1367
|
+
for (const f of files) {
|
|
1368
|
+
if (results.length >= maxResults)
|
|
1369
|
+
break;
|
|
1370
|
+
if (filePattern && !this.matchGlob(f.filePath, filePattern))
|
|
1371
|
+
continue;
|
|
1372
|
+
const fullPath = path.resolve(dir, f.filePath);
|
|
1373
|
+
let fileContent;
|
|
1374
|
+
try {
|
|
1375
|
+
fileContent = fs.readFileSync(fullPath, 'utf-8');
|
|
1376
|
+
}
|
|
1377
|
+
catch {
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const lines = fileContent.split('\n');
|
|
1381
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
1382
|
+
const matched = regex ? regex.test(lines[i]) : lines[i].toLowerCase().includes(query.toLowerCase());
|
|
1383
|
+
if (regex)
|
|
1384
|
+
regex.lastIndex = 0;
|
|
1385
|
+
if (matched) {
|
|
1386
|
+
results.push({ filePath: f.filePath, line: i + 1, content: lines[i] });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
this.json(res, { results });
|
|
1391
|
+
}
|
|
1392
|
+
catch (error) {
|
|
1393
|
+
this.json(res, { error: error.message }, 500);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
async handleBrowseDelete(_req, res, body) {
|
|
1397
|
+
try {
|
|
1398
|
+
const { filePath } = JSON.parse(body);
|
|
1399
|
+
if (!filePath) {
|
|
1400
|
+
this.json(res, { error: 'filePath required' }, 400);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
const dir = this.options.projectDir;
|
|
1404
|
+
const fullPath = path.resolve(dir, filePath);
|
|
1405
|
+
if (!fullPath.startsWith(path.resolve(dir))) {
|
|
1406
|
+
this.json(res, { error: 'Path traversal not allowed' }, 403);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (!fs.existsSync(fullPath)) {
|
|
1410
|
+
this.json(res, { error: 'File not found' }, 404);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
fs.unlinkSync(fullPath);
|
|
1414
|
+
this.json(res, { success: true });
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
this.json(res, { error: error.message }, 500);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
matchGlob(filePath, pattern) {
|
|
1421
|
+
const regexPattern = '^' + pattern
|
|
1422
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
1423
|
+
.replace(/\*\*/g, '<<<GLOBSTAR>>>')
|
|
1424
|
+
.replace(/\*/g, '[^/]*')
|
|
1425
|
+
.replace(/<<<GLOBSTAR>>>/g, '.*')
|
|
1426
|
+
.replace(/\?/g, '.') + '$';
|
|
1427
|
+
return new RegExp(regexPattern, 'i').test(filePath);
|
|
1428
|
+
}
|
|
1229
1429
|
// ─── /live/set-dev-port ─────────────────────────────────────
|
|
1230
1430
|
async handleSetDevPort(_req, res, body) {
|
|
1231
1431
|
try {
|
package/dist/tunnel.js
CHANGED
|
@@ -503,7 +503,7 @@ export class TunnelClient {
|
|
|
503
503
|
*/
|
|
504
504
|
handleHttpRequest(request) {
|
|
505
505
|
const { url, headers } = request;
|
|
506
|
-
const isApiRequest = url.startsWith('/live/') || url === '/health';
|
|
506
|
+
const isApiRequest = url.startsWith('/live/') || url.startsWith('/browse/') || url === '/health';
|
|
507
507
|
// Use x-target-port header if present (multi-port tunneling), else fall back to default
|
|
508
508
|
const portHeader = headers?.['x-target-port'];
|
|
509
509
|
const overridePort = portHeader ? parseInt(String(portHeader), 10) : NaN;
|
package/package.json
CHANGED