samarthya-bot 2.0.0 → 2.1.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/backend/bin/samarthya.js +619 -315
- package/backend/package.json +1 -1
- package/package.json +1 -1
package/backend/bin/samarthya.js
CHANGED
|
@@ -6,171 +6,407 @@ const readline = require('readline');
|
|
|
6
6
|
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
const command = args[0];
|
|
9
|
-
|
|
10
9
|
const backendDir = path.join(__dirname, '..');
|
|
11
10
|
|
|
12
|
-
//
|
|
11
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
12
|
+
// ║ 🎨 TERMINAL COLORS ║
|
|
13
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
14
|
+
const c = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
bold: '\x1b[1m',
|
|
17
|
+
dim: '\x1b[2m',
|
|
18
|
+
italic: '\x1b[3m',
|
|
19
|
+
under: '\x1b[4m',
|
|
20
|
+
// Indian Flag Colors
|
|
21
|
+
saffron: '\x1b[38;2;255;153;51m',
|
|
22
|
+
white: '\x1b[38;2;255;255;255m',
|
|
23
|
+
green: '\x1b[38;2;19;136;8m',
|
|
24
|
+
navy: '\x1b[38;2;0;0;128m',
|
|
25
|
+
// UI Colors
|
|
26
|
+
red: '\x1b[38;2;239;68;68m',
|
|
27
|
+
cyan: '\x1b[38;2;34;211;238m',
|
|
28
|
+
yellow: '\x1b[38;2;250;204;21m',
|
|
29
|
+
purple: '\x1b[38;2;168;85;247m',
|
|
30
|
+
pink: '\x1b[38;2;244;114;182m',
|
|
31
|
+
lime: '\x1b[38;2;74;222;128m',
|
|
32
|
+
gray: '\x1b[38;2;120;120;120m',
|
|
33
|
+
dimWhite: '\x1b[38;2;180;180,180m',
|
|
34
|
+
// Backgrounds
|
|
35
|
+
bgSaffron: '\x1b[48;2;255;153;51m',
|
|
36
|
+
bgGreen: '\x1b[48;2;19;136;8m',
|
|
37
|
+
bgNavy: '\x1b[48;2;0;0;128m',
|
|
38
|
+
bgDark: '\x1b[48;2;15;15;25m',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
42
|
+
// ║ 🏳️ ASCII ART BANNER ║
|
|
43
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
44
|
+
const BANNER = `
|
|
45
|
+
${c.saffron}${c.bold} ███████╗ █████╗ ███╗ ███╗ █████╗ ██████╗ ████████╗██╗ ██╗██╗ ██╗ █████╗
|
|
46
|
+
██╔════╝██╔══██╗████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝██║ ██║╚██╗ ██╔╝██╔══██╗
|
|
47
|
+
███████╗███████║██╔████╔██║███████║██████╔╝ ██║ ███████║ ╚████╔╝ ███████║
|
|
48
|
+
${c.white} ╚════██║██╔══██║██║╚██╔╝██║██╔══██║██╔══██╗ ██║ ██╔══██║ ╚██╔╝ ██╔══██║
|
|
49
|
+
███████║██║ ██║██║ ╚═╝ ██║██║ ██║██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║
|
|
50
|
+
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
51
|
+
${c.green} ██████╗ ██████╗ ████████╗
|
|
52
|
+
██╔══██╗██╔═══██╗╚══██╔══╝
|
|
53
|
+
██████╔╝██║ ██║ ██║
|
|
54
|
+
${c.green} ██╔══██╗██║ ██║ ██║
|
|
55
|
+
██████╔╝╚██████╔╝ ██║
|
|
56
|
+
╚═════╝ ╚═════╝ ╚═╝ ${c.reset}
|
|
57
|
+
|
|
58
|
+
${c.gray} ─────────────────────────────────────────────────────────────────────────────${c.reset}
|
|
59
|
+
${c.dim} 🇮🇳 Privacy-First Local Agentic OS • Made in India 🇮🇳${c.reset}
|
|
60
|
+
${c.gray} ─────────────────────────────────────────────────────────────────────────────${c.reset}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const MINI_BANNER = `
|
|
64
|
+
${c.saffron}${c.bold} ╔═══╗╔═══╗╔╗╔╗╔═══╗╔═══╗╔════╗╔╗ ╔╗╔╗ ╔╗╔═══╗ ╔══╗╔═══╗╔════╗${c.reset}
|
|
65
|
+
${c.saffron} ║╔═╗║║╔═╗║║║║║║╔═╗║║╔═╗║║╔╗╔╗║║║ ║║╚╝ ╚╝║╔═╗║ ║╔╗║║╔═╗║║╔╗╔╗║${c.reset}
|
|
66
|
+
${c.white}${c.bold} ║╚══╗║║ ║║║╚╝║║║ ║║║╚═╝║╚╝║║╚╝║╚═╝║ ║║ ║║ ║╚╝╚╗║║ ║║╚╝║║╚╝${c.reset}
|
|
67
|
+
${c.white} ╚══╗║║╚═╝║║╔╗║║╚═╝║║╔╗╔╝ ║║ ║╔═╗║ ║╚═╝║ ║╔═╗║║║ ║║ ║║ ${c.reset}
|
|
68
|
+
${c.green}${c.bold} ║╚═╝║║╔═╗║║║║║║╔═╗║║║║║ ║║ ║║ ║║ ║╔═╗║ ║╚═╝║║╚═╝║ ║║ ${c.reset}
|
|
69
|
+
${c.green} ╚═══╝╚╝ ╚╝╚╝╚╝╚╝ ╚╝╚╝╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚╝ ╚═══╝╚═══╝ ╚╝ ${c.reset}
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
73
|
+
// ║ 🛠 HELPER UTILITIES ║
|
|
74
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
75
|
+
|
|
76
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
77
|
+
|
|
78
|
+
// Animated spinner
|
|
79
|
+
class Spinner {
|
|
80
|
+
constructor(text) {
|
|
81
|
+
this.text = text;
|
|
82
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
83
|
+
this.i = 0;
|
|
84
|
+
this.timer = null;
|
|
85
|
+
}
|
|
86
|
+
start() {
|
|
87
|
+
this.timer = setInterval(() => {
|
|
88
|
+
process.stdout.write(`\r${c.cyan}${this.frames[this.i++ % this.frames.length]}${c.reset} ${this.text}`);
|
|
89
|
+
}, 80);
|
|
90
|
+
}
|
|
91
|
+
stop(successText) {
|
|
92
|
+
clearInterval(this.timer);
|
|
93
|
+
process.stdout.write(`\r${c.lime}✔${c.reset} ${successText || this.text}\n`);
|
|
94
|
+
}
|
|
95
|
+
fail(failText) {
|
|
96
|
+
clearInterval(this.timer);
|
|
97
|
+
process.stdout.write(`\r${c.red}✖${c.reset} ${failText || this.text}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Progress bar
|
|
102
|
+
const progressBar = (current, total, width = 30) => {
|
|
103
|
+
const filled = Math.round((current / total) * width);
|
|
104
|
+
const empty = width - filled;
|
|
105
|
+
const bar = `${c.saffron}${'█'.repeat(filled)}${c.gray}${'░'.repeat(empty)}${c.reset}`;
|
|
106
|
+
return `${bar} ${c.dim}${current}/${total}${c.reset}`;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Step header
|
|
110
|
+
const stepHeader = (num, total, text) => {
|
|
111
|
+
console.log(`\n${c.saffron}${c.bold} [${num}/${total}]${c.reset} ${c.white}${c.bold}${text}${c.reset}`);
|
|
112
|
+
console.log(`${c.gray} ${'─'.repeat(60)}${c.reset}`);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Labeled print
|
|
116
|
+
const info = (text) => console.log(` ${c.cyan}ℹ${c.reset} ${text}`);
|
|
117
|
+
const success = (text) => console.log(` ${c.lime}✔${c.reset} ${text}`);
|
|
118
|
+
const warn = (text) => console.log(` ${c.yellow}⚠${c.reset} ${text}`);
|
|
119
|
+
const error = (text) => console.log(` ${c.red}✖${c.reset} ${text}`);
|
|
120
|
+
|
|
121
|
+
// Create readline interface
|
|
122
|
+
const createRL = () => readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
123
|
+
const ask = (rl, query) => new Promise(resolve => rl.question(query, resolve));
|
|
124
|
+
|
|
125
|
+
// Check if server is running
|
|
13
126
|
const isServerRunning = () => {
|
|
14
127
|
try {
|
|
15
128
|
if (process.platform === 'win32') {
|
|
16
129
|
const output = execSync('netstat -ano | findstr :5000', { encoding: 'utf-8' });
|
|
17
130
|
return output.includes('LISTENING');
|
|
18
131
|
} else {
|
|
19
|
-
// macOS and Linux
|
|
20
|
-
// Using lsof as it's more standard across unix than fuser
|
|
21
132
|
execSync('lsof -i:5000 -t 2>/dev/null');
|
|
22
133
|
return true;
|
|
23
134
|
}
|
|
24
|
-
} catch {
|
|
25
|
-
|
|
135
|
+
} catch { return false; }
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Read existing .env
|
|
139
|
+
const readEnv = () => {
|
|
140
|
+
const envPath = path.join(backendDir, '.env');
|
|
141
|
+
const envVars = {};
|
|
142
|
+
if (fs.existsSync(envPath)) {
|
|
143
|
+
fs.readFileSync(envPath, 'utf8').split('\n').forEach(line => {
|
|
144
|
+
const eqIndex = line.indexOf('=');
|
|
145
|
+
if (eqIndex > 0) {
|
|
146
|
+
const k = line.substring(0, eqIndex).trim();
|
|
147
|
+
const v = line.substring(eqIndex + 1).trim();
|
|
148
|
+
if (k) envVars[k] = v;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
26
151
|
}
|
|
27
|
-
|
|
28
|
-
|
|
152
|
+
return envVars;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Write .env
|
|
156
|
+
const writeEnv = (envVars) => {
|
|
157
|
+
const envPath = path.join(backendDir, '.env');
|
|
158
|
+
const content = Object.keys(envVars).map(k => `${k}=${envVars[k]}`).join('\n');
|
|
159
|
+
fs.writeFileSync(envPath, content);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Mask API key for display
|
|
163
|
+
const maskKey = (key) => {
|
|
164
|
+
if (!key || key.length < 8) return '****';
|
|
165
|
+
return key.substring(0, 4) + '•'.repeat(Math.min(key.length - 8, 20)) + key.substring(key.length - 4);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
169
|
+
// ║ PROVIDER & MODEL DEFINITIONS ║
|
|
170
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
171
|
+
|
|
172
|
+
const PROVIDERS = [
|
|
173
|
+
{ id: 'gemini', name: 'Google Gemini', tag: 'Free Tier', color: c.cyan, envKey: 'GEMINI_API_KEY' },
|
|
174
|
+
{ id: 'groq', name: 'Groq', tag: 'Fastest', color: c.yellow, envKey: 'GROQ_API_KEY' },
|
|
175
|
+
{ id: 'anthropic', name: 'Anthropic Claude', tag: 'Smartest', color: c.purple, envKey: 'ANTHROPIC_API_KEY' },
|
|
176
|
+
{ id: 'openai', name: 'OpenAI', tag: 'GPT-5', color: c.lime, envKey: 'OPENAI_API_KEY' },
|
|
177
|
+
{ id: 'deepseek', name: 'DeepSeek', tag: 'Budget', color: c.cyan, envKey: 'DEEPSEEK_API_KEY' },
|
|
178
|
+
{ id: 'qwen', name: 'Qwen', tag: 'Alibaba', color: c.pink, envKey: 'QWEN_API_KEY' },
|
|
179
|
+
{ id: 'openrouter', name: 'OpenRouter', tag: '100+ Models', color: c.saffron, envKey: 'OPENROUTER_API_KEY' },
|
|
180
|
+
{ id: 'ollama', name: 'Ollama', tag: 'Offline', color: c.green, envKey: null },
|
|
181
|
+
{ id: 'mistral', name: 'Mistral AI', tag: 'EU Privacy', color: c.white, envKey: 'MISTRAL_API_KEY' },
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const MODELS = {
|
|
185
|
+
gemini: [
|
|
186
|
+
{ id: 'gemini-2.5-flash', desc: 'Fastest reasoning, great pricing' },
|
|
187
|
+
{ id: 'gemini-2.5-pro', desc: 'Advanced multi-step reasoning' },
|
|
188
|
+
{ id: 'gemini-2.5-flash-lite', desc: 'Budget friendly' },
|
|
189
|
+
{ id: 'gemini-2.0-flash', desc: 'Older 1M context' },
|
|
190
|
+
],
|
|
191
|
+
groq: [
|
|
192
|
+
{ id: 'llama-3.3-70b-versatile', desc: 'Best overall Llama' },
|
|
193
|
+
{ id: 'llama-3.1-8b-instant', desc: 'Extreme fast' },
|
|
194
|
+
{ id: 'qwen/qwen3-32b', desc: 'Powerful 32b reasoning' },
|
|
195
|
+
],
|
|
196
|
+
anthropic: [
|
|
197
|
+
{ id: 'claude-3-5-sonnet-latest', desc: 'Best balance' },
|
|
198
|
+
{ id: 'claude-3-opus-latest', desc: 'Most capable' },
|
|
199
|
+
],
|
|
200
|
+
openai: [
|
|
201
|
+
{ id: 'gpt-5.2', desc: 'Best for coding & agentic' },
|
|
202
|
+
{ id: 'gpt-5-mini', desc: 'Faster, cost-efficient' },
|
|
203
|
+
{ id: 'gpt-4o', desc: 'Fast, intelligent' },
|
|
204
|
+
{ id: 'o3-mini', desc: 'Small reasoning' },
|
|
205
|
+
],
|
|
206
|
+
deepseek: [
|
|
207
|
+
{ id: 'deepseek-chat', desc: 'General purpose chat' },
|
|
208
|
+
{ id: 'deepseek-coder', desc: 'Code specialized' },
|
|
209
|
+
],
|
|
210
|
+
qwen: [
|
|
211
|
+
{ id: 'qwen-max', desc: 'Most capable Qwen' },
|
|
212
|
+
{ id: 'qwen-turbo', desc: 'Fast and efficient' },
|
|
213
|
+
],
|
|
214
|
+
openrouter: [
|
|
215
|
+
{ id: 'auto', desc: 'Auto-select best model' },
|
|
216
|
+
{ id: 'anthropic/claude-3.5-sonnet', desc: 'Claude via OpenRouter' },
|
|
217
|
+
{ id: 'google/gemini-2.5-flash', desc: 'Gemini via OpenRouter' },
|
|
218
|
+
],
|
|
219
|
+
ollama: [
|
|
220
|
+
{ id: 'dolphin3:8b-llama3.1-q4_K_M', desc: 'Default local' },
|
|
221
|
+
{ id: 'llama3:8b', desc: 'Meta Llama 3' },
|
|
222
|
+
{ id: 'mistral', desc: 'Mistral local' },
|
|
223
|
+
],
|
|
224
|
+
mistral: [
|
|
225
|
+
{ id: 'mistral-large-3', desc: 'General-purpose multimodal' },
|
|
226
|
+
{ id: 'ministral-3-8b', desc: 'Powerful & efficient' },
|
|
227
|
+
{ id: 'devstral-2', desc: 'Best for code & agents' },
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
232
|
+
// ║ 🚀 CLI COMMANDS ║
|
|
233
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
234
|
+
|
|
235
|
+
// ─── Show help ───
|
|
29
236
|
if (!command) {
|
|
30
|
-
console.log(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`);
|
|
237
|
+
console.log(BANNER);
|
|
238
|
+
console.log(`${c.bold} COMMANDS:${c.reset}`);
|
|
239
|
+
console.log(` ${c.saffron}samarthya onboard${c.reset} ${c.dim}Setup + Start everything (single command!)${c.reset}`);
|
|
240
|
+
console.log(` ${c.saffron}samarthya gateway${c.reset} ${c.dim}Start the local server${c.reset}`);
|
|
241
|
+
console.log(` ${c.saffron}samarthya model${c.reset} ${c.dim}Change AI provider/model${c.reset}`);
|
|
242
|
+
console.log(` ${c.saffron}samarthya telegram${c.reset} ${c.dim}Configure Telegram bot${c.reset}`);
|
|
243
|
+
console.log(` ${c.saffron}samarthya discord${c.reset} ${c.dim}Configure Discord bot${c.reset}`);
|
|
244
|
+
console.log(` ${c.saffron}samarthya config${c.reset} ${c.dim}View current configuration${c.reset}`);
|
|
245
|
+
console.log(` ${c.saffron}samarthya tunnel${c.reset} ${c.dim}Expose to internet & setup webhooks${c.reset}`);
|
|
246
|
+
console.log(` ${c.saffron}samarthya status${c.reset} ${c.dim}Check if agent is running${c.reset}`);
|
|
247
|
+
console.log(` ${c.saffron}samarthya stop${c.reset} ${c.dim}Stop the gateway${c.reset}`);
|
|
248
|
+
console.log(` ${c.saffron}samarthya restart${c.reset} ${c.dim}Restart the gateway${c.reset}\n`);
|
|
41
249
|
process.exit(0);
|
|
42
250
|
}
|
|
43
251
|
|
|
44
252
|
switch (command) {
|
|
45
|
-
case 'onboard':
|
|
46
|
-
console.log('🚀 Running SamarthyaBot Setup Wizard...');
|
|
47
253
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
254
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
255
|
+
// ║ 📦 ONBOARD WIZARD ║
|
|
256
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
257
|
+
case 'onboard': {
|
|
258
|
+
console.log(BANNER);
|
|
259
|
+
console.log(` ${c.saffron}${c.bold}🚀 Setup Wizard${c.reset}${c.dim} — Let's get your AI agent ready!\n${c.reset}`);
|
|
52
260
|
|
|
53
|
-
const
|
|
261
|
+
const rl = createRL();
|
|
262
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
54
263
|
|
|
55
264
|
(async () => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
console.log(
|
|
66
|
-
|
|
67
|
-
let providerRaw = await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
265
|
+
const TOTAL_STEPS = 5;
|
|
266
|
+
|
|
267
|
+
// ── Step 1: Select AI Provider ──
|
|
268
|
+
stepHeader(1, TOTAL_STEPS, 'Select AI Provider');
|
|
269
|
+
console.log();
|
|
270
|
+
PROVIDERS.forEach((p, i) => {
|
|
271
|
+
const num = `${i + 1}`.padStart(2);
|
|
272
|
+
console.log(` ${c.dim}${num})${c.reset} ${p.color}${c.bold}${p.name}${c.reset} ${c.gray}(${p.tag})${c.reset}`);
|
|
273
|
+
});
|
|
274
|
+
console.log();
|
|
275
|
+
|
|
276
|
+
let providerRaw = await q(` Enter choice [${c.saffron}1-9${c.reset}, default ${c.saffron}1${c.reset}]: `);
|
|
277
|
+
const provIdx = parseInt(providerRaw.trim()) - 1;
|
|
278
|
+
const provider = PROVIDERS[provIdx >= 0 && provIdx < PROVIDERS.length ? provIdx : 0];
|
|
279
|
+
const activeProvider = provider.id;
|
|
280
|
+
const useOllama = activeProvider === 'ollama' ? 'true' : 'false';
|
|
281
|
+
|
|
282
|
+
console.log(`\n ${c.lime}✔${c.reset} Selected: ${provider.color}${c.bold}${provider.name}${c.reset}\n`);
|
|
283
|
+
|
|
284
|
+
// ── Step 2: API Key for selected provider ──
|
|
285
|
+
stepHeader(2, TOTAL_STEPS, 'API Key Configuration');
|
|
286
|
+
let apiKey = '';
|
|
287
|
+
if (provider.envKey) {
|
|
288
|
+
apiKey = await q(` 🔑 Enter ${provider.name} API Key: `);
|
|
289
|
+
if (apiKey.trim()) {
|
|
290
|
+
success(`Key saved: ${c.dim}${maskKey(apiKey.trim())}${c.reset}`);
|
|
291
|
+
} else {
|
|
292
|
+
info(`Skipped — will use existing key if available`);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
info(`${provider.name} runs locally — no API key needed`);
|
|
87
296
|
}
|
|
297
|
+
console.log();
|
|
298
|
+
|
|
299
|
+
// ── Step 3: Telegram configuration ──
|
|
300
|
+
stepHeader(3, TOTAL_STEPS, 'Channel Integrations');
|
|
301
|
+
let telegramToken = '';
|
|
302
|
+
let discordToken = '';
|
|
303
|
+
|
|
304
|
+
const enableTg = await q(` 📱 Enable Telegram Bot? (${c.lime}y${c.reset}/${c.red}n${c.reset}): `);
|
|
305
|
+
if (enableTg.trim().toLowerCase() === 'y' || enableTg.trim().toLowerCase() === 'yes') {
|
|
306
|
+
telegramToken = await q(` 🤖 Enter Telegram Bot Token: `);
|
|
307
|
+
if (telegramToken.trim()) {
|
|
308
|
+
success(`Telegram configured: ${c.dim}${maskKey(telegramToken.trim())}${c.reset}`);
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
info(`Telegram skipped`);
|
|
312
|
+
}
|
|
313
|
+
console.log();
|
|
88
314
|
|
|
89
|
-
|
|
315
|
+
const enableDc = await q(` 🟣 Enable Discord Bot? (${c.lime}y${c.reset}/${c.red}n${c.reset}): `);
|
|
316
|
+
if (enableDc.trim().toLowerCase() === 'y' || enableDc.trim().toLowerCase() === 'yes') {
|
|
317
|
+
discordToken = await q(` 🟣 Enter Discord Bot Token: `);
|
|
318
|
+
if (discordToken.trim()) {
|
|
319
|
+
success(`Discord configured: ${c.dim}${maskKey(discordToken.trim())}${c.reset}`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
info(`Discord skipped`);
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
|
|
326
|
+
// ── Step 4: Save configuration ──
|
|
327
|
+
stepHeader(4, TOTAL_STEPS, 'Saving Configuration');
|
|
90
328
|
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const openAiKey = await question('🔑 Enter OpenAI API Key (or press Enter to skip): ');
|
|
95
|
-
const deepseekKey = await question('🔑 Enter DeepSeek API Key (or press Enter to skip): ');
|
|
96
|
-
const qwenKey = await question('🔑 Enter Qwen API Key (or press Enter to skip): ');
|
|
97
|
-
const openrouterKey = await question('🔑 Enter OpenRouter API Key (or press Enter to skip): ');
|
|
98
|
-
const telegramToken = await question('🤖 Enter Telegram Bot Token (or press Enter to skip): ');
|
|
99
|
-
const discordToken = await question('🟣 Enter Discord Bot Token (or press Enter to skip): ');
|
|
100
|
-
const encKey = await question('🔐 Enter 32-char Encryption Key (or press Enter for auto-generate): ');
|
|
329
|
+
const spin1 = new Spinner('Writing .env configuration...');
|
|
330
|
+
spin1.start();
|
|
331
|
+
await sleep(600);
|
|
101
332
|
|
|
102
333
|
const envPath = path.join(backendDir, '.env');
|
|
103
|
-
let envVars =
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
currentEnv.split('\n').forEach(line => {
|
|
109
|
-
const eqIndex = line.indexOf('=');
|
|
110
|
-
if (eqIndex > 0) {
|
|
111
|
-
const k = line.substring(0, eqIndex).trim();
|
|
112
|
-
const v = line.substring(eqIndex + 1).trim();
|
|
113
|
-
if (k) envVars[k] = v;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
} else {
|
|
117
|
-
envVars = {
|
|
334
|
+
let envVars = readEnv();
|
|
335
|
+
|
|
336
|
+
// Set defaults if fresh install
|
|
337
|
+
if (!envVars['PORT']) {
|
|
338
|
+
Object.assign(envVars, {
|
|
118
339
|
PORT: '5000',
|
|
119
340
|
MONGO_URI: 'mongodb://localhost:27017/samarthya',
|
|
120
341
|
JWT_SECRET: 'samarthya_secret_key_change_in_production',
|
|
121
342
|
NODE_ENV: 'production',
|
|
122
343
|
CORS_ORIGIN: 'http://localhost:5000',
|
|
123
|
-
USE_OLLAMA: useOllama,
|
|
124
|
-
ACTIVE_PROVIDER: activeProvider,
|
|
125
|
-
ACTIVE_MODEL: activeProvider === 'gemini' ? 'gemini-2.5-flash' : '',
|
|
126
344
|
OLLAMA_URL: 'http://localhost:11434',
|
|
127
345
|
OLLAMA_MODEL: 'dolphin3:8b-llama3.1-q4_K_M',
|
|
128
346
|
RESTRICT_TO_WORKSPACE: 'true',
|
|
129
|
-
HEARTBEAT_INTERVAL: '30'
|
|
130
|
-
};
|
|
347
|
+
HEARTBEAT_INTERVAL: '30',
|
|
348
|
+
});
|
|
131
349
|
}
|
|
132
350
|
|
|
133
|
-
envVars['USE_OLLAMA'] = useOllama;
|
|
134
351
|
envVars['ACTIVE_PROVIDER'] = activeProvider;
|
|
352
|
+
envVars['USE_OLLAMA'] = useOllama;
|
|
353
|
+
if (activeProvider === 'gemini' && !envVars['ACTIVE_MODEL']) envVars['ACTIVE_MODEL'] = 'gemini-2.5-flash';
|
|
135
354
|
|
|
136
|
-
|
|
137
|
-
if (geminiKey.trim()) envVars['GEMINI_API_KEY'] = geminiKey.trim();
|
|
138
|
-
if (anthropicKey.trim()) envVars['ANTHROPIC_API_KEY'] = anthropicKey.trim();
|
|
139
|
-
if (groqKey.trim()) envVars['GROQ_API_KEY'] = groqKey.trim();
|
|
140
|
-
if (openAiKey.trim()) envVars['OPENAI_API_KEY'] = openAiKey.trim();
|
|
141
|
-
if (deepseekKey.trim()) envVars['DEEPSEEK_API_KEY'] = deepseekKey.trim();
|
|
142
|
-
if (qwenKey.trim()) envVars['QWEN_API_KEY'] = qwenKey.trim();
|
|
143
|
-
if (openrouterKey.trim()) envVars['OPENROUTER_API_KEY'] = openrouterKey.trim();
|
|
355
|
+
if (apiKey.trim() && provider.envKey) envVars[provider.envKey] = apiKey.trim();
|
|
144
356
|
if (telegramToken.trim()) envVars['TELEGRAM_BOT_TOKEN'] = telegramToken.trim();
|
|
145
357
|
if (discordToken.trim()) envVars['DISCORD_BOT_TOKEN'] = discordToken.trim();
|
|
146
358
|
|
|
147
|
-
// Auto-generate encryption key
|
|
148
|
-
if (
|
|
149
|
-
envVars['MEMORY_ENCRYPTION_KEY'] = encKey.trim();
|
|
150
|
-
} else if (!envVars['MEMORY_ENCRYPTION_KEY']) {
|
|
359
|
+
// Auto-generate encryption key
|
|
360
|
+
if (!envVars['MEMORY_ENCRYPTION_KEY']) {
|
|
151
361
|
const crypto = require('crypto');
|
|
152
362
|
envVars['MEMORY_ENCRYPTION_KEY'] = crypto.randomBytes(16).toString('hex');
|
|
153
|
-
console.log('🔐 Auto-generated encryption key.');
|
|
154
363
|
}
|
|
155
364
|
|
|
156
365
|
if (!envVars['GEMINI_API_KEY']) envVars['GEMINI_API_KEY'] = 'dummy';
|
|
157
366
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
fs.writeFileSync(envPath, newEnvContent);
|
|
161
|
-
console.log('\n✅ Keys saved to .env file securely.');
|
|
367
|
+
writeEnv(envVars);
|
|
368
|
+
spin1.stop('Configuration saved');
|
|
162
369
|
|
|
163
|
-
|
|
370
|
+
const spin2 = new Spinner('Installing dependencies...');
|
|
371
|
+
spin2.start();
|
|
164
372
|
try {
|
|
165
|
-
execSync('npm install --production', { cwd: backendDir, stdio: 'ignore' });
|
|
166
|
-
|
|
373
|
+
execSync('npm install --production 2>/dev/null', { cwd: backendDir, stdio: 'ignore' });
|
|
374
|
+
spin2.stop('Dependencies installed');
|
|
375
|
+
} catch {
|
|
376
|
+
spin2.fail('Dependencies install failed (non-critical)');
|
|
377
|
+
}
|
|
167
378
|
|
|
168
379
|
rl.close();
|
|
169
380
|
|
|
170
|
-
//
|
|
171
|
-
|
|
381
|
+
// ── Step 5: Boot Gateway ──
|
|
382
|
+
stepHeader(5, TOTAL_STEPS, 'Starting SamarthyaBot');
|
|
383
|
+
|
|
384
|
+
console.log();
|
|
385
|
+
const bootSteps = [
|
|
386
|
+
'Loading AI modules',
|
|
387
|
+
'Connecting database',
|
|
388
|
+
'Starting security sandbox',
|
|
389
|
+
'Initializing heartbeat service',
|
|
390
|
+
'Registering tool packs',
|
|
391
|
+
'Booting web server'
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
for (let i = 0; i < bootSteps.length; i++) {
|
|
395
|
+
const s = new Spinner(bootSteps[i] + '...');
|
|
396
|
+
s.start();
|
|
397
|
+
await sleep(400 + Math.random() * 300);
|
|
398
|
+
s.stop(bootSteps[i]);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
console.log(`\n${c.gray} ─────────────────────────────────────────────────────────────${c.reset}`);
|
|
402
|
+
console.log(` ${c.lime}${c.bold}🚀 SamarthyaBot is READY!${c.reset}`);
|
|
403
|
+
console.log(` ${c.dim}Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}`);
|
|
404
|
+
console.log(` ${c.dim}Provider: ${provider.color}${provider.name}${c.reset}`);
|
|
405
|
+
if (telegramToken.trim()) console.log(` ${c.dim}Telegram: ${c.lime}Connected${c.reset}`);
|
|
406
|
+
if (discordToken.trim()) console.log(` ${c.dim}Discord: ${c.purple}Connected${c.reset}`);
|
|
407
|
+
console.log(`${c.gray} ─────────────────────────────────────────────────────────────${c.reset}\n`);
|
|
172
408
|
|
|
173
|
-
// Load
|
|
409
|
+
// Load .env and spawn the server
|
|
174
410
|
try { require('dotenv').config({ path: envPath }); } catch (e) { }
|
|
175
411
|
|
|
176
412
|
const gatewayChild = spawn('node', ['server.js'], {
|
|
@@ -179,338 +415,406 @@ switch (command) {
|
|
|
179
415
|
});
|
|
180
416
|
|
|
181
417
|
gatewayChild.on('close', (code) => {
|
|
182
|
-
console.log(
|
|
418
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
183
419
|
});
|
|
184
420
|
|
|
185
|
-
//
|
|
421
|
+
// Auto-start tunnel if Telegram configured
|
|
186
422
|
setTimeout(() => {
|
|
187
423
|
if (envVars['TELEGRAM_BOT_TOKEN'] && envVars['TELEGRAM_BOT_TOKEN'] !== 'dummy') {
|
|
188
|
-
|
|
424
|
+
info('Auto-starting tunnel for Telegram webhook...');
|
|
189
425
|
const isWin = process.platform === 'win32';
|
|
190
426
|
const tunnelChild = spawn('npm', ['exec', 'localtunnel', '--', '--port', '5000'], {
|
|
191
|
-
stdio: 'pipe',
|
|
192
|
-
shell: isWin
|
|
427
|
+
stdio: 'pipe', shell: isWin
|
|
193
428
|
});
|
|
194
|
-
|
|
195
429
|
tunnelChild.stdout.on('data', async (data) => {
|
|
196
430
|
const output = data.toString();
|
|
197
|
-
console.log(output.trim());
|
|
198
431
|
const match = output.match(/your url is: (https:\/\/.+)/);
|
|
199
432
|
if (match && match[1]) {
|
|
200
433
|
const publicUrl = match[1];
|
|
201
|
-
|
|
434
|
+
success(`Public URL: ${c.cyan}${c.under}${publicUrl}${c.reset}`);
|
|
202
435
|
if (envVars['TELEGRAM_BOT_TOKEN']) {
|
|
203
436
|
try {
|
|
204
437
|
const tgUrl = `https://api.telegram.org/bot${envVars['TELEGRAM_BOT_TOKEN']}/setWebhook?url=${publicUrl}/api/telegram/webhook`;
|
|
205
438
|
const res = await fetch(tgUrl);
|
|
206
439
|
const result = await res.json();
|
|
207
|
-
if (result.ok)
|
|
208
|
-
else
|
|
209
|
-
} catch (err) {
|
|
210
|
-
console.log('🔴 Webhook error:', err.message);
|
|
211
|
-
}
|
|
440
|
+
if (result.ok) success('Telegram Webhook set!');
|
|
441
|
+
else warn('Telegram Webhook failed: ' + result.description);
|
|
442
|
+
} catch (err) { warn('Webhook error: ' + err.message); }
|
|
212
443
|
}
|
|
213
444
|
}
|
|
214
445
|
});
|
|
215
|
-
|
|
216
|
-
tunnelChild.stderr.on('data', (data) => console.error('Tunnel:', data.toString()));
|
|
217
|
-
} else {
|
|
218
|
-
console.log('\n✅ Gateway is running! Open http://localhost:5000');
|
|
219
|
-
console.log('ℹ️ No Telegram token found — skipping tunnel. Run "samarthya tunnel" later if needed.');
|
|
446
|
+
tunnelChild.stderr.on('data', (data) => warn('Tunnel: ' + data.toString().trim()));
|
|
220
447
|
}
|
|
221
448
|
}, 3000);
|
|
449
|
+
})();
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
454
|
+
// ║ 🧠 MODEL SWITCH ║
|
|
455
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
456
|
+
case 'model': {
|
|
457
|
+
console.log(`\n${c.saffron}${c.bold} 🧠 Model Selector${c.reset}\n`);
|
|
458
|
+
|
|
459
|
+
const rl = createRL();
|
|
460
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
461
|
+
|
|
462
|
+
(async () => {
|
|
463
|
+
// Step 1: Provider
|
|
464
|
+
console.log(` ${c.bold}Select Provider:${c.reset}`);
|
|
465
|
+
PROVIDERS.forEach((p, i) => {
|
|
466
|
+
console.log(` ${c.dim}${i + 1})${c.reset} ${p.color}${p.name}${c.reset} ${c.gray}(${p.tag})${c.reset}`);
|
|
467
|
+
});
|
|
468
|
+
console.log();
|
|
469
|
+
|
|
470
|
+
let pRaw = await q(` Choice [1-9]: `);
|
|
471
|
+
const pIdx = parseInt(pRaw.trim()) - 1;
|
|
472
|
+
const prov = PROVIDERS[pIdx >= 0 && pIdx < PROVIDERS.length ? pIdx : 0];
|
|
473
|
+
|
|
474
|
+
console.log(`\n ${c.lime}✔${c.reset} Provider: ${prov.color}${c.bold}${prov.name}${c.reset}\n`);
|
|
475
|
+
|
|
476
|
+
// Step 2: Model
|
|
477
|
+
const models = MODELS[prov.id] || [];
|
|
478
|
+
if (models.length > 0) {
|
|
479
|
+
console.log(` ${c.bold}Select Model:${c.reset}`);
|
|
480
|
+
models.forEach((m, i) => {
|
|
481
|
+
console.log(` ${c.dim}${i + 1})${c.reset} ${c.white}${m.id}${c.reset} ${c.gray}— ${m.desc}${c.reset}`);
|
|
482
|
+
});
|
|
483
|
+
console.log();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
let mRaw = await q(` Choice (or type custom ID): `);
|
|
487
|
+
const mIdx = parseInt(mRaw.trim()) - 1;
|
|
488
|
+
let modelId = '';
|
|
489
|
+
if (mIdx >= 0 && mIdx < models.length) modelId = models[mIdx].id;
|
|
490
|
+
else if (mRaw.trim()) modelId = mRaw.trim();
|
|
491
|
+
else modelId = models[0]?.id || '';
|
|
492
|
+
|
|
493
|
+
console.log(`\n ${c.lime}✔${c.reset} Model: ${c.cyan}${c.bold}${modelId}${c.reset}\n`);
|
|
222
494
|
|
|
223
|
-
//
|
|
495
|
+
// Save
|
|
496
|
+
const spin = new Spinner('Updating configuration...');
|
|
497
|
+
spin.start();
|
|
498
|
+
await sleep(400);
|
|
499
|
+
|
|
500
|
+
const envVars = readEnv();
|
|
501
|
+
envVars['ACTIVE_PROVIDER'] = prov.id;
|
|
502
|
+
envVars['ACTIVE_MODEL'] = modelId;
|
|
503
|
+
envVars['USE_OLLAMA'] = prov.id === 'ollama' ? 'true' : 'false';
|
|
504
|
+
if (prov.id === 'ollama') envVars['OLLAMA_MODEL'] = modelId;
|
|
505
|
+
writeEnv(envVars);
|
|
506
|
+
|
|
507
|
+
spin.stop('Configuration updated');
|
|
508
|
+
warn('Restart the gateway for changes to take effect: ' + c.saffron + 'samarthya restart' + c.reset);
|
|
509
|
+
|
|
510
|
+
rl.close();
|
|
224
511
|
})();
|
|
225
512
|
break;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
516
|
+
// ║ 📱 TELEGRAM CONFIG ║
|
|
517
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
518
|
+
case 'telegram': {
|
|
519
|
+
console.log(`\n${c.saffron}${c.bold} 📱 Telegram Configuration${c.reset}\n`);
|
|
226
520
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const rlModel = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
230
|
-
const qModel = (query) => new Promise(res => rlModel.question(query, res));
|
|
521
|
+
const rl = createRL();
|
|
522
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
231
523
|
|
|
232
524
|
(async () => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.log(" 5) DeepSeek");
|
|
239
|
-
console.log(" 6) Qwen (Alibaba)");
|
|
240
|
-
console.log(" 7) OpenRouter (100+ models)");
|
|
241
|
-
console.log(" 8) Local Ollama (Offline)");
|
|
242
|
-
console.log(" 9) Mistral AI\n");
|
|
243
|
-
|
|
244
|
-
let pRaw = await qModel("Enter choice (1-9, default 1): ");
|
|
245
|
-
let aProv = 'gemini';
|
|
246
|
-
let uOll = 'false';
|
|
247
|
-
|
|
248
|
-
switch (pRaw.trim()) {
|
|
249
|
-
case '2': aProv = 'anthropic'; break;
|
|
250
|
-
case '3': aProv = 'groq'; break;
|
|
251
|
-
case '4': aProv = 'openai'; break;
|
|
252
|
-
case '5': aProv = 'deepseek'; break;
|
|
253
|
-
case '6': aProv = 'qwen'; break;
|
|
254
|
-
case '7': aProv = 'openrouter'; break;
|
|
255
|
-
case '8': aProv = 'ollama'; uOll = 'true'; break;
|
|
256
|
-
case '9': aProv = 'mistral'; break;
|
|
257
|
-
default: aProv = 'gemini'; break;
|
|
525
|
+
const envVars = readEnv();
|
|
526
|
+
const existing = envVars['TELEGRAM_BOT_TOKEN'];
|
|
527
|
+
|
|
528
|
+
if (existing && existing !== 'dummy') {
|
|
529
|
+
info(`Current token: ${c.dim}${maskKey(existing)}${c.reset}`);
|
|
258
530
|
}
|
|
259
531
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
else if (mSel.trim() === '4') aMod = 'gemini-2.0-flash';
|
|
272
|
-
else aMod = mSel.trim();
|
|
273
|
-
} else if (aProv === 'openai') {
|
|
274
|
-
console.log(" 1) gpt-5.2 (Best for coding & agentic tasks)");
|
|
275
|
-
console.log(" 2) gpt-5-mini (Faster, cost-efficient GPT-5)");
|
|
276
|
-
console.log(" 3) gpt-4o (Fast, intelligent, flexible)");
|
|
277
|
-
console.log(" 4) o3-mini (Small reasoning alternative)");
|
|
278
|
-
const mSel = await qModel("Enter model choice (or type custom ID): ");
|
|
279
|
-
if (mSel.trim() === '1') aMod = 'gpt-5.2';
|
|
280
|
-
else if (mSel.trim() === '2') aMod = 'gpt-5-mini';
|
|
281
|
-
else if (mSel.trim() === '3') aMod = 'gpt-4o';
|
|
282
|
-
else if (mSel.trim() === '4') aMod = 'o3-mini';
|
|
283
|
-
else aMod = mSel.trim();
|
|
284
|
-
} else if (aProv === 'groq') {
|
|
285
|
-
console.log(" 1) llama-3.3-70b-versatile (Best overall Llama)");
|
|
286
|
-
console.log(" 2) llama-3.1-8b-instant (Extreme Fast Llama)");
|
|
287
|
-
console.log(" 3) qwen/qwen3-32b (Powerful 32b reasoning)");
|
|
288
|
-
const mSel = await qModel("Enter model choice (or type custom ID): ");
|
|
289
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'llama-3.3-70b-versatile';
|
|
290
|
-
else if (mSel.trim() === '2') aMod = 'llama-3.1-8b-instant';
|
|
291
|
-
else if (mSel.trim() === '3') aMod = 'qwen/qwen3-32b';
|
|
292
|
-
else aMod = mSel.trim();
|
|
293
|
-
} else if (aProv === 'mistral') {
|
|
294
|
-
console.log(" 1) mistral-large-3 (General-purpose multimodal)");
|
|
295
|
-
console.log(" 2) ministral-3-8b (Powerful & efficient)");
|
|
296
|
-
console.log(" 3) mistral-small-3.2 (Small latest model)");
|
|
297
|
-
console.log(" 4) devstral-2 (Best for code & agents)");
|
|
298
|
-
const mSel = await qModel("Enter model choice (or type custom ID): ");
|
|
299
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'mistral-large-3';
|
|
300
|
-
else if (mSel.trim() === '2') aMod = 'ministral-3-8b';
|
|
301
|
-
else if (mSel.trim() === '3') aMod = 'mistral-small-3.2';
|
|
302
|
-
else if (mSel.trim() === '4') aMod = 'devstral-2';
|
|
303
|
-
else aMod = mSel.trim();
|
|
304
|
-
} else if (aProv === 'anthropic') {
|
|
305
|
-
console.log(" 1) claude-3-5-sonnet-latest");
|
|
306
|
-
console.log(" 2) claude-3-opus-latest");
|
|
307
|
-
const mSel = await qModel("Enter model choice (or type custom ID): ");
|
|
308
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'claude-3-5-sonnet-latest';
|
|
309
|
-
else if (mSel.trim() === '2') aMod = 'claude-3-opus-latest';
|
|
310
|
-
else aMod = mSel.trim();
|
|
311
|
-
} else if (aProv === 'ollama') {
|
|
312
|
-
console.log(" Your models list usually includes:");
|
|
313
|
-
console.log(" 1) dolphin3:8b-llama3.1-q4_K_M (Default)");
|
|
314
|
-
console.log(" 2) llama3:8b");
|
|
315
|
-
console.log(" 3) mistral");
|
|
316
|
-
const mSel = await qModel("Enter model choice (or type custom Ollama tag): ");
|
|
317
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'dolphin3:8b-llama3.1-q4_K_M';
|
|
318
|
-
else if (mSel.trim() === '2') aMod = 'llama3:8b';
|
|
319
|
-
else if (mSel.trim() === '3') aMod = 'mistral';
|
|
320
|
-
else aMod = mSel.trim();
|
|
532
|
+
const token = await q(` 🤖 Enter Telegram Bot Token (or Enter to keep): `);
|
|
533
|
+
if (token.trim()) {
|
|
534
|
+
const spin = new Spinner('Saving Telegram token...');
|
|
535
|
+
spin.start();
|
|
536
|
+
await sleep(400);
|
|
537
|
+
envVars['TELEGRAM_BOT_TOKEN'] = token.trim();
|
|
538
|
+
writeEnv(envVars);
|
|
539
|
+
spin.stop('Telegram token saved');
|
|
540
|
+
info(`Run ${c.saffron}samarthya tunnel${c.reset} to set webhook automatically`);
|
|
541
|
+
} else {
|
|
542
|
+
info('No changes made');
|
|
321
543
|
}
|
|
544
|
+
rl.close();
|
|
545
|
+
})();
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
322
548
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
let mUpdated = false;
|
|
329
|
-
|
|
330
|
-
lines = lines.map(line => {
|
|
331
|
-
if (line.startsWith('ACTIVE_PROVIDER=')) { updated = true; return `ACTIVE_PROVIDER=${aProv}`; }
|
|
332
|
-
if (line.startsWith('ACTIVE_MODEL=')) { mUpdated = true; return `ACTIVE_MODEL=${aMod}`; }
|
|
333
|
-
if (line.startsWith('USE_OLLAMA=')) { uUpdated = true; return `USE_OLLAMA=${uOll}`; }
|
|
334
|
-
if (aProv === 'ollama' && line.startsWith('OLLAMA_MODEL=')) { return `OLLAMA_MODEL=${aMod}`; }
|
|
335
|
-
return line;
|
|
336
|
-
});
|
|
549
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
550
|
+
// ║ 🟣 DISCORD CONFIG ║
|
|
551
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
552
|
+
case 'discord': {
|
|
553
|
+
console.log(`\n${c.purple}${c.bold} 🟣 Discord Configuration${c.reset}\n`);
|
|
337
554
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (!uUpdated) lines.push(`USE_OLLAMA=${uOll}`);
|
|
555
|
+
const rl = createRL();
|
|
556
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
341
557
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
558
|
+
(async () => {
|
|
559
|
+
const envVars = readEnv();
|
|
560
|
+
const existing = envVars['DISCORD_BOT_TOKEN'];
|
|
561
|
+
|
|
562
|
+
if (existing) {
|
|
563
|
+
info(`Current token: ${c.dim}${maskKey(existing)}${c.reset}`);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const token = await q(` 🟣 Enter Discord Bot Token (or Enter to keep): `);
|
|
567
|
+
if (token.trim()) {
|
|
568
|
+
const spin = new Spinner('Saving Discord token...');
|
|
569
|
+
spin.start();
|
|
570
|
+
await sleep(400);
|
|
571
|
+
envVars['DISCORD_BOT_TOKEN'] = token.trim();
|
|
572
|
+
writeEnv(envVars);
|
|
573
|
+
spin.stop('Discord token saved');
|
|
574
|
+
info(`Restart gateway to connect: ${c.saffron}samarthya restart${c.reset}`);
|
|
345
575
|
} else {
|
|
346
|
-
|
|
576
|
+
info('No changes made');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Allow-list config
|
|
580
|
+
const allowFrom = await q(` 👤 Discord user IDs to allow (comma-separated, or Enter to skip): `);
|
|
581
|
+
if (allowFrom.trim()) {
|
|
582
|
+
envVars['DISCORD_ALLOW_FROM'] = allowFrom.trim();
|
|
583
|
+
writeEnv(envVars);
|
|
584
|
+
success('Allow-list updated');
|
|
347
585
|
}
|
|
348
586
|
|
|
349
|
-
|
|
587
|
+
rl.close();
|
|
350
588
|
})();
|
|
351
589
|
break;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
593
|
+
// ║ ⚙️ CONFIG VIEWER ║
|
|
594
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
595
|
+
case 'config': {
|
|
596
|
+
console.log(`\n${c.saffron}${c.bold} ⚙️ Current Configuration${c.reset}\n`);
|
|
597
|
+
const envVars = readEnv();
|
|
598
|
+
|
|
599
|
+
const configItems = [
|
|
600
|
+
{ label: 'AI Provider', key: 'ACTIVE_PROVIDER', color: c.cyan },
|
|
601
|
+
{ label: 'AI Model', key: 'ACTIVE_MODEL', color: c.cyan },
|
|
602
|
+
{ label: 'Port', key: 'PORT', color: c.white },
|
|
603
|
+
{ label: 'MongoDB', key: 'MONGO_URI', color: c.green },
|
|
604
|
+
{ label: 'Gemini Key', key: 'GEMINI_API_KEY', color: c.cyan, mask: true },
|
|
605
|
+
{ label: 'Groq Key', key: 'GROQ_API_KEY', color: c.yellow, mask: true },
|
|
606
|
+
{ label: 'Anthropic Key', key: 'ANTHROPIC_API_KEY', color: c.purple, mask: true },
|
|
607
|
+
{ label: 'OpenAI Key', key: 'OPENAI_API_KEY', color: c.lime, mask: true },
|
|
608
|
+
{ label: 'DeepSeek Key', key: 'DEEPSEEK_API_KEY', color: c.cyan, mask: true },
|
|
609
|
+
{ label: 'OpenRouter Key', key: 'OPENROUTER_API_KEY', color: c.saffron, mask: true },
|
|
610
|
+
{ label: 'Telegram Token', key: 'TELEGRAM_BOT_TOKEN', color: c.cyan, mask: true },
|
|
611
|
+
{ label: 'Discord Token', key: 'DISCORD_BOT_TOKEN', color: c.purple, mask: true },
|
|
612
|
+
{ label: 'Encryption Key', key: 'MEMORY_ENCRYPTION_KEY', color: c.red, mask: true },
|
|
613
|
+
{ label: 'Sandbox', key: 'RESTRICT_TO_WORKSPACE', color: c.green },
|
|
614
|
+
{ label: 'Heartbeat (min)', key: 'HEARTBEAT_INTERVAL', color: c.pink },
|
|
615
|
+
];
|
|
616
|
+
|
|
617
|
+
const maxLabel = Math.max(...configItems.map(i => i.label.length));
|
|
618
|
+
|
|
619
|
+
configItems.forEach(item => {
|
|
620
|
+
const val = envVars[item.key];
|
|
621
|
+
const label = item.label.padEnd(maxLabel + 2);
|
|
622
|
+
if (val && val !== 'dummy') {
|
|
623
|
+
const display = item.mask ? maskKey(val) : val;
|
|
624
|
+
console.log(` ${c.dim}${label}${c.reset} ${item.color}${display}${c.reset}`);
|
|
625
|
+
} else {
|
|
626
|
+
console.log(` ${c.dim}${label}${c.reset} ${c.gray}not set${c.reset}`);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
console.log(`\n ${c.dim}File: ${path.join(backendDir, '.env')}${c.reset}\n`);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
352
633
|
|
|
353
|
-
|
|
634
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
635
|
+
// ║ 🌐 GATEWAY ║
|
|
636
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
637
|
+
case 'gateway': {
|
|
354
638
|
if (isServerRunning()) {
|
|
355
|
-
|
|
356
|
-
|
|
639
|
+
warn('Gateway is already running on port 5000!');
|
|
640
|
+
info(`Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}`);
|
|
357
641
|
process.exit(0);
|
|
358
642
|
}
|
|
359
643
|
|
|
360
|
-
|
|
644
|
+
console.log(MINI_BANNER);
|
|
361
645
|
|
|
362
|
-
|
|
363
|
-
const activeModel = process.env.ACTIVE_MODEL || 'gemini-2.5-flash';
|
|
646
|
+
try { require('dotenv').config({ path: path.join(backendDir, '.env') }); } catch (e) { }
|
|
364
647
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
console.log(
|
|
648
|
+
const prov = process.env.ACTIVE_PROVIDER?.toUpperCase() || 'GEMINI';
|
|
649
|
+
const mod = process.env.ACTIVE_MODEL || 'gemini-2.5-flash';
|
|
650
|
+
|
|
651
|
+
console.log(` ${c.dim}Provider: ${c.saffron}${prov}${c.reset} ${c.dim}(${mod})${c.reset}`);
|
|
652
|
+
console.log(` ${c.dim}Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}\n`);
|
|
369
653
|
|
|
370
|
-
// We use spawn to run the server
|
|
371
654
|
const child = spawn('node', ['server.js'], {
|
|
372
655
|
cwd: backendDir,
|
|
373
656
|
stdio: 'inherit'
|
|
374
657
|
});
|
|
375
658
|
|
|
376
659
|
child.on('close', (code) => {
|
|
377
|
-
console.log(
|
|
660
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
378
661
|
});
|
|
379
662
|
break;
|
|
663
|
+
}
|
|
380
664
|
|
|
381
|
-
|
|
665
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
666
|
+
// ║ 📊 STATUS ║
|
|
667
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
668
|
+
case 'status': {
|
|
669
|
+
console.log();
|
|
382
670
|
if (isServerRunning()) {
|
|
383
|
-
|
|
384
|
-
|
|
671
|
+
success(`${c.bold}SamarthyaBot is ${c.lime}ONLINE${c.reset}`);
|
|
672
|
+
info(`Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}`);
|
|
673
|
+
|
|
674
|
+
const envVars = readEnv();
|
|
675
|
+
if (envVars['ACTIVE_PROVIDER']) {
|
|
676
|
+
info(`Provider: ${c.saffron}${envVars['ACTIVE_PROVIDER'].toUpperCase()}${c.reset}`);
|
|
677
|
+
}
|
|
385
678
|
} else {
|
|
386
|
-
|
|
679
|
+
error(`${c.bold}SamarthyaBot is ${c.red}OFFLINE${c.reset}`);
|
|
680
|
+
info(`Start with: ${c.saffron}samarthya gateway${c.reset}`);
|
|
387
681
|
}
|
|
682
|
+
console.log();
|
|
388
683
|
break;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
687
|
+
// ║ 🚇 TUNNEL ║
|
|
688
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
689
|
+
case 'tunnel': {
|
|
690
|
+
console.log(`\n${c.saffron}${c.bold} 🚇 LocalTunnel${c.reset}\n`);
|
|
389
691
|
|
|
390
|
-
case 'tunnel':
|
|
391
|
-
console.log('🚇 Starting LocalTunnel to expose port 5000 to the internet...');
|
|
392
692
|
if (!isServerRunning()) {
|
|
393
|
-
|
|
693
|
+
warn('Gateway is not running! Start it first: ' + c.saffron + 'samarthya gateway' + c.reset);
|
|
394
694
|
}
|
|
395
695
|
|
|
396
696
|
try { require('dotenv').config({ path: path.join(backendDir, '.env') }); } catch (e) { }
|
|
397
697
|
|
|
398
|
-
const
|
|
698
|
+
const spin = new Spinner('Connecting to tunnel service...');
|
|
699
|
+
spin.start();
|
|
700
|
+
|
|
701
|
+
const isWin = process.platform === 'win32';
|
|
399
702
|
const tunnelProcess = spawn('npm', ['exec', 'localtunnel', '--', '--port', '5000'], {
|
|
400
|
-
stdio: 'pipe',
|
|
401
|
-
shell: isWindows
|
|
703
|
+
stdio: 'pipe', shell: isWin
|
|
402
704
|
});
|
|
403
705
|
|
|
404
706
|
tunnelProcess.stdout.on('data', async (data) => {
|
|
405
707
|
const output = data.toString();
|
|
406
|
-
console.log(output.trim());
|
|
407
708
|
const match = output.match(/your url is: (https:\/\/.+)/);
|
|
408
709
|
if (match && match[1]) {
|
|
710
|
+
spin.stop('Tunnel connected');
|
|
409
711
|
const publicUrl = match[1];
|
|
410
|
-
|
|
712
|
+
success(`Public URL: ${c.cyan}${c.under}${publicUrl}${c.reset}`);
|
|
713
|
+
info(`Telegram webhook: ${publicUrl}/api/telegram/webhook`);
|
|
714
|
+
info(`WhatsApp webhook: ${publicUrl}/api/whatsapp/webhook`);
|
|
411
715
|
|
|
412
|
-
// Set Telegram Webhook Automatically
|
|
413
716
|
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
414
|
-
|
|
717
|
+
const spin2 = new Spinner('Setting Telegram webhook...');
|
|
718
|
+
spin2.start();
|
|
415
719
|
try {
|
|
416
720
|
const tgUrl = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/setWebhook?url=${publicUrl}/api/telegram/webhook`;
|
|
417
721
|
const res = await fetch(tgUrl);
|
|
418
722
|
const result = await res.json();
|
|
419
|
-
if (result.ok)
|
|
420
|
-
|
|
421
|
-
} else {
|
|
422
|
-
console.log('🔴 Failed to set Telegram Webhook:', result.description);
|
|
423
|
-
}
|
|
723
|
+
if (result.ok) spin2.stop('Telegram webhook set!');
|
|
724
|
+
else spin2.fail('Webhook failed: ' + result.description);
|
|
424
725
|
} catch (err) {
|
|
425
|
-
|
|
726
|
+
spin2.fail('Webhook error: ' + err.message);
|
|
426
727
|
}
|
|
427
|
-
} else {
|
|
428
|
-
console.log('ℹ️ TELEGRAM_BOT_TOKEN not found in .env. Skipping Telegram Webhook setup.');
|
|
429
728
|
}
|
|
430
729
|
|
|
431
|
-
console.log(
|
|
432
|
-
console.log(` ${publicUrl}/api/whatsapp/webhook`);
|
|
433
|
-
console.log('\n(Leave this terminal running to keep the tunnel open natively)');
|
|
730
|
+
console.log(`\n ${c.dim}Keep this terminal open to maintain the tunnel${c.reset}\n`);
|
|
434
731
|
}
|
|
435
732
|
});
|
|
436
733
|
|
|
437
734
|
tunnelProcess.stderr.on('data', (data) => {
|
|
438
|
-
|
|
735
|
+
const msg = data.toString().trim();
|
|
736
|
+
if (msg) warn('Tunnel: ' + msg);
|
|
439
737
|
});
|
|
440
738
|
|
|
441
739
|
tunnelProcess.on('close', (code) => {
|
|
442
|
-
console.log(
|
|
740
|
+
console.log(`\n${c.dim}Tunnel closed with code ${code}${c.reset}`);
|
|
443
741
|
});
|
|
444
|
-
|
|
445
742
|
break;
|
|
743
|
+
}
|
|
446
744
|
|
|
447
|
-
|
|
745
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
746
|
+
// ║ 🛑 STOP ║
|
|
747
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
748
|
+
case 'stop': {
|
|
749
|
+
console.log();
|
|
448
750
|
if (isServerRunning()) {
|
|
449
|
-
|
|
751
|
+
const spin = new Spinner('Stopping SamarthyaBot...');
|
|
752
|
+
spin.start();
|
|
450
753
|
try {
|
|
451
754
|
if (process.platform === 'win32') {
|
|
452
|
-
// Find PID of process listening on port 5000 and kill it
|
|
453
755
|
const netstatOut = execSync('netstat -ano | findstr :5000', { encoding: 'utf-8' });
|
|
454
|
-
|
|
455
|
-
for (const line of lines) {
|
|
756
|
+
netstatOut.split('\n').forEach(line => {
|
|
456
757
|
if (line.includes('LISTENING')) {
|
|
457
|
-
const parts = line.trim().split(
|
|
758
|
+
const parts = line.trim().split(/\s+/);
|
|
458
759
|
const pid = parts[parts.length - 1];
|
|
459
|
-
if (pid && pid !== '0') {
|
|
460
|
-
execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
461
|
-
}
|
|
760
|
+
if (pid && pid !== '0') execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
462
761
|
}
|
|
463
|
-
}
|
|
762
|
+
});
|
|
464
763
|
} else {
|
|
465
|
-
// macOS and Linux
|
|
466
764
|
execSync('lsof -t -i:5000 | xargs kill -9 2>/dev/null || fuser -k 5000/tcp 2>/dev/null');
|
|
467
765
|
}
|
|
468
|
-
|
|
469
|
-
} catch
|
|
470
|
-
|
|
766
|
+
spin.stop('Gateway stopped');
|
|
767
|
+
} catch {
|
|
768
|
+
spin.fail('Failed to stop gateway');
|
|
471
769
|
}
|
|
472
770
|
} else {
|
|
473
|
-
|
|
771
|
+
info('Gateway is not running');
|
|
474
772
|
}
|
|
773
|
+
console.log();
|
|
475
774
|
break;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
778
|
+
// ║ 🔄 RESTART ║
|
|
779
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
780
|
+
case 'restart': {
|
|
781
|
+
console.log();
|
|
782
|
+
const spin = new Spinner('Restarting SamarthyaBot...');
|
|
783
|
+
spin.start();
|
|
476
784
|
|
|
477
|
-
case 'restart':
|
|
478
|
-
console.log('🔄 Restarting SamarthyaBot Gateway...');
|
|
479
785
|
if (isServerRunning()) {
|
|
480
786
|
try {
|
|
481
787
|
if (process.platform === 'win32') {
|
|
482
788
|
const netstatOut = execSync('netstat -ano | findstr :5000', { encoding: 'utf-8' });
|
|
483
|
-
|
|
484
|
-
for (const line of lines) {
|
|
789
|
+
netstatOut.split('\n').forEach(line => {
|
|
485
790
|
if (line.includes('LISTENING')) {
|
|
486
|
-
const parts = line.trim().split(
|
|
791
|
+
const parts = line.trim().split(/\s+/);
|
|
487
792
|
const pid = parts[parts.length - 1];
|
|
488
|
-
if (pid && pid !== '0') {
|
|
489
|
-
execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
490
|
-
}
|
|
793
|
+
if (pid && pid !== '0') execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
491
794
|
}
|
|
492
|
-
}
|
|
795
|
+
});
|
|
493
796
|
} else {
|
|
494
797
|
execSync('lsof -t -i:5000 | xargs kill -9 2>/dev/null || fuser -k 5000/tcp 2>/dev/null');
|
|
495
798
|
}
|
|
496
|
-
} catch
|
|
799
|
+
} catch { }
|
|
497
800
|
}
|
|
498
|
-
|
|
801
|
+
|
|
499
802
|
setTimeout(() => {
|
|
803
|
+
spin.stop('Restarted');
|
|
500
804
|
const restartChild = spawn('node', ['server.js'], {
|
|
501
805
|
cwd: backendDir,
|
|
502
806
|
stdio: 'inherit'
|
|
503
807
|
});
|
|
504
808
|
restartChild.on('close', (code) => {
|
|
505
|
-
console.log(
|
|
809
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
506
810
|
});
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}, 1000);
|
|
811
|
+
info(`Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}\n`);
|
|
812
|
+
}, 1200);
|
|
510
813
|
break;
|
|
814
|
+
}
|
|
511
815
|
|
|
512
816
|
default:
|
|
513
|
-
|
|
514
|
-
|
|
817
|
+
error(`Unknown command: ${c.bold}${command}${c.reset}`);
|
|
818
|
+
info(`Run ${c.saffron}samarthya${c.reset} to see all commands`);
|
|
515
819
|
process.exit(1);
|
|
516
820
|
}
|
package/backend/package.json
CHANGED