@siteboon/claude-code-ui 1.15.0 → 1.16.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/dist/index.html CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Prevent zoom on iOS -->
27
27
  <meta name="format-detection" content="telephone=no" />
28
- <script type="module" crossorigin src="/assets/index-Dqg05I_l.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-CHjSV-rX.js"></script>
29
29
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DcyRfQm3.js">
30
30
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CJLzwpLB.js">
31
31
  <link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DfaPXD3y.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteboon/claude-code-ui",
3
- "version": "1.15.0",
3
+ "version": "1.16.2",
4
4
  "description": "A web-based UI for Claude Code CLI",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -14,7 +14,7 @@
14
14
  "dist/",
15
15
  "README.md"
16
16
  ],
17
- "homepage": "https://claudecodeui.siteboon.ai",
17
+ "homepage": "https://cloudcli.ai",
18
18
  "repository": {
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/siteboon/claudecodeui.git"
@@ -28,6 +28,7 @@
28
28
  "client": "vite --host",
29
29
  "build": "vite build",
30
30
  "preview": "vite preview",
31
+ "typecheck": "tsc --noEmit -p tsconfig.json",
31
32
  "start": "npm run build && npm run server",
32
33
  "release": "./release.sh"
33
34
  },
@@ -96,6 +97,7 @@
96
97
  "ws": "^8.14.2"
97
98
  },
98
99
  "devDependencies": {
100
+ "@types/node": "^22.19.7",
99
101
  "@types/react": "^18.2.43",
100
102
  "@types/react-dom": "^18.2.17",
101
103
  "@vitejs/plugin-react": "^4.6.0",
@@ -107,6 +109,7 @@
107
109
  "release-it": "^19.0.5",
108
110
  "sharp": "^0.34.2",
109
111
  "tailwindcss": "^3.4.0",
112
+ "typescript": "^5.9.3",
110
113
  "vite": "^7.0.4"
111
114
  }
112
115
  }
package/server/index.js CHANGED
@@ -70,7 +70,7 @@ import mcpUtilsRoutes from './routes/mcp-utils.js';
70
70
  import commandsRoutes from './routes/commands.js';
71
71
  import settingsRoutes from './routes/settings.js';
72
72
  import agentRoutes from './routes/agent.js';
73
- import projectsRoutes, { FORBIDDEN_PATHS } from './routes/projects.js';
73
+ import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes/projects.js';
74
74
  import cliAuthRoutes from './routes/cli-auth.js';
75
75
  import userRoutes from './routes/user.js';
76
76
  import codexRoutes from './routes/codex.js';
@@ -484,22 +484,42 @@ app.post('/api/projects/create', authenticateToken, async (req, res) => {
484
484
  }
485
485
  });
486
486
 
487
+ const expandWorkspacePath = (inputPath) => {
488
+ if (!inputPath) return inputPath;
489
+ if (inputPath === '~') {
490
+ return WORKSPACES_ROOT;
491
+ }
492
+ if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
493
+ return path.join(WORKSPACES_ROOT, inputPath.slice(2));
494
+ }
495
+ return inputPath;
496
+ };
497
+
487
498
  // Browse filesystem endpoint for project suggestions - uses existing getFileTree
488
499
  app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
