@lucenaone/coder 1.1.6 → 1.1.16
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/package.json +1 -1
- package/src/agent.js +65 -7
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -11,7 +11,9 @@ import { LucenaShell } from './lucena-shell.js';
|
|
|
11
11
|
|
|
12
12
|
const IGNORED_PATTERNS = [
|
|
13
13
|
'node_modules', '.git', '.next', '.wrangler', '.DS_Store',
|
|
14
|
-
'dist', 'build', '.cache', '.turbo', '.vercel', '.firebase'
|
|
14
|
+
'dist', 'build', '.cache', '.turbo', '.vercel', '.firebase',
|
|
15
|
+
'.venv', 'venv', '__pycache__', '.pytest_cache', '.mypy_cache',
|
|
16
|
+
'coverage', '.coverage', 'target', 'out', '.gradle'
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
const SEARCH_GLOB = '*.{js,jsx,ts,tsx,json,md,css,html,py,rb,go,rs}';
|
|
@@ -210,6 +212,7 @@ export class LucenaAgent {
|
|
|
210
212
|
case 'execute': return this.executeCommand(command);
|
|
211
213
|
case 'read_file': return this.readFileCmd(command);
|
|
212
214
|
case 'write_file': return this.writeFileCmd(command);
|
|
215
|
+
case 'list_files': return this.listFiles(command);
|
|
213
216
|
case 'list_dir': return this.listDir(command);
|
|
214
217
|
case 'stat': return this.statFile(command);
|
|
215
218
|
case 'delete_file': return this.deleteFile(command);
|
|
@@ -272,7 +275,7 @@ export class LucenaAgent {
|
|
|
272
275
|
|
|
273
276
|
async writeFileCmd({ messageId, path: filePath, content }) {
|
|
274
277
|
const fullPath = getJailedPath(this.cwd, filePath);
|
|
275
|
-
const relPath =
|
|
278
|
+
const relPath = toBrowserPath(relative(this.cwd, fullPath));
|
|
276
279
|
try {
|
|
277
280
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
278
281
|
await writeFile(fullPath, content, 'utf-8');
|
|
@@ -296,6 +299,38 @@ export class LucenaAgent {
|
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
|
|
302
|
+
async listFiles({ messageId }) {
|
|
303
|
+
try {
|
|
304
|
+
const files = await this.walkFiles(this.cwd);
|
|
305
|
+
this.pushResponse(messageId, 'done', JSON.stringify(files));
|
|
306
|
+
} catch (err) {
|
|
307
|
+
this.pushResponse(messageId, 'error', this._sanitize(err.message));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async walkFiles(dirPath, relativeDir = '') {
|
|
312
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
313
|
+
const files = [];
|
|
314
|
+
|
|
315
|
+
for (const entry of entries) {
|
|
316
|
+
if (IGNORED_PATTERNS.includes(entry.name)) continue;
|
|
317
|
+
|
|
318
|
+
const relPath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
319
|
+
const fullPath = join(dirPath, entry.name);
|
|
320
|
+
|
|
321
|
+
if (entry.isDirectory()) {
|
|
322
|
+
files.push(...await this.walkFiles(fullPath, relPath));
|
|
323
|
+
} else if (entry.isFile()) {
|
|
324
|
+
files.push({
|
|
325
|
+
path: relPath,
|
|
326
|
+
lineCount: await countTextFileLines(fullPath),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return files;
|
|
332
|
+
}
|
|
333
|
+
|
|
299
334
|
async statFile({ messageId, path: filePath }) {
|
|
300
335
|
const fullPath = getJailedPath(this.cwd, filePath);
|
|
301
336
|
try {
|
|
@@ -314,7 +349,7 @@ export class LucenaAgent {
|
|
|
314
349
|
|
|
315
350
|
async deleteFile({ messageId, path: filePath }) {
|
|
316
351
|
const fullPath = getJailedPath(this.cwd, filePath);
|
|
317
|
-
const relPath =
|
|
352
|
+
const relPath = toBrowserPath(relative(this.cwd, fullPath));
|
|
318
353
|
try {
|
|
319
354
|
if ((await stat(fullPath)).isDirectory()) {
|
|
320
355
|
await rm(fullPath, { recursive: true });
|
|
@@ -329,7 +364,7 @@ export class LucenaAgent {
|
|
|
329
364
|
|
|
330
365
|
async mkdirCmd({ messageId, path: dirPath }) {
|
|
331
366
|
const fullPath = getJailedPath(this.cwd, dirPath);
|
|
332
|
-
const relPath =
|
|
367
|
+
const relPath = toBrowserPath(relative(this.cwd, fullPath));
|
|
333
368
|
try {
|
|
334
369
|
await mkdir(fullPath, { recursive: true });
|
|
335
370
|
this.pushResponse(messageId, 'done', `Created ${relPath}`);
|
|
@@ -390,22 +425,23 @@ export class LucenaAgent {
|
|
|
390
425
|
|
|
391
426
|
this.watcher.on('all', (event, filePath) => {
|
|
392
427
|
const relPath = relative(this.cwd, filePath);
|
|
428
|
+
const normalizedRelPath = relPath.replace(/\\/g, '/');
|
|
393
429
|
|
|
394
430
|
// Stop anything escaping local disk
|
|
395
|
-
if (!
|
|
431
|
+
if (!normalizedRelPath || normalizedRelPath.startsWith('..') || isAbsolute(relPath)) {
|
|
396
432
|
return;
|
|
397
433
|
}
|
|
398
434
|
|
|
399
435
|
const changesRef = ref(this.db, `tunnels/${this.tunnelId}/fileChanges`);
|
|
400
436
|
push(changesRef, {
|
|
401
437
|
event,
|
|
402
|
-
path:
|
|
438
|
+
path: normalizedRelPath,
|
|
403
439
|
timestamp: serverTimestamp()
|
|
404
440
|
});
|
|
405
441
|
|
|
406
442
|
// ── Push incremental index delta for changed files ──
|
|
407
443
|
if (event === 'change' || event === 'add') {
|
|
408
|
-
this.pushIndexDelta(
|
|
444
|
+
this.pushIndexDelta(normalizedRelPath).catch(() => {}); // Non-blocking, non-fatal
|
|
409
445
|
}
|
|
410
446
|
});
|
|
411
447
|
}
|
|
@@ -426,3 +462,25 @@ export class LucenaAgent {
|
|
|
426
462
|
this.connected = false;
|
|
427
463
|
}
|
|
428
464
|
}
|
|
465
|
+
|
|
466
|
+
function toBrowserPath(pathValue) {
|
|
467
|
+
return '/' + String(pathValue || '').replace(/\\/g, '/');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function countTextFileLines(fullPath) {
|
|
471
|
+
if (!isTextLikeFile(fullPath)) return null;
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
const info = await stat(fullPath);
|
|
475
|
+
if (info.size > 5 * 1024 * 1024) return null;
|
|
476
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
477
|
+
if (!content) return 0;
|
|
478
|
+
return content.split('\n').length;
|
|
479
|
+
} catch {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function isTextLikeFile(filePath) {
|
|
485
|
+
return /\.(cjs|css|csv|go|html?|js|jsx|json|mdx?|mjs|py|rb|rs|sql|svg|ts|tsx|txt|xml|ya?ml)$/i.test(filePath);
|
|
486
|
+
}
|