feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI

This commit is contained in:
XFlow Deploy
2026-09-28 08:57:32 +05:00
parent edcd30c829
commit 0837b3f612
204 changed files with 24616 additions and 39 deletions
+158
View File
@@ -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);
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@xflow/desktop-cli",
"version": "1.0.0",
"description": "XFlow Windows/Linux Native CLI and Agent Bridge",
"type": "module",
"bin": {
"xflow": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
},
"dependencies": {
"commander": "^12.0.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}
+164
View File
@@ -0,0 +1,164 @@
#!/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');
interface XFlowConfig {
serverUrl: string;
userId: string;
apiKey?: string;
}
function loadConfig(): XFlowConfig {
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: XFlowConfig) {
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
console.log(`[XFlow] Настройки сохранены в ${CONFIG_FILE}`);
}
async function addNote(title: string, listTitle: string = 'Фильмы', category: string = '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() as any;
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: any) {
console.error(`\x1b[31m✖ Не удалось связаться с сервером XFlow (${config.serverUrl}):\x1b[0m ${err.message}`);
console.log(` Убедитесь, что сервер XFlow запущен на ${config.serverUrl}`);
}
}
async function listNotes(category?: string) {
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() as any;
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: any) {
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);