mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-01-31 05:47:30 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9719256fc | ||
|
|
55caaf060c | ||
|
|
f9c7321c8c | ||
|
|
88bda6e5c0 | ||
|
|
86b421c790 | ||
|
|
41ef84c283 | ||
|
|
53224e47b6 | ||
|
|
bbb51dbf99 |
37
package-lock.json
generated
37
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@siteboon/claude-code-ui",
|
||||
"version": "1.15.0",
|
||||
"version": "1.16.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@siteboon/claude-code-ui",
|
||||
"version": "1.15.0",
|
||||
"version": "1.16.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.29",
|
||||
@@ -68,6 +68,7 @@
|
||||
"cloudcli": "server/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.7",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
@@ -79,6 +80,7 @@
|
||||
"release-it": "^19.0.5",
|
||||
"sharp": "^0.34.2",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
},
|
||||
@@ -2906,6 +2908,16 @@
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
|
||||
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-path": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz",
|
||||
@@ -11662,6 +11674,20 @@
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-js": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||
@@ -11686,6 +11712,13 @@
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@siteboon/claude-code-ui",
|
||||
"version": "1.15.0",
|
||||
"version": "1.16.3",
|
||||
"description": "A web-based UI for Claude Code CLI",
|
||||
"type": "module",
|
||||
"main": "server/index.js",
|
||||
@@ -14,7 +14,7 @@
|
||||
"dist/",
|
||||
"README.md"
|
||||
],
|
||||
"homepage": "https://claudecodeui.siteboon.ai",
|
||||
"homepage": "https://cloudcli.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/siteboon/claudecodeui.git"
|
||||
@@ -28,6 +28,7 @@
|
||||
"client": "vite --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json",
|
||||
"start": "npm run build && npm run server",
|
||||
"release": "./release.sh"
|
||||
},
|
||||
@@ -96,6 +97,7 @@
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.7",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
@@ -107,6 +109,7 @@
|
||||
"release-it": "^19.0.5",
|
||||
"sharp": "^0.34.2",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ import mcpUtilsRoutes from './routes/mcp-utils.js';
|
||||
import commandsRoutes from './routes/commands.js';
|
||||
import settingsRoutes from './routes/settings.js';
|
||||
import agentRoutes from './routes/agent.js';
|
||||
import projectsRoutes, { FORBIDDEN_PATHS } from './routes/projects.js';
|
||||
import projectsRoutes, { WORKSPACES_ROOT, validateWorkspacePath } from './routes/projects.js';
|
||||
import cliAuthRoutes from './routes/cli-auth.js';
|
||||
import userRoutes from './routes/user.js';
|
||||
import codexRoutes from './routes/codex.js';
|
||||
@@ -484,22 +484,42 @@ app.post('/api/projects/create', authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
const expandWorkspacePath = (inputPath) => {
|
||||
if (!inputPath) return inputPath;
|
||||
if (inputPath === '~') {
|
||||
return WORKSPACES_ROOT;
|
||||
}
|
||||
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
||||
return path.join(WORKSPACES_ROOT, inputPath.slice(2));
|
||||
}
|
||||
return inputPath;
|
||||
};
|
||||
|
||||
// Browse filesystem endpoint for project suggestions - uses existing getFileTree
|
||||
app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { path: dirPath } = req.query;
|
||||
|
||||
console.log('[API] Browse filesystem request for path:', dirPath);
|
||||
console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
|
||||
// Default to home directory if no path provided
|
||||
const homeDir = os.homedir();
|
||||
let targetPath = dirPath ? dirPath.replace('~', homeDir) : homeDir;
|
||||
const defaultRoot = WORKSPACES_ROOT;
|
||||
let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
|
||||
|
||||
// Resolve and normalize the path
|
||||
targetPath = path.resolve(targetPath);
|
||||
|
||||
// Security check - ensure path is within allowed workspace root
|
||||
const validation = await validateWorkspacePath(targetPath);
|
||||
if (!validation.valid) {
|
||||
return res.status(403).json({ error: validation.error });
|
||||
}
|
||||
const resolvedPath = validation.resolvedPath || targetPath;
|
||||
|
||||
// Security check - ensure path is accessible
|
||||
try {
|
||||
await fs.promises.access(targetPath);
|
||||
const stats = await fs.promises.stat(targetPath);
|
||||
await fs.promises.access(resolvedPath);
|
||||
const stats = await fs.promises.stat(resolvedPath);
|
||||
|
||||
if (!stats.isDirectory()) {
|
||||
return res.status(400).json({ error: 'Path is not a directory' });
|
||||
@@ -509,7 +529,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
// Use existing getFileTree function with shallow depth (only direct children)
|
||||
const fileTree = await getFileTree(targetPath, 1, 0, false); // maxDepth=1, showHidden=false
|
||||
const fileTree = await getFileTree(resolvedPath, 1, 0, false); // maxDepth=1, showHidden=false
|
||||
|
||||
// Filter only directories and format for suggestions
|
||||
const directories = fileTree
|
||||
@@ -529,7 +549,13 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
||||
|
||||
// Add common directories if browsing home directory
|
||||
const suggestions = [];
|
||||
if (targetPath === homeDir) {
|
||||
let resolvedWorkspaceRoot = defaultRoot;
|
||||
try {
|
||||
resolvedWorkspaceRoot = await fsPromises.realpath(defaultRoot);
|
||||
} catch (error) {
|
||||
// Use default root as-is if realpath fails
|
||||
}
|
||||
if (resolvedPath === resolvedWorkspaceRoot) {
|
||||
const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
|
||||
const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
|
||||
const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
|
||||
@@ -540,7 +566,7 @@ app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
res.json({
|
||||
path: targetPath,
|
||||
path: resolvedPath,
|
||||
suggestions: suggestions
|
||||
});
|
||||
|
||||
@@ -556,22 +582,13 @@ app.post('/api/create-folder', authenticateToken, async (req, res) => {
|
||||
if (!folderPath) {
|
||||
return res.status(400).json({ error: 'Path is required' });
|
||||
}
|
||||
const homeDir = os.homedir();
|
||||
const targetPath = path.resolve(folderPath.replace('~', homeDir));
|
||||
const normalizedPath = path.normalize(targetPath);
|
||||
const comparePath = normalizedPath.toLowerCase();
|
||||
const forbiddenLower = FORBIDDEN_PATHS.map(p => p.toLowerCase());
|
||||
if (forbiddenLower.includes(comparePath) || comparePath === '/') {
|
||||
return res.status(403).json({ error: 'Cannot create folders in system directories' });
|
||||
}
|
||||
for (const forbidden of forbiddenLower) {
|
||||
if (comparePath.startsWith(forbidden + path.sep)) {
|
||||
if (forbidden === '/var' && (comparePath.startsWith('/var/tmp') || comparePath.startsWith('/var/folders'))) {
|
||||
continue;
|
||||
}
|
||||
return res.status(403).json({ error: `Cannot create folders in system directory: ${forbidden}` });
|
||||
}
|
||||
const expandedPath = expandWorkspacePath(folderPath);
|
||||
const resolvedInput = path.resolve(expandedPath);
|
||||
const validation = await validateWorkspacePath(resolvedInput);
|
||||
if (!validation.valid) {
|
||||
return res.status(403).json({ error: validation.error });
|
||||
}
|
||||
const targetPath = validation.resolvedPath || resolvedInput;
|
||||
const parentDir = path.dirname(targetPath);
|
||||
try {
|
||||
await fs.promises.access(parentDir);
|
||||
|
||||
@@ -13,7 +13,7 @@ function sanitizeGitError(message, token) {
|
||||
}
|
||||
|
||||
// Configure allowed workspace root (defaults to user's home directory)
|
||||
const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
||||
export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
|
||||
|
||||
// System-critical paths that should never be used as workspace directories
|
||||
export const FORBIDDEN_PATHS = [
|
||||
@@ -48,7 +48,7 @@ export const FORBIDDEN_PATHS = [
|
||||
* @param {string} requestedPath - The path to validate
|
||||
* @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
|
||||
*/
|
||||
async function validateWorkspacePath(requestedPath) {
|
||||
export async function validateWorkspacePath(requestedPath) {
|
||||
try {
|
||||
// Resolve to absolute path
|
||||
let absolutePath = path.resolve(requestedPath);
|
||||
|
||||
@@ -31,13 +31,13 @@ function LoginModal({
|
||||
|
||||
switch (provider) {
|
||||
case 'claude':
|
||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /login --dangerously-skip-permissions';
|
||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /exit --dangerously-skip-permissions';
|
||||
case 'cursor':
|
||||
return 'cursor-agent login';
|
||||
case 'codex':
|
||||
return isPlatform ? 'codex login --device-auth' : 'codex login';
|
||||
default:
|
||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /login --dangerously-skip-permissions';
|
||||
return isAuthenticated ? 'claude setup-token --dangerously-skip-permissions' : 'claude /exit --dangerously-skip-permissions';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ const Onboarding = ({ onComplete }) => {
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const [activeLoginProvider, setActiveLoginProvider] = useState(null);
|
||||
const [selectedProject] = useState({ name: 'default', fullPath: '' });
|
||||
const isPlatform = import.meta.env.VITE_IS_PLATFORM === 'true';
|
||||
const [selectedProject] = useState({ name: 'default', fullPath: isPlatform ? '/workspace' : '' });
|
||||
|
||||
const [claudeAuthStatus, setClaudeAuthStatus] = useState({
|
||||
authenticated: false,
|
||||
|
||||
@@ -183,7 +183,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || t('projectWizard.errors.failedToCreate'));
|
||||
throw new Error(data.details || data.error || t('projectWizard.errors.failedToCreate'));
|
||||
}
|
||||
|
||||
if (onProjectCreated) {
|
||||
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
// "checkJs": true,
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src", "shared", "vite.config.js"]
|
||||
}
|
||||
Reference in New Issue
Block a user