@flowdevcli/flowdev 1.0.7 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowdevcli/flowdev",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "AI-powered CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
package/src/core/cli.js CHANGED
@@ -34,7 +34,7 @@ import { generateCommand } from '../commands/scaffold/generate.js';
34
34
  import { auditCommand } from '../commands/ai/audit.js';
35
35
  import { testCommand } from '../commands/ai/test.js';
36
36
  import { updateCommand } from '../commands/system/update.js';
37
- import { configCommand } from './commands/config.js'
37
+ import { configCommand } from '../commands/config.js'
38
38
 
39
39
 
40
40
  const require = createRequire(import.meta.url);
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview FlowDev - Intelligent CLI tool
3
+ * @module flowdev
4
+ * @version 1.0.5
5
+ *
6
+ * @license MIT
7
+ * Copyright (c) 2026 FlowDev Technologies.
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in all
17
+ * copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ */
23
+
24
+ import Conf from 'conf';
25
+ const config = new Conf({
26
+ projectName: 'flowdev-cli',
27
+ projectSuffix: ''
28
+ });
29
+
30
+
31
+ const CONFIG_KEYS = {
32
+ DEEPSEEK_KEY: 'api_keys.deepseek',
33
+ DEFAULT_PROVIDER: 'preferences.default_provider'
34
+ };
35
+
36
+ /**
37
+ *
38
+ * @returns {string|undefined}
39
+ */
40
+ export function getDeepSeekKey() {
41
+ return config.get(CONFIG_KEYS.DEEPSEEK_KEY);
42
+ }
43
+
44
+ /**
45
+ *
46
+ * @param {string} key
47
+ */
48
+ export function setDeepSeekKey(key) {
49
+ if (!key) return;
50
+ config.set(CONFIG_KEYS.DEEPSEEK_KEY, key);
51
+ }
52
+
53
+ export function clearDeepSeekKey() {
54
+ config.delete(CONFIG_KEYS.DEEPSEEK_KEY);
55
+ }
56
+
57
+ export function getAllConfig() {
58
+ return config.store;
59
+ }
@@ -3,8 +3,8 @@ import chalk from 'chalk';
3
3
  import inquirer from 'inquirer';
4
4
  import { exec, spawn } from 'node:child_process';
5
5
  import { promisify } from 'util';
6
- import { logger } from './logger.js';
7
- import { getDeepSeekKey } from './config-manager.js';
6
+ import { logger } from '../utils/logger.js';
7
+ import { getDeepSeekKey } from '../utils/config-manager.js';
8
8
 
9
9
  const execAsync = promisify(exec);
