feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
const CONFIG_FILE = path.join(os.homedir(), '.xflow.json');
|
||||
|
||||
function loadConfig() {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
||||
} catch {
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
return {
|
||||
serverUrl: process.env.XFLOW_SERVER_URL || 'http://localhost:3000',
|
||||
userId: process.env.XFLOW_USER_ID || 'default-user'
|
||||
};
|
||||
}
|
||||
|
||||
function saveConfig(config) {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
||||
console.log(`[XFlow] Настройки сохранены в ${CONFIG_FILE}`);
|
||||
}
|
||||
|
||||
async function addNote(title, listTitle = 'Фильмы на просмотр', category = 'movies') {
|
||||
const config = loadConfig();
|
||||
const url = `${config.serverUrl.replace(/\/$/, '')}/api/v1/lists/items`;
|
||||
|
||||
console.log(`[XFlow Desktop] Отправка заметки: "${title}" в список "${listTitle}"...`);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
listTitle,
|
||||
category,
|
||||
ownerId: config.userId
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (res.ok && data.success) {
|
||||
console.log(`\x1b[32m✔ Успешно добавлено!\x1b[0m ID: ${data.item.id}, Статус: ${data.item.status}`);
|
||||
console.log(` Синхронизировано с сервером и отправлено на мобильный клиент.`);
|
||||
} else {
|
||||
console.error(`\x1b[31m✖ Ошибка сервера:\x1b[0m`, data.message || data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`\x1b[31m✖ Не удалось связаться с сервером XFlow (${config.serverUrl}):\x1b[0m ${err.message}`);
|
||||
console.log(` Убедитесь, что сервер XFlow запущен на ${config.serverUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function listNotes(category) {
|
||||
const config = loadConfig();
|
||||
const query = category ? `?category=${encodeURIComponent(category)}` : '';
|
||||
const url = `${config.serverUrl.replace(/\/$/, '')}/api/v1/lists${query}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
if (res.ok && data.success) {
|
||||
console.log(`\n\x1b[1m=== Списки XFlow ===\x1b[0m`);
|
||||
for (const list of data.lists) {
|
||||
console.log(`\n📂 \x1b[36m${list.title}\x1b[0m (${list.category}) [${list.items?.length || 0} элементов]:`);
|
||||
for (const item of list.items || []) {
|
||||
const check = item.isCompleted ? '☑' : '☐';
|
||||
console.log(` ${check} ${item.title} \x1b[90m(статус: ${item.status})\x1b[0m`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
} else {
|
||||
console.error(`Ошибка:`, data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Ошибка связи с сервером: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const command = args[0];
|
||||
|
||||
if (!command || command === '--help' || command === '-h') {
|
||||
console.log(`
|
||||
\x1b[1m🚀 XFlow CLI — Native Desktop & Terminal Agent Bridge\x1b[0m
|
||||
|
||||
Использование:
|
||||
xflow movie "<название фильма>" Быстро добавить фильм в список на просмотр
|
||||
xflow add "<заметка>" [--list <список>] Добавить элемент в указанный список
|
||||
xflow list [категория] Показать списки и элементы
|
||||
xflow config --url <url> --user <id> Настроить подключение к серверу XFlow
|
||||
|
||||
Примеры:
|
||||
xflow movie "Интерстеллар"
|
||||
xflow add "Купить молоко" --list "Покупки"
|
||||
xflow list
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'movie') {
|
||||
const title = args.slice(1).join(' ');
|
||||
if (!title) {
|
||||
console.error('Укажите название фильма: xflow movie "Интерстеллар"');
|
||||
return;
|
||||
}
|
||||
await addNote(title, 'Фильмы на просмотр', 'movies');
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'add') {
|
||||
const title = args[1];
|
||||
let listTitle = 'Общий список';
|
||||
let category = 'general';
|
||||
|
||||
for (let i = 2; i < args.length; i++) {
|
||||
if (args[i] === '--list' && args[i + 1]) {
|
||||
listTitle = args[i + 1];
|
||||
if (listTitle.toLowerCase().includes('фильм')) category = 'movies';
|
||||
if (listTitle.toLowerCase().includes('покупк')) category = 'shopping';
|
||||
}
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
console.error('Укажите текст заметки: xflow add "Заметка"');
|
||||
return;
|
||||
}
|
||||
await addNote(title, listTitle, category);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'list') {
|
||||
const category = args[1];
|
||||
await listNotes(category);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'config') {
|
||||
const config = loadConfig();
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
if (args[i] === '--url' && args[i + 1]) config.serverUrl = args[i + 1];
|
||||
if (args[i] === '--user' && args[i + 1]) config.userId = args[i + 1];
|
||||
}
|
||||
saveConfig(config);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`Неизвестная команда: ${command}. Используйте --help`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user