brainctl 0.1.25 → 0.1.26

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/ui/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
2
+ import { readFile, readdir, stat } from 'node:fs/promises';
3
3
  import { BrainctlError, ProfileError, ProfileNotFoundError, ValidationError } from '../errors.js';
4
4
  import { createAgentConfigService } from '../services/agent/agent-config-service.js';
5
5
  import { createMcpPreflightService } from '../services/platform/mcp-preflight-service.js';
@@ -16,6 +16,17 @@ import path from 'node:path';
16
16
  import os from 'node:os';
17
17
  import { fileURLToPath } from 'node:url';
18
18
  import { spawn } from 'node:child_process';
19
+ function revealInFileManager(targetPath) {
20
+ if (process.platform === 'darwin') {
21
+ spawn('open', ['-R', targetPath], { detached: true, stdio: 'ignore' }).unref();
22
+ return;
23
+ }
24
+ if (process.platform === 'win32') {
25
+ spawn('explorer', [`/select,${targetPath}`], { detached: true, stdio: 'ignore' }).unref();
26
+ return;
27
+ }
28
+ spawn('xdg-open', [path.dirname(targetPath)], { detached: true, stdio: 'ignore' }).unref();
29
+ }
19
30
  function resolveCwd(req, fallback) {
20
31
  const url = new URL(req.url ?? '/', 'http://localhost');
21
32
  const raw = url.searchParams.get('cwd');
@@ -89,13 +100,8 @@ export function createUiRouteHandler(dependencies) {
89
100
  if (!existsSync(folderPath)) {
90
101
  return sendJson(response, 404, { error: `Path not found: ${folderPath}` });
91
102
  }
92
- const opener = process.platform === 'darwin'
93
- ? 'open'
94
- : process.platform === 'win32'
95
- ? 'explorer'
96
- : 'xdg-open';
97
103
  try {
98
- spawn(opener, [folderPath], { detached: true, stdio: 'ignore' }).unref();
104
+ revealInFileManager(folderPath);
99
105
  return sendJson(response, 200, { ok: true, path: folderPath });
100
106
  }
101
107
  catch (error) {
@@ -221,6 +227,49 @@ export function createUiRouteHandler(dependencies) {
221
227
  const recents = await recentProjectsService.addRecent(cwd);
222
228
  return sendJson(response, 200, { recents });
223
229
  }
230
+ case '/api/fs/browse': {
231
+ if (request.method !== 'GET') {
232
+ return sendJson(response, 405, { error: 'Method not allowed' });
233
+ }
234
+ const raw = url.searchParams.get('path');
235
+ const mode = url.searchParams.get('mode') === 'file' ? 'file' : 'dir';
236
+ const startPath = raw && path.isAbsolute(raw) ? raw : os.homedir();
237
+ try {
238
+ const stats = await stat(startPath);
239
+ if (!stats.isDirectory()) {
240
+ return sendJson(response, 400, { error: 'Path is not a directory' });
241
+ }
242
+ const entries = await readdir(startPath, { withFileTypes: true });
243
+ const items = entries
244
+ .filter((entry) => !entry.name.startsWith('.'))
245
+ .filter((entry) => {
246
+ if (mode === 'file')
247
+ return entry.isDirectory() || entry.isFile();
248
+ return entry.isDirectory();
249
+ })
250
+ .map((entry) => ({
251
+ name: entry.name,
252
+ isDir: entry.isDirectory(),
253
+ }))
254
+ .sort((a, b) => {
255
+ if (a.isDir !== b.isDir)
256
+ return a.isDir ? -1 : 1;
257
+ return a.name.localeCompare(b.name);
258
+ });
259
+ const parent = path.dirname(startPath);
260
+ return sendJson(response, 200, {
261
+ path: startPath,
262
+ parent: parent === startPath ? null : parent,
263
+ home: os.homedir(),
264
+ entries: items,
265
+ });
266
+ }
267
+ catch (error) {
268
+ return sendJson(response, 404, {
269
+ error: error instanceof Error ? error.message : 'Cannot read path',
270
+ });
271
+ }
272
+ }
224
273
  case '/api/profiles/snapshot': {
225
274
  if (request.method !== 'POST') {
226
275
  return sendJson(response, 405, { error: 'Method not allowed' });
@@ -258,13 +307,8 @@ export function createUiRouteHandler(dependencies) {
258
307
  if (!existsSync(folderPath)) {
259
308
  return sendJson(response, 404, { error: `Profile folder not found: ${folderPath}` });
260
309
  }
261
- const opener = process.platform === 'darwin'
262
- ? 'open'
263
- : process.platform === 'win32'
264
- ? 'explorer'
265
- : 'xdg-open';
266
310
  try {
267
- spawn(opener, [folderPath], { detached: true, stdio: 'ignore' }).unref();
311
+ revealInFileManager(folderPath);
268
312
  return sendJson(response, 200, { ok: true, path: folderPath });
269
313
  }
270
314
  catch (error) {