10
10
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -24,24 +24,40 @@ async function waitForOllamaServer({ retries = 15, delayMs = 1000 } = {}) {
24
24
 
25
25
  async function installOllamaEngine(spinner) {
26
26
  const isWindows = process.platform === 'win32';
27
+
28
+ if (spinner) spinner.stop();
29
+
30
+ console.log(chalk.yellow('\n Ollama engine is missing.'));
31
+ console.log(chalk.dim('FlowDev requires Ollama to run local models without API keys.'));
32
+
33
+
34
+ const { confirm } = await inquirer.prompt([{
35
+ type: 'confirm',
36
+ name: 'confirm',
37
+ message: 'Do you want to install Ollama automatically now?',
38
+ default: true
39
+ }]);
40
+ if (!confirm) throw new Error("Ollama installation aborted by user.");
41
+
42
+
43
+ if (spinner) spinner.start(chalk.yellow("Installing local neural components... (Please wait)"));
44
+
27
45
  const installCmd = isWindows
28
46
  ? 'winget install Ollama.Ollama --silent --accept-source-agreements'
29
47
  : 'curl -fsSL https://ollama.com/install.sh | sh';
30
48
 
31
- spinner.text = chalk.yellow("Installing local neural components... (Please wait)");
32
-
33
49
  try {
34
50
  await execAsync(installCmd, { maxBuffer: 10 * 1024 * 1024 });
35
- spinner.succeed(chalk.green('Local engine installed.'));
51
+ if (spinner) spinner.succeed(chalk.green('Local engine installed.'));
36
52
  await sleep(1500);
37
53
  } catch (error) {
38
- spinner.fail(chalk.red("Automatic installation failed."));
39
- logger.error(`Please install Ollama manually.`);
54
+ if (spinner) spinner.fail(chalk.red("Automatic installation failed."));
55
+ logger.error(`Please install Ollama manually from https://ollama.com`);
40
56
  throw error;
41
57
  }
42
58
  }
43
59
 
44
- async function ensureOllamaReady(spinner, modelName) {
60
+ export async function ensureOllamaReady(spinner, modelName) {
45
61
  try {
46
62
  await execAsync('ollama --version');
47
63
  } catch (e) {
@@ -49,7 +65,7 @@ async function ensureOllamaReady(spinner, modelName) {
49
65
  }
50
66
 
51
67
  if (!(await waitForOllamaServer({ retries: 3, delayMs: 1000 }))) {
52
- spinner.text = chalk.blue('Starting local inference service...');
68
+ if (spinner) spinner.text = chalk.blue('Starting local inference service...');
53
69
  const child = spawn('ollama', ['serve'], { detached: true, stdio: 'ignore' });
54
70
  child.unref();
55
71
 
@@ -64,17 +80,22 @@ async function ensureOllamaReady(spinner, modelName) {
64
80
  } catch (err) {}
65
81
 
66
82
  if (!hasModel) {
67
- spinner.text = chalk.magenta(`Acquiring the model ${modelName} (size: ~4GB)...`);
83
+ if (spinner) spinner.text = chalk.magenta(`Acquiring the model ${modelName} (size: ~4GB)... This happens only once.`);
68
84
  await ollama.pull({ model: modelName });
69
- spinner.succeed(chalk.green('Local engine ready.'));
70
- spinner.start();
85
+ if (spinner) spinner.succeed(chalk.green('Local engine ready.'));
86
+ if (spinner) spinner.start();
71
87
  }
72
88
  }
73
89
 
74
90
 
91
+
75
92
  async function* streamDeepSeek(messages) {
76
93
  const apiKey = getDeepSeekKey();
77
94
 
95
+ if (!apiKey) {
96
+ throw new Error("API Key missing. Please run 'flowdev config' to set it up.");
97
+ }
98
+
78
99
  const response = await fetch('https://api.deepseek.com/chat/completions', {
79
100
  method: 'POST',
80
101
  headers: {
@@ -89,8 +110,13 @@ async function* streamDeepSeek(messages) {
89
110
  });
90
111
 
91
112
  if (!response.ok) {
92
- const err = await response.text();
93
- throw new Error(`DeepSeek API Error: ${response.status} - ${err}`);
113
+ const errText = await response.text();
114
+ if (response.status === 401) {
115
+ throw new Error(`Invalid API Key. Please run 'flowdev config' to update it.`);
116
+ } else if (response.status === 402) {
117
+ throw new Error(`Insufficient Balance on DeepSeek account.`);
118
+ }
119
+ throw new Error(`DeepSeek API Error: ${response.status} - ${errText}`);
94
120
  }
95
121
 
96
122
  const reader = response.body.getReader();
@@ -100,30 +126,39 @@ async function* streamDeepSeek(messages) {
100
126
  while (true) {
101
127
  const { done, value } = await reader.read();
102
128
  if (done) break;
129
+
103
130
  buffer += decoder.decode(value, { stream: true });
104
- const lines = buffer.split("\n");
105
- buffer = lines.pop();
106
-
107
- for (const line of lines) {
108
- const trimmed = line.trim();
109
- if (!trimmed || trimmed === "data: [DONE]") continue;
110
- if (trimmed.startsWith("data: ")) {
111
- try {
112
- const json = JSON.parse(trimmed.replace("data: ", ""));
113
- const content = json.choices[0]?.delta?.content;
114
- if (content) yield { message: { content: content } };
115
- } catch (e) {}
116
- }
131
+
132
+ let boundary = buffer.indexOf('\n');
133
+ while (boundary !== -1) {
134
+ const line = buffer.slice(0, boundary).trim();
135
+ buffer = buffer.slice(boundary + 1);
136
+
137
+ if (line.startsWith("data: ")) {
138
+ const jsonStr = line.slice(6);
139
+ if (jsonStr === "[DONE]") continue;
140
+
141
+ try {
142
+ const json = JSON.parse(jsonStr);
143
+ const content = json.choices[0]?.delta?.content;
144
+ if (content) yield { message: { content: content } };
145
+ } catch (e) {
146
+
147
+ }
148
+ }
149
+ boundary = buffer.indexOf('\n');
117
150
  }
118
151
  }
119
152
  }
120
153
 
121
154
 
155
+
122
156
  export async function getAIResponse(messages, spinner, forceModel = null) {
123
157
  if (spinner) spinner.stop();
124
158
 
125
159
  let provider = forceModel;
126
160
 
161
+
127
162
  if (!provider) {
128
163
  const hasKey = !!getDeepSeekKey();
129
164
 
@@ -134,12 +169,12 @@ export async function getAIResponse(messages, spinner, forceModel = null) {
134
169
  message: 'Select AI Model:',
135
170
  choices: [
136
171
  {
137
- name: ' Llama 3 (Local - Private, Free, requires download)',
172
+ name: 'Llama 3 (Local - Private, Free, requires RAM)',
138
173
  value: 'llama3',
139
174
  short: 'Llama 3 (Local)'
140
175
  },
141
176
  {
142
- name: `DeepSeek V3 (Cloud - Fast)`,
177
+ name: ` DeepSeek V3 (Cloud - Fast, requires Key)`,
143
178
  value: 'deepseek',
144
179
  disabled: !hasKey ? 'run "flowdev config" first' : false,
145
180
  short: 'DeepSeek (Cloud)'
@@ -153,10 +188,10 @@ export async function getAIResponse(messages, spinner, forceModel = null) {
153
188
  if (spinner) spinner.start();
154
189
 
155
190
  if (provider === 'llama3') {
156
- if (spinner) spinner.text = chalk.cyan('Local environment check...');
191
+ if (spinner) spinner.text = chalk.cyan('Checking local neural engine...');
157
192
  await ensureOllamaReady(spinner, 'llama3');
158
193
 
159
- if (spinner) spinner.text = chalk.magenta('Thinking...');
194
+ if (spinner) spinner.text = chalk.magenta('Thinking (Local)...');
160
195
  return await ollama.chat({
161
196
  model: 'llama3',
162
197
  messages: messages,
@@ -164,16 +199,19 @@ export async function getAIResponse(messages, spinner, forceModel = null) {
164
199
  });
165
200
  }
166
201
  else if (provider === 'deepseek') {
167
- if (spinner) spinner.text = chalk.cyan('Connecting to the DeepSeek remote server...');
202
+ if (spinner) spinner.text = chalk.cyan('Handshaking with DeepSeek server...');
203
+
204
+
168
205
  try {
169
- await fetch('https://www.google.com', { method: 'HEAD' });
206
+ await fetch('https://www.google.com', { method: 'HEAD', signal: AbortSignal.timeout(3000) });
170
207
  } catch (e) {
171
- throw new Error("An active internet connection is required to use DeepSeek.");
208
+ throw new Error("No internet connection detected for Cloud AI.");
172
209
  }
173
210
 
174
- if (spinner) spinner.text = chalk.magenta('Analyzing...');
211
+ if (spinner) spinner.text = chalk.magenta('Thinking (Cloud)...');
175
212
  return streamDeepSeek(messages);
176
213
  }
177
214
  }
178
215
 
216
+
179
217
  export { ensureOllamaReady as ensureEngineReady };