/
threed
/
claud-web
Обзор
Документация
Войти
/
threed
/
claud-web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
lib/projects.js
93 строки
3 KB
threed
Claud Web: веб-терминал Claude Code — tmux-вкладки, мультипроект, мобильная версия
03 авг 2026, 20:30
03 авг 2026, 20:30
710eb6a
Код
Авторство
О чём код?
/** * Список «открытых» проектов (папок). Хранится в data/projects.json. * Удаление проекта из списка НЕ удаляет папку с диска. */ const fs = require('fs'); const os = require('os'); const path = require('path'); const crypto = require('crypto'); const DATA_DIR = path.join(__dirname, '..', 'data'); const PROJECTS_FILE = path.join(DATA_DIR, 'projects.json'); function load() { try { return JSON.parse(fs.readFileSync(PROJECTS_FILE, 'utf8')); } catch { return []; } } function save(projects) { fs.mkdirSync(DATA_DIR, { recursive: true }); fs.writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2)); } function projectId(dirPath) { return crypto.createHash('sha1').update(dirPath).digest('hex').slice(0, 10); } function list() { return load().map(p => ({ ...p, exists: fs.existsSync(p.path) })); } function get(id) { return load().find(p => p.id === id) || null; } function add(dirPath) { if (typeof dirPath !== 'string' || !path.isAbsolute(dirPath)) { throw new Error('Нужен абсолютный путь к папке'); } const resolved = path.resolve(dirPath); let stat; try { stat = fs.statSync(resolved); } catch { throw new Error(`Папка не найдена: ${resolved}`); } if (!stat.isDirectory()) { throw new Error(`Это не папка: ${resolved}`); } const projects = load(); const id = projectId(resolved); if (!projects.some(p => p.id === id)) { projects.push({ id, path: resolved, name: path.basename(resolved) }); save(projects); } return { id, path: resolved, name: path.basename(resolved) }; } function remove(id) { const projects = load(); const filtered = projects.filter(p => p.id !== id); if (filtered.length !== projects.length) save(filtered); } /** Листинг подпапок для «проводника» выбора папки проекта. */ function browse(dirPath) { const target = dirPath && path.isAbsolute(dirPath) ? path.resolve(dirPath) : os.homedir(); let entries; try { entries = fs.readdirSync(target, { withFileTypes: true }); } catch (err) { throw new Error(`Не удалось открыть ${target}: ${err.code === 'EACCES' ? 'нет доступа' : err.message}`); } const dirs = entries .filter(e => e.isDirectory() && !e.name.startsWith('.')) .map(e => e.name) .sort((a, b) => a.localeCompare(b)); const parent = path.dirname(target); return { path: target, parent: parent === target ? null : parent, dirs }; } module.exports = { list, get, add, remove, browse, projectId };