@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.
package/.dockerignore ADDED
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .git
3
+ .env
4
+ dist
5
+ build
6
+ __pycache__
7
+ *.log
package/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ # Auto-generated by FlowDev
2
+ # Environment Variables Example
3
+
4
+ OLLAMA_MODEL=
5
+
package/.eslintrc.json ADDED
File without changes
package/Dockerfile ADDED
@@ -0,0 +1,14 @@
1
+ # Stage 1: Build
2
+ FROM node:18-alpine AS builder
3
+ WORKDIR /app
4
+ COPY package*.json ./
5
+ RUN npm ci
6
+ COPY . .
7
+
8
+ # Stage 2: Production
9
+ FROM node:18-alpine
10
+ WORKDIR /app
11
+ COPY --from=builder /app ./
12
+ EXPOSE 3000
13
+ # Formats the command string into a JSON array for CMD
14
+ CMD ["npm", "run", "dev"]
package/bin/flowdev.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import { showLogo } from '../src/utils/ascii.js';
7
+ import { setupCLI } from '../src/core/cli.js';
8
+
9
+ async function handleBranding() {
10
+ const ppid = process.ppid;
11
+ const sessionFlag = path.join(os.tmpdir(), `flowdev_session_${ppid}`);
12
+
13
+ if (!fs.existsSync(sessionFlag)) {
14
+ await showLogo();
15
+ try {
16
+ fs.writeFileSync(sessionFlag, '');
17
+ } catch (e) {
18
+
19
+ }
20
+ }
21
+ }
22
+
23
+ async function main() {
24
+ await handleBranding();
25
+ const program = setupCLI();
26
+ program.parse(process.argv);
27
+ }
28
+
29
+ main().catch((err) => {
30
+ console.error('\x1b[31m%s\x1b[0m', `Critical error: ${err.message}`);
31
+ process.exit(1);
32
+ });
@@ -0,0 +1,9 @@
1
+ version: '3.8'
2
+ services:
3
+ flowdev:
4
+ build: .
5
+ ports:
6
+ - "3000:3000"
7
+ environment:
8
+ - NODE_ENV=production
9
+ restart: always
package/k8s.yaml ADDED
@@ -0,0 +1,32 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: flowdev
5
+ spec:
6
+ replicas: 2
7
+ selector:
8
+ matchLabels:
9
+ app: flowdev
10
+ template:
11
+ metadata:
12
+ labels:
13
+ app: flowdev
14
+ spec:
15
+ containers:
16
+ - name: flowdev
17
+ image: flowdev:latest
18
+ ports:
19
+ - containerPort: 3000
20
+ ---
21
+ apiVersion: v1
22
+ kind: Service
23
+ metadata:
24
+ name: flowdev-service
25
+ spec:
26
+ selector:
27
+ app: flowdev
28
+ ports:
29
+ - protocol: TCP
30
+ port: 80
31
+ targetPort: 3000
32
+ type: LoadBalancer
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@flowdevcli/flowdev",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered CLI tool",
5
+ "main": "bin/flowdev.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "flowdev": "/bin/flowdev.js",
9
+ "fd": "/bin/flowdev.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node bin/flowdev.js",
13
+ "test": "mocha test/**/*.js",
14
+ "lint": "eslint src/**/*.js"
15
+ },
16
+ "keywords": [
17
+ "cli",
18
+ "devtools",
19
+ "ai",
20
+ "devops",
21
+ "scaffold"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "axios": "^1.6.0",
27
+ "boxen": "^7.1.1",
28
+ "chalk": "^5.3.0",
29
+ "commander": "^11.1.0",
30
+ "dotenv": "^16.3.1",
31
+ "execa": "^8.0.1",
32
+ "figlet": "^1.7.0",
33
+ "fs-extra": "^11.1.1",
34
+ "gradient-string": "^2.0.2",
35
+ "inquirer": "^9.2.11",
36
+ "is-binary-path": "^3.0.0",
37
+ "mammoth": "^1.11.0",
38
+ "ollama": "^0.6.3",
39
+ "ora": "^7.0.1",
40
+ "pdf-parse": "^2.4.5",
41
+ "tabtab": "^3.0.2"
42
+ },
43
+ "devDependencies": {
44
+ "eslint": "^8.52.0",
45
+ "mocha": "^10.2.0"
46
+ },
47
+
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }
@@ -0,0 +1,178 @@
1
+ import ollama from 'ollama';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs-extra';
5
+ import { exec, spawn } from 'node:child_process';
6
+ import { promisify } from 'util';
7
+ import { logger } from '../../utils/logger.js';
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
12
+
13
+
14
+ async function waitForOllamaServer({ retries = 15, delayMs = 1000 } = {}) {
15
+ for (let i = 0; i < retries; i++) {
16
+ try {
17
+ await ollama.list();
18
+ return true;
19
+ } catch (e) {
20
+
21
+ logger.debug(`Ollama not ready (attempt ${i + 1}/${retries}): ${e.message}`);
22
+ await sleep(delayMs);
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+
28
+ async function installOllamaEngine(spinner) {
29
+ const isWindows = process.platform === 'win32';
30
+ const installCmd = isWindows
31
+ ? 'winget install Ollama.Ollama --silent --accept-source-agreements'
32
+ : 'curl -fsSL https://ollama.com/install.sh | sh';
33
+
34
+ spinner.text = chalk.yellow('Downloading FlowDev engine package... (This may take a while)');
35
+
36
+ try {
37
+
38
+ const { stdout, stderr } = await execAsync(installCmd, { maxBuffer: 10 * 1024 * 1024 });
39
+ logger.debug('install stdout: ' + (stdout || '').toString().slice(0, 2000));
40
+ logger.debug('install stderr: ' + (stderr || '').toString().slice(0, 2000));
41
+
42
+ spinner.succeed(chalk.green('FlowDev Engine installed successfully!'));
43
+
44
+ await sleep(1500);
45
+ } catch (error) {
46
+
47
+ spinner.fail(chalk.red('Automatic installation failed (admin rights or network?)'));
48
+ logger.error('Automatic installation error: ' + (error.message || error));
49
+ if (error.stdout || error.stderr) {
50
+ logger.error('Installer output (truncated):');
51
+ logger.error((error.stdout || '').toString().slice(0, 500));
52
+ logger.error((error.stderr || '').toString().slice(0, 500));
53
+ }
54
+
55
+ logger.error(`Try installing it manually: ${isWindows ? 'winget install Ollama.Ollama' : 'curl -fsSL https://ollama.com/install.sh | sh'}`);
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ async function ensureOllamaRunning(spinner) {
61
+ try {
62
+
63
+ await execAsync('ollama --version');
64
+ } catch (e) {
65
+ logger.info('Ollama binary not found. Attempting automatic install...');
66
+
67
+ await installOllamaEngine(spinner);
68
+ }
69
+
70
+
71
+ if (await waitForOllamaServer({ retries: 3, delayMs: 1000 })) {
72
+ return;
73
+ }
74
+
75
+
76
+ logger.info('Starting ollama serve in background...');
77
+ try {
78
+
79
+ const child = spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' });
80
+ child.unref();
81
+ } catch (spawnErr) {
82
+ logger.error('Failed to spawn ollama serve: ' + spawnErr.message);
83
+ throw spawnErr;
84
+ }
85
+
86
+
87
+ const ready = await waitForOllamaServer({ retries: 30, delayMs: 1000 });
88
+ if (!ready) {
89
+ throw new Error('Ollama server did not start in time. Check logs or run `ollama serve` manually.');
90
+ }
91
+ }
92
+
93
+ export async function askCommand(question) {
94
+ if (!question) {
95
+ logger.error('Ask me a question!');
96
+ return;
97
+ }
98
+
99
+ let spinner = ora(chalk.cyan('Initialization...')).start();
100
+
101
+ try {
102
+ await ensureOllamaRunning(spinner);
103
+
104
+
105
+ const modelName = process.env.OLLAMA_MODEL || 'llama3';
106
+
107
+ let hasModel = false;
108
+ try {
109
+ const models = await ollama.list();
110
+ hasModel = Array.isArray(models?.models) && models.models.some((m) => m.name?.startsWith(modelName));
111
+ } catch (err) {
112
+ logger.debug('Error listing models: ' + (err?.message || err));
113
+
114
+ }
115
+
116
+ if (!hasModel) {
117
+ spinner.text = chalk.magenta('First launch: Configuring neurons...');
118
+ spinner.start();
119
+
120
+ try {
121
+ await ollama.pull({ model: modelName });
122
+ spinner.succeed(chalk.green('AI brain in charge !'));
123
+ } catch (pullErr) {
124
+ spinner.fail(chalk.red('Failed to download model.'));
125
+ logger.error('ollama.pull error: ' + (pullErr?.message || pullErr));
126
+ throw pullErr;
127
+ }
128
+
129
+
130
+ spinner = ora(chalk.cyan('Thinking...')).start();
131
+ } else {
132
+ spinner.text = chalk.cyan('Thinking...');
133
+ }
134
+
135
+
136
+ let context = 'flowdev intelligent CLI tool';
137
+ try {
138
+ if (await fs.pathExists('package.json')) {
139
+ const pkg = await fs.readJson('package.json');
140
+ if (pkg?.name) context += ` CURRENT CONTEXT: The user is in the project folder "${pkg.name}".`;
141
+ }
142
+ } catch (e) {
143
+ logger.debug('Failed to read package.json: ' + (e?.message || e));
144
+ }
145
+
146
+ const response = await ollama.chat({
147
+ model: modelName,
148
+ messages: [
149
+ { role: 'system', content: context },
150
+ { role: 'user', content: question },
151
+ ],
152
+ stream: true,
153
+ });
154
+
155
+
156
+ spinner.stop();
157
+
158
+ for await (const part of response) {
159
+ const content = part?.message?.content;
160
+ if (typeof content === 'string') {
161
+ process.stdout.write(chalk.white(content));
162
+ }
163
+ }
164
+
165
+ process.stdout.write('\n');
166
+ } catch (error) {
167
+
168
+ try { spinner.stop(); } catch (_) {}
169
+
170
+ logger.error('askCommand error: ' + (error?.message || error));
171
+
172
+ if (error?.code === 'ENOENT') {
173
+ console.log(chalk.red('\nUnable to find or install Ollama automatically.'));
174
+ } else {
175
+ console.log(chalk.red(`\nError: ${error?.message || error}`));
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,78 @@
1
+ import ollama from 'ollama';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import { ensureEngineReady } from '../../utils/engine-check.js';
7
+ import { logger } from '../../utils/logger.js';
8
+
9
+ const AUDIT_EXTENSIONS = ['.js', '.ts', '.py', '.php', '.go', '.jsx', '.tsx', '.vue'];
10
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', 'venv'];
11
+
12
+ async function getFiles(dir) {
13
+ const subdirs = await fs.readdir(dir);
14
+ const files = await Promise.all(subdirs.map(async (subdir) => {
15
+ const res = path.resolve(dir, subdir);
16
+ if (IGNORE_DIRS.includes(subdir)) return [];
17
+ return (await fs.stat(res)).isDirectory() ? getFiles(res) : res;
18
+ }));
19
+ return files.flat().filter(f => AUDIT_EXTENSIONS.includes(path.extname(f)));
20
+ }
21
+
22
+ export async function auditCommand() {
23
+ const spinner = ora(chalk.cyan('Scanning project for audit...')).start();
24
+
25
+ try {
26
+ const rootDir = process.cwd();
27
+ const files = await getFiles(rootDir);
28
+
29
+ if (files.length === 0) {
30
+ spinner.fail(chalk.red('No source files found to audit.'));
31
+ return;
32
+ }
33
+
34
+ await ensureEngineReady(spinner, 'llama3');
35
+
36
+ spinner.text = chalk.magenta(`Auditing ${files.length} files...`);
37
+
38
+ let codeContext = "";
39
+ for (const file of files.slice(0, 10)) {
40
+ const content = await fs.readFile(file, 'utf-8');
41
+ codeContext += `\n--- File: ${path.basename(file)} ---\n${content}\n`;
42
+ }
43
+
44
+ const prompt = `
45
+ As an Expert Security Auditor,
46
+ Analyze the following code for:
47
+ 1. Potential Bugs or logic errors.
48
+ 2. Security vulnerabilities (exposed keys, unsafe inputs).
49
+ 3. Performance bottlenecks.
50
+ 4. Code quality and Best Practices.
51
+
52
+ Provide a concise report with clear headings and bullet points.
53
+ If the code is perfect, congratulate the developer.
54
+
55
+ Code to analyze:
56
+ ${codeContext}
57
+ `;
58
+
59
+ const response = await ollama.chat({
60
+ model: 'llama3',
61
+ messages: [{ role: 'user', content: prompt }],
62
+ stream: true,
63
+ });
64
+
65
+ spinner.stop();
66
+ console.log(chalk.bold.yellow('\n FLOWDEV AUDIT REPORT\n'));
67
+
68
+ for await (const part of response) {
69
+ process.stdout.write(chalk.white(part.message.content));
70
+ }
71
+
72
+ console.log(chalk.cyan('\n\nAudit complete. Always double-check AI suggestions before applying.'));
73
+
74
+ } catch (error) {
75
+ spinner.stop();
76
+ logger.error(`Audit failed: ${error.message}`);
77
+ }
78
+ }
@@ -0,0 +1,66 @@
1
+ import ollama from 'ollama';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import isBinaryPath from 'is-binary-path';
7
+ import { createRequire } from 'module';
8
+ import { ensureEngineReady } from '../../utils/engine-check.js';
9
+ import { logger } from '../../utils/logger.js';
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const pdf = require('pdf-parse');
13
+ const mammoth = require('mammoth');
14
+
15
+ async function extractText(filePath) {
16
+ const ext = path.extname(filePath).toLowerCase();
17
+ if (ext === '.docx') {
18
+ const buffer = await fs.readFile(filePath);
19
+ const result = await mammoth.extractRawText({ buffer });
20
+ return result.value;
21
+ }
22
+ if (ext === '.pdf') {
23
+ const buffer = await fs.readFile(filePath);
24
+ const data = await pdf(buffer);
25
+ return data.text;
26
+ }
27
+ if (isBinaryPath(filePath)) throw new Error('Binary files are not supported.');
28
+
29
+ return await fs.readFile(filePath, 'utf-8');
30
+ }
31
+
32
+ export async function explainCommand(filePath) {
33
+ if (!filePath) {
34
+ logger.error('Please specify a file path.');
35
+ return;
36
+ }
37
+
38
+ const spinner = ora(chalk.cyan('Initializing...')).start();
39
+
40
+ try {
41
+ await ensureEngineReady(spinner, 'llama3');
42
+
43
+ spinner.text = chalk.cyan(`Reading ${path.basename(filePath)}...`);
44
+ const content = await extractText(filePath);
45
+
46
+ spinner.text = chalk.magenta('Analyzing content...');
47
+ const response = await ollama.chat({
48
+ model: 'llama3',
49
+ messages: [{
50
+ role: 'user',
51
+ content: `Explain the following content clearly and concisely:\n\n${content.substring(0, 15000)}`
52
+ }],
53
+ stream: true,
54
+ });
55
+
56
+ spinner.stop();
57
+ console.log(chalk.red.bold(`ANALYSIS: ${path.basename(filePath).toUpperCase()} `));
58
+
59
+ for await (const part of response) {
60
+ process.stdout.write(chalk.white(part.message.content));
61
+ }
62
+ } catch (error) {
63
+ spinner.stop();
64
+ logger.error(`Explain error: ${error.message}`);
65
+ }
66
+ }
@@ -0,0 +1,73 @@
1
+ import ollama from 'ollama';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import { logger } from '../../utils/logger.js';
7
+ import { ensureEngineReady } from '../../utils/engine-check.js';
8
+
9
+ export async function testCommand(fileRelativePath) {
10
+ const spinner = ora(chalk.cyan(`Preparing test generation...`)).start();
11
+
12
+ try {
13
+ const filePath = path.resolve(process.cwd(), fileRelativePath);
14
+
15
+
16
+ if (!(await fs.pathExists(filePath))) {
17
+ spinner.fail(chalk.red(`File not found: ${fileRelativePath}`));
18
+ return;
19
+ }
20
+
21
+
22
+ await ensureEngineReady(spinner, 'llama3');
23
+
24
+ const content = await fs.readFile(filePath, 'utf-8');
25
+ const ext = path.extname(filePath);
26
+ const fileName = path.basename(filePath, ext);
27
+ const dir = path.dirname(filePath);
28
+
29
+ let framework = "Vitest";
30
+ let testFileName = `${fileName}.test${ext}`;
31
+
32
+ if (ext === '.py') {
33
+ framework = "Pytest";
34
+ testFileName = `test_${fileName}${ext}`;
35
+ }
36
+
37
+ spinner.text = chalk.magenta(`Writing ${framework} tests for ${fileName}...`);
38
+
39
+ const prompt = `
40
+ Analyze the following code and generate a complete test file using ${framework}.
41
+ Requirements:
42
+ - Include all necessary imports.
43
+ - Cover the main functions with "happy path" and "edge cases" (extremes, nulls, errors).
44
+ - Use descriptive test names.
45
+ - Return ONLY the code block starting with \`\`\` and ending with \`\`\`.
46
+
47
+ Code to test:
48
+ ${content}
49
+ `;
50
+
51
+ const response = await ollama.chat({
52
+ model: 'llama3',
53
+ messages: [{ role: 'user', content: prompt }],
54
+ });
55
+
56
+ const fullResponse = response.message.content;
57
+
58
+ const codeBlockRegex = /```[\w]*\n([\s\S]*?)```/;
59
+ const match = fullResponse.match(codeBlockRegex);
60
+ const testCode = match ? match[1] : fullResponse;
61
+
62
+ const outputPath = path.join(dir, testFileName);
63
+ await fs.writeFile(outputPath, testCode);
64
+
65
+ (chalk.green(`Tests generated successfully!`));
66
+ console.log(chalk.gray(`\n Location: `) + chalk.white(outputPath));
67
+ console.log(chalk.yellow(` Tip: Run your tests with '${ext === '.py' ? 'pytest' : 'npm test'}'`));
68
+
69
+ } catch (error) {
70
+ (chalk.red('Test generation failed.'));
71
+ logger.error(error.message);
72
+ }
73
+ }
File without changes
@@ -0,0 +1,90 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ import inquirer from 'inquirer';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ import { nodeDockerfile, pythonDockerfile, dockerCompose } from '../../templates/docker/templates.js';
7
+
8
+ export async function dockerizeCommand() {
9
+ const cwd = process.cwd();
10
+ const projectName = path.basename(cwd);
11
+
12
+ let detectedType = 'unknown';
13
+ if (await fs.pathExists(path.join(cwd, 'package.json'))) detectedType = 'node';
14
+ else if (await fs.pathExists(path.join(cwd, 'requirements.txt'))) detectedType = 'python';
15
+
16
+ console.log(chalk.bold(`\n Dockerizing ${chalk.cyan(projectName)}`));
17
+ if (detectedType !== 'unknown') {
18
+ console.log(chalk.gray(`Detected type: ${detectedType.toUpperCase()}`));
19
+ }
20
+
21
+ const answers = await inquirer.prompt([
22
+ {
23
+ type: 'list',
24
+ name: 'type',
25
+ message: 'What is your application type?',
26
+ choices: ['Node.js', 'Python', 'Other'],
27
+ default: detectedType === 'node' ? 'Node.js' : (detectedType === 'python' ? 'Python' : 'Other')
28
+ },
29
+ {
30
+ type: 'input',
31
+ name: 'port',
32
+ message: 'Which port is your app listening on?',
33
+ default: '3000',
34
+ validate: (input) => !isNaN(input) || 'Please enter a number.'
35
+ },
36
+ {
37
+ type: 'input',
38
+ name: 'version',
39
+ message: 'Which language version (Docker tag)?',
40
+ default: (answers) => answers.type === 'Node.js' ? '18' : '3.11'
41
+ },
42
+
43
+ {
44
+ type: 'input',
45
+ name: 'startCommand',
46
+ message: 'Start command (e.g., npm run dev):',
47
+ default: 'npm start',
48
+ when: (answers) => answers.type === 'Node.js'
49
+ },
50
+
51
+ {
52
+ type: 'input',
53
+ name: 'startFile',
54
+ message: 'Main entry file (e.g., app.py, main.py):',
55
+ default: 'app.py',
56
+ when: (answers) => answers.type === 'Python'
57
+ }
58
+ ]);
59
+
60
+ const spinner = ora('Generating containers...').start();
61
+
62
+ try {
63
+ let dockerfileContent = '';
64
+
65
+
66
+ if (answers.type === 'Node.js') {
67
+ dockerfileContent = nodeDockerfile(answers.version, answers.port, answers.startCommand);
68
+ } else if (answers.type === 'Python') {
69
+ dockerfileContent = pythonDockerfile(answers.version, answers.port, answers.startFile);
70
+ } else {
71
+ dockerfileContent = '# Generic template\nFROM alpine:latest\nCMD ["echo", "Hello World"]';
72
+ }
73
+
74
+ await fs.writeFile(path.join(cwd, 'Dockerfile'), dockerfileContent.trim());
75
+ const composeContent = dockerCompose(projectName.toLowerCase().replace(/[^a-z0-9]/g, '-'), answers.port);
76
+ await fs.writeFile(path.join(cwd, 'docker-compose.yml'), composeContent.trim());
77
+
78
+ const ignoreContent = 'node_modules\n.git\n.env\ndist\nbuild\n__pycache__\n*.log';
79
+ await fs.writeFile(path.join(cwd, '.dockerignore'), ignoreContent);
80
+
81
+ spinner.succeed(chalk.green('Dockerfiles generated successfully!'));
82
+
83
+ console.log('\nTo start your project:');
84
+ console.log(chalk.blue(` docker-compose up --build`));
85
+
86
+ } catch (error) {
87
+ spinner.fail(chalk.red('Error during generation.'));
88
+ console.error(error);
89
+ }
90
+ }