@flowdevcli/flowdev 1.0.0

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.
@@ -0,0 +1,10 @@
1
+ import chalk from 'chalk';
2
+ import { generateTree } from '../../services/file-system.js';
3
+
4
+ export async function treeCommand() {
5
+ console.log(chalk.bold.cyan('\nProject\' tree :'));
6
+ console.log(chalk.cyan('.\n'));
7
+
8
+ const tree = await generateTree(process.cwd());
9
+ console.log(tree);
10
+ }
@@ -0,0 +1,113 @@
1
+ import { program } from 'commander';
2
+ import { createRequire } from 'node:module';
3
+ import chalk from 'chalk';
4
+ import { statsCommand } from '../commands/utils/stats.js';
5
+ import { treeCommand } from '../commands/utils/tree.js';
6
+ import { dockerizeCommand } from '../commands/devops/dockerize.js';
7
+ import {findCommand} from '../commands/scaffold/find.js'
8
+ import { askCommand } from '../commands/ai/ask.js';
9
+ import { explainCommand } from '../commands/ai/explain.js';
10
+ import { envCommand } from '../commands/devops/env.js';
11
+ import { kubeCommand } from '../commands/devops/kube.js';
12
+ import { generateCommand } from '../commands/scaffold/generate.js';
13
+ import { auditCommand } from '../commands/ai/audit.js';
14
+ import { testCommand } from '../commands/ai/test.js';
15
+
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const pkg = require('../../package.json');
19
+
20
+ export function setupCLI() {
21
+ program
22
+ .name('flowdev')
23
+ .description('Intelligent CLI to automate your workflow')
24
+ .version(pkg.version, '-v, --version', 'output the current version');
25
+
26
+
27
+ program
28
+ .command('version')
29
+ .description('Display the current version of FlowDev')
30
+ .action(() => {
31
+ console.log(`\n ${chalk.bold('FLOWDEV')} ${chalk.cyan('v' + pkg.version)}`);
32
+ });
33
+
34
+ program
35
+ .command('tree')
36
+ .description('Displays the project tree')
37
+ .action(treeCommand);
38
+
39
+
40
+ program.on('command:*', () => {
41
+ console.error('Invalid command. Type "flowdev --help" to see the list.');
42
+ process.exit(1);
43
+ });
44
+
45
+ program
46
+ .command('dockerize')
47
+ .description('Automatically generates Dockerfile and docker-compose.yml')
48
+ .action(dockerizeCommand);
49
+
50
+ program
51
+ .command('stats')
52
+ .description('Analyzes the current project and displays code statistics')
53
+ .action(async () => {
54
+ await statsCommand();
55
+ });
56
+
57
+
58
+ program
59
+ .command('find <pattern>')
60
+ .description('Search for a text in the project')
61
+ .option('-e, --ext <extensions>', ' Filter by extensions(ex: js,md,json)')
62
+ .action((pattern, options) => findCommand(pattern, options));
63
+
64
+ program
65
+ .command('ask <question...>')
66
+ .description('Ask a question to the local AI ')
67
+ .action((args) => {
68
+ const question = args.join('');
69
+ askCommand(question)
70
+ });
71
+
72
+ program
73
+ .command('explain <file>')
74
+ .description('Analyzes and explains the contents of a source file')
75
+ .action(async (file) => {
76
+ await explainCommand(file);
77
+ })
78
+
79
+ program
80
+ .command('env')
81
+ .description('Scans project code and generates a .env.example file')
82
+ .action(async () => {
83
+ await envCommand();
84
+ });
85
+
86
+ program
87
+ .command('kube')
88
+ .description('Generate Kubernetes deployment and service manifests')
89
+ .action(async () => {
90
+ await kubeCommand();
91
+ });
92
+
93
+ program
94
+ .command('generate')
95
+ .description('Generates a complete project (React, Django, Vue, etc...) with Git and dependencies')
96
+ .action(generateCommand);
97
+
98
+ program
99
+ .command('audit')
100
+ .description('Audit your code for bugs, security, and performance with AI')
101
+ .action(async () => {
102
+ await auditCommand();
103
+ });
104
+
105
+ program
106
+ .command('test <file>')
107
+ .description('Automatically generates unit tests for a specific file')
108
+ .action(async (file) => {
109
+ await testCommand(file);
110
+ });
111
+
112
+ return program;
113
+ }
@@ -0,0 +1,77 @@
1
+ import fs from 'node:fs/promises';
2
+ import { createReadStream } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'out', 'target']);
6
+ const SUPPORTED_EXTENSIONS = {
7
+ '.js': 'JavaScript',
8
+ '.ts': 'TypeScript',
9
+ '.py': 'Python',
10
+ '.go': 'Go',
11
+ '.java': 'Java',
12
+ '.rb': 'Ruby',
13
+ '.php': 'PHP',
14
+ '.html': 'HTML',
15
+ '.css': 'CSS',
16
+ '.json': 'JSON',
17
+ '.yml': 'YAML',
18
+ '.yaml': 'YAML'
19
+ };
20
+
21
+ const countLinesInFile = (filePath) => {
22
+ return new Promise((resolve) => {
23
+ let count = 0;
24
+ createReadStream(filePath)
25
+ .on('data', (chunk) => {
26
+ for (let i = 0; i < chunk.length; ++i) {
27
+ if (chunk[i] === 10) count++;
28
+ }
29
+ })
30
+ .on('end', () => resolve(count))
31
+ .on('error', () => resolve(0));
32
+ });
33
+ };
34
+
35
+ async function scanDirectory(dirPath, stats) {
36
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
37
+
38
+ const tasks = entries.map(async (entry) => {
39
+ const fullPath = path.join(dirPath, entry.name);
40
+
41
+ if (entry.isDirectory()) {
42
+ if (!IGNORED_DIRS.has(entry.name)) {
43
+ await scanDirectory(fullPath, stats);
44
+ }
45
+ } else if (entry.isFile()) {
46
+ const ext = path.extname(entry.name).toLowerCase();
47
+ if (SUPPORTED_EXTENSIONS[ext]) {
48
+ const lang = SUPPORTED_EXTENSIONS[ext];
49
+ const lines = await countLinesInFile(fullPath);
50
+ stats.totalFiles++;
51
+ stats.totalLines += lines;
52
+ if (!stats.languages[lang]) {
53
+ stats.languages[lang] = { files: 0, lines: 0 };
54
+ }
55
+ stats.languages[lang].files++;
56
+ stats.languages[lang].lines += lines;
57
+ }
58
+ }
59
+ });
60
+ await Promise.all(tasks);
61
+ }
62
+
63
+ export async function analyzeProject(rootPath = process.cwd()) {
64
+ const stats = {
65
+ totalFiles: 0,
66
+ totalLines: 0,
67
+ languages: {},
68
+ timestamp: new Date().toISOString()
69
+ };
70
+
71
+ try {
72
+ await scanDirectory(rootPath, stats);
73
+ return stats;
74
+ } catch (error) {
75
+ throw new Error(`Erreur lors de l'analyse du projet : ${error.message}`);
76
+ }
77
+ }
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+
5
+ const IGNORED = new Set(['node_modules', '.git', 'dist']);
6
+
7
+ export async function generateTree(dir, prefix = '') {
8
+ const entries = await fs.readdir(dir, { withFileTypes: true });
9
+ let output = '';
10
+
11
+ entries.sort((a, b) => b.isDirectory() - a.isDirectory());
12
+
13
+ for (let i = 0; i < entries.length; i++) {
14
+ const entry = entries[i];
15
+ if (IGNORED.has(entry.name)) continue;
16
+
17
+ const isLast = i === entries.length - 1;
18
+ const connector = isLast ? '└── ' : '├── ';
19
+ const name = entry.isDirectory()
20
+ ? chalk.blue.bold(entry.name + '/')
21
+ : chalk.white(entry.name);
22
+
23
+ output += `${prefix}${connector}${name}\n`;
24
+
25
+ if (entry.isDirectory()) {
26
+ const newPrefix = prefix + (isLast ? ' ' : '│ ');
27
+ output += await generateTree(path.join(dir, entry.name), newPrefix);
28
+ }
29
+ }
30
+ return output;
31
+ }
@@ -0,0 +1,40 @@
1
+
2
+ export const nodeDockerfile = (version, port, command) => `
3
+ # Stage 1: Build
4
+ FROM node:${version}-alpine AS builder
5
+ WORKDIR /app
6
+ COPY package*.json ./
7
+ RUN npm ci
8
+ COPY . .
9
+
10
+ # Stage 2: Production
11
+ FROM node:${version}-alpine
12
+ WORKDIR /app
13
+ COPY --from=builder /app ./
14
+ EXPOSE ${port}
15
+ # Formats the command string into a JSON array for CMD
16
+ CMD ["${command.split(' ').join('", "')}"]
17
+ `.trim();
18
+
19
+
20
+ export const pythonDockerfile = (version, port, startFile) => `
21
+ FROM python:${version}-slim
22
+ WORKDIR /app
23
+ COPY requirements.txt .
24
+ RUN pip install --no-cache-dir -r requirements.txt
25
+ COPY . .
26
+ EXPOSE ${port}
27
+ CMD ["python", "${startFile}"]
28
+ `.trim();
29
+
30
+ export const dockerCompose = (projectName, port) => `
31
+ version: '3.8'
32
+ services:
33
+ ${projectName}:
34
+ build: .
35
+ ports:
36
+ - "${port}:${port}"
37
+ environment:
38
+ - NODE_ENV=production
39
+ restart: always
40
+ `.trim();
@@ -0,0 +1,49 @@
1
+ import figlet from 'figlet';
2
+ import gradient from 'gradient-string';
3
+ import chalk from 'chalk';
4
+
5
+ export async function showLogo() {
6
+ try {
7
+ const text = 'FLOWDEV';
8
+ const subTitle = "System intelligence & Workflow automation";
9
+
10
+ const data = figlet.textSync(text, {
11
+ font: 'ANSI Shadow',
12
+ horizontalLayout: 'full',
13
+ });
14
+
15
+ const colors = [
16
+ { color: '#fe0000', pos: 0 },
17
+ { color: '#f02e20', pos: 0.2 },
18
+ { color: '#f35322', pos: 0.6 },
19
+ { color: '#ff6a00', pos: 1 }
20
+ ];
21
+
22
+ const flowGradient = gradient(colors);
23
+
24
+ const lines = data.split('\n').filter(line => line.trim() !== "");
25
+ const maxLength = Math.max(...lines.map(l => l.length));
26
+
27
+ const padding = " ";
28
+
29
+ const b = (char) => chalk.whiteBright.bold(char);
30
+
31
+ console.log("");
32
+
33
+
34
+ console.log(`${padding}${b('┏━')} ${" ".repeat(maxLength - 2)} ${b('━┓')}`);
35
+
36
+ lines.forEach(line => {
37
+ console.log(`${padding} ${flowGradient(line)}`);
38
+ });
39
+
40
+ console.log(`${padding} ${chalk.whiteBright.bold(subTitle)}`);
41
+
42
+ console.log(`${padding}${b('┗━')} ${" ".repeat(maxLength - 2)} ${b('━┛')}`);
43
+
44
+ console.log("");
45
+
46
+ } catch (err) {
47
+ console.log(chalk.red.bold('\n FLOWDEV'));
48
+ }
49
+ }
@@ -0,0 +1,71 @@
1
+ import ollama from 'ollama';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { exec, spawn } from 'node:child_process';
5
+ import { promisify } from 'util';
6
+ import { logger } from './logger.js';
7
+
8
+ const execAsync = promisify(exec);
9
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
10
+
11
+ async function waitForOllamaServer({ retries = 15, delayMs = 1000 } = {}) {
12
+ for (let i = 0; i < retries; i++) {
13
+ try {
14
+ await ollama.list();
15
+ return true;
16
+ } catch (e) {
17
+ await sleep(delayMs);
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+
23
+ async function installOllamaEngine(spinner) {
24
+ const isWindows = process.platform === 'win32';
25
+ const installCmd = isWindows
26
+ ? 'winget install Ollama.Ollama --silent --accept-source-agreements'
27
+ : 'curl -fsSL https://ollama.com/install.sh | sh';
28
+
29
+
30
+ spinner.text = chalk.yellow("Installing FlowDev's engine package to run the command... (This may take a while)");
31
+
32
+ try {
33
+ await execAsync(installCmd, { maxBuffer: 10 * 1024 * 1024 });
34
+ spinner.succeed(chalk.green('FlowDev Engine installed successfully!'));
35
+ await sleep(1500);
36
+ } catch (error) {
37
+ spinner.fail(chalk.red('Automatic installation failed.'));
38
+ logger.error(`Please install it manually: ${isWindows ? 'winget install Ollama.Ollama' : 'curl -fsSL https://ollama.com/install.sh | sh'}`);
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ export async function ensureEngineReady(spinner, modelName = 'llama3') {
44
+
45
+ try {
46
+ await execAsync('ollama --version');
47
+ } catch (e) {
48
+ await installOllamaEngine(spinner);
49
+ }
50
+
51
+ if (!(await waitForOllamaServer({ retries: 3, delayMs: 1000 }))) {
52
+ spinner.text = chalk.blue('Starting engine in background...');
53
+ const child = spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' });
54
+ child.unref();
55
+
56
+ const ready = await waitForOllamaServer({ retries: 30, delayMs: 1000 });
57
+ if (!ready) throw new Error('Engine server failed to start.');
58
+ }
59
+ let hasModel = false;
60
+ try {
61
+ const models = await ollama.list();
62
+ hasModel = Array.isArray(models?.models) && models.models.some((m) => m.name?.startsWith(modelName));
63
+ } catch (err) { }
64
+
65
+ if (!hasModel) {
66
+ spinner.text = chalk.magenta(`First launch: Downloading AI neurons (${modelName})...`);
67
+ await ollama.pull({ model: modelName });
68
+ spinner.succeed(chalk.green('AI brain loaded!'));
69
+ spinner.start();
70
+ }
71
+ }
@@ -0,0 +1,23 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const logger = {
4
+ info: (msg) => {
5
+ console.log(`${chalk.blue.bold(' INFO')} ${msg}`);
6
+ },
7
+
8
+ success: (msg) => {
9
+ console.log(`${chalk.green.bold(' SUCCESS')} ${msg}`);
10
+ },
11
+
12
+ warn: (msg) => {
13
+ console.log(`${chalk.yellow.bold(' WARN')} ${msg}`);
14
+ },
15
+
16
+ error: (msg) => {
17
+ console.log(`${chalk.red.bold(' ERROR')} ${msg}`);
18
+ },
19
+
20
+ log: (msg) => {
21
+ console.log(msg);
22
+ }
23
+ };