@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/agent.js +65 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucenaone/coder",
3
- "version": "1.1.6",
3
+ "version": "1.1.16",
4
4
  "description": "Private tunnel for connecting LucenaCoder.com to your local folder. Always remains folder scoped while providing full terminal access.",
5
5
  "type": "module",
6
6
  "bin": {
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 = '/' + relative(this.cwd, fullPath);
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 = '/' + relative(this.cwd, fullPath);
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 = '/' + relative(this.cwd, fullPath);
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 (!relPath || relPath.startsWith('..') || isAbsolute(relPath)) {
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: relPath,
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(relPath).catch(() => {}); // Non-blocking, non-fatal
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
+ }