@siteboon/claude-code-ui 1.15.0 → 1.16.3
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-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-D9u4X-u6.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.
|
|
3
|
+
"version": "1.16.3",
|
|
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://
|
|
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, {
|
|
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
|
|
494
|
-
let targetPath = dirPath ? dirPath
|
|
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(
|
|
502
|
-
const stats = await fs.promises.stat(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
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);
|