489
500
  try {
490
501
  const { path: dirPath } = req.query;
491
502
 
503
+ console.log('[API] Browse filesystem request for path:', dirPath);
504
+ console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
492
505
  // Default to home directory if no path provided
493
- const homeDir = os.homedir();
494
- let targetPath = dirPath ? dirPath.replace('~', homeDir) : homeDir;
506
+ const defaultRoot = WORKSPACES_ROOT;
507
+ let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
495
508
 
496
509
  // Resolve and normalize the path
497
510
  targetPath = path.resolve(targetPath);
511
+
512
+ // Security check - ensure path is within allowed workspace root
513
+ const validation = await validateWorkspacePath(targetPath);
514
+ if (!validation.valid) {
515
+ return res.status(403).json({ error: validation.error });
516
+ }
517
+ const resolvedPath = validation.resolvedPath || targetPath;
498
518
 
499
519
  // Security check - ensure path is accessible
500
520
  try {
501
- await fs.promises.access(targetPath);
502
- const stats = await fs.promises.stat(targetPath);
521
+ await fs.promises.access(resolvedPath);
522
+ const stats = await fs.promises.stat(resolvedPath);
503
523
 
504
524
  if (!stats.isDirectory()) {
505
525
  return res.status(400).json({ error: 'Path is not a directory' });
@@ -509,7 +529,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
509
529
  }
510
530
 
511
531
  // Use existing getFileTree function with shallow depth (only direct children)
512
- const fileTree = await getFileTree(targetPath, 1, 0, false); // maxDepth=1, showHidden=false
532
+ const fileTree = await getFileTree(resolvedPath, 1, 0, false); // maxDepth=1, showHidden=false
513
533
 
514
534
  // Filter only directories and format for suggestions
515
535
  const directories = fileTree
@@ -529,7 +549,13 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
529
549
 
530
550
  // Add common directories if browsing home directory
531
551
  const suggestions = [];
532
- if (targetPath === homeDir) {
552
+ let resolvedWorkspaceRoot = defaultRoot;
553
+ try {
554
+ resolvedWorkspaceRoot = await fsPromises.realpath(defaultRoot);
555
+ } catch (error) {
556
+ // Use default root as-is if realpath fails
557
+ }
558
+ if (resolvedPath === resolvedWorkspaceRoot) {
533
559
  const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
534
560
  const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
535
561
  const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
@@ -540,7 +566,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
540
566
  }
541
567
 
542
568
  res.json({
543
- path: targetPath,
569
+ path: resolvedPath,
544
570
  suggestions: suggestions
545
571
  });
546
572
 
@@ -556,22 +582,13 @@ app.post('/api/create-folder', authenticateToken, async (req, res) => {
556
582
  if (!folderPath) {
557
583
  return res.status(400).json({ error: 'Path is required' });
558
584
  }
559
- const homeDir = os.homedir();
560
- const targetPath = path.resolve(folderPath.replace('~', homeDir));
561
- const normalizedPath = path.normalize(targetPath);
562
- const comparePath = normalizedPath.toLowerCase();
563
- const forbiddenLower = FORBIDDEN_PATHS.map(p => p.toLowerCase());
564
- if (forbiddenLower.includes(comparePath) || comparePath === '/') {
565
- return res.status(403).json({ error: 'Cannot create folders in system directories' });
566
- }
567
- for (const forbidden of forbiddenLower) {
568
- if (comparePath.startsWith(forbidden + path.sep)) {
569
- if (forbidden === '/var' && (comparePath.startsWith('/var/tmp') || comparePath.startsWith('/var/folders'))) {
570
- continue;
571
- }
572
- return res.status(403).json({ error: `Cannot create folders in system directory: ${forbidden}` });
573
- }
585
+ const expandedPath = expandWorkspacePath(folderPath);
586
+ const resolvedInput = path.resolve(expandedPath);
587
+ const validation = await validateWorkspacePath(resolvedInput);
588
+ if (!validation.valid) {
589
+ return res.status(403).json({ error: validation.error });
574
590
  }
591
+ const targetPath = validation.resolvedPath || resolvedInput;
575
592
  const parentDir = path.dirname(targetPath);
576
593
  try {
577
594
  await fs.promises.access(parentDir);
@@ -13,7 +13,7 @@ function sanitizeGitError(message, token) {
13
13
  }
14
14
 
15
15
  // Configure allowed workspace root (defaults to user's home directory)
16
- const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
16
+ export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
17
17
 
18
18
  // System-critical paths that should never be used as workspace directories
19
19
  export const FORBIDDEN_PATHS = [
@@ -48,7 +48,7 @@ export const FORBIDDEN_PATHS = [
48
48
  * @param {string} requestedPath - The path to validate
49
49
  * @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
50
50
  */
51
- async function validateWorkspacePath(requestedPath) {
51
+ export async function validateWorkspacePath(requestedPath) {
52
52
  try {
53
53
  // Resolve to absolute path
54
54
  let absolutePath = path.resolve(requestedPath);