samarthya-bot 1.1.4 → 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/README.md +274 -98
- package/backend/.env.example +31 -7
- package/backend/bin/samarthya.js +660 -267
- package/backend/package-lock.json +47 -25
- package/backend/package.json +4 -3
- package/backend/public/assets/index-BFRAq8Y1.js +149 -0
- package/backend/public/index.html +1 -1
- package/backend/server.js +70 -22
- package/backend/server.log +29 -0
- package/backend/services/agent/spawnService.js +140 -0
- package/backend/services/discord/discordService.js +188 -0
- package/backend/services/heartbeat/heartbeatService.js +157 -0
- package/backend/services/llm/llmService.js +118 -1
- package/backend/services/security/sandboxService.js +115 -0
- package/backend/services/voice/voiceService.js +151 -0
- package/package.json +2 -2
- package/server.log +35 -0
package/backend/bin/samarthya.js
CHANGED
|
@@ -6,422 +6,815 @@ 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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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`);
|
|
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();
|
|
314
|
+
|
|
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`);
|
|
81
323
|
}
|
|
324
|
+
console.log();
|
|
82
325
|
|
|
83
|
-
|
|
326
|
+
// ── Step 4: Save configuration ──
|
|
327
|
+
stepHeader(4, TOTAL_STEPS, 'Saving Configuration');
|
|
84
328
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const openAiKey = await question('🔑 Enter OpenAI API Key (or press Enter to skip): ');
|
|
89
|
-
const telegramToken = await question('🤖 Enter Telegram Bot Token (or press Enter to skip): ');
|
|
329
|
+
const spin1 = new Spinner('Writing .env configuration...');
|
|
330
|
+
spin1.start();
|
|
331
|
+
await sleep(600);
|
|
90
332
|
|
|
91
333
|
const envPath = path.join(backendDir, '.env');
|
|
92
|
-
let envVars =
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
currentEnv.split('\n').forEach(line => {
|
|
98
|
-
const [k, v] = line.split('=');
|
|
99
|
-
if (k && v) envVars[k.trim()] = v.trim();
|
|
100
|
-
});
|
|
101
|
-
} else {
|
|
102
|
-
envVars = {
|
|
334
|
+
let envVars = readEnv();
|
|
335
|
+
|
|
336
|
+
// Set defaults if fresh install
|
|
337
|
+
if (!envVars['PORT']) {
|
|
338
|
+
Object.assign(envVars, {
|
|
103
339
|
PORT: '5000',
|
|
104
|
-
|
|
340
|
+
MONGO_URI: 'mongodb://localhost:27017/samarthya',
|
|
105
341
|
JWT_SECRET: 'samarthya_secret_key_change_in_production',
|
|
106
342
|
NODE_ENV: 'production',
|
|
107
343
|
CORS_ORIGIN: 'http://localhost:5000',
|
|
108
|
-
USE_OLLAMA: useOllama,
|
|
109
|
-
ACTIVE_PROVIDER: activeProvider,
|
|
110
|
-
ACTIVE_MODEL: activeProvider === 'gemini' ? 'gemini-2.5-flash' : '',
|
|
111
344
|
OLLAMA_URL: 'http://localhost:11434',
|
|
112
|
-
OLLAMA_MODEL: 'dolphin3:8b-llama3.1-q4_K_M'
|
|
113
|
-
|
|
345
|
+
OLLAMA_MODEL: 'dolphin3:8b-llama3.1-q4_K_M',
|
|
346
|
+
RESTRICT_TO_WORKSPACE: 'true',
|
|
347
|
+
HEARTBEAT_INTERVAL: '30',
|
|
348
|
+
});
|
|
114
349
|
}
|
|
115
350
|
|
|
116
|
-
envVars['USE_OLLAMA'] = useOllama;
|
|
117
351
|
envVars['ACTIVE_PROVIDER'] = activeProvider;
|
|
352
|
+
envVars['USE_OLLAMA'] = useOllama;
|
|
353
|
+
if (activeProvider === 'gemini' && !envVars['ACTIVE_MODEL']) envVars['ACTIVE_MODEL'] = 'gemini-2.5-flash';
|
|
118
354
|
|
|
119
|
-
|
|
120
|
-
if (geminiKey.trim()) envVars['GEMINI_API_KEY'] = geminiKey.trim();
|
|
121
|
-
if (anthropicKey.trim()) envVars['ANTHROPIC_API_KEY'] = anthropicKey.trim();
|
|
122
|
-
if (groqKey.trim()) envVars['GROQ_API_KEY'] = groqKey.trim();
|
|
123
|
-
if (openAiKey.trim()) envVars['OPENAI_API_KEY'] = openAiKey.trim();
|
|
355
|
+
if (apiKey.trim() && provider.envKey) envVars[provider.envKey] = apiKey.trim();
|
|
124
356
|
if (telegramToken.trim()) envVars['TELEGRAM_BOT_TOKEN'] = telegramToken.trim();
|
|
357
|
+
if (discordToken.trim()) envVars['DISCORD_BOT_TOKEN'] = discordToken.trim();
|
|
358
|
+
|
|
359
|
+
// Auto-generate encryption key
|
|
360
|
+
if (!envVars['MEMORY_ENCRYPTION_KEY']) {
|
|
361
|
+
const crypto = require('crypto');
|
|
362
|
+
envVars['MEMORY_ENCRYPTION_KEY'] = crypto.randomBytes(16).toString('hex');
|
|
363
|
+
}
|
|
125
364
|
|
|
126
365
|
if (!envVars['GEMINI_API_KEY']) envVars['GEMINI_API_KEY'] = 'dummy';
|
|
127
366
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
fs.writeFileSync(envPath, newEnvContent);
|
|
131
|
-
console.log('\n✅ Keys saved to .env file securely.');
|
|
367
|
+
writeEnv(envVars);
|
|
368
|
+
spin1.stop('Configuration saved');
|
|
132
369
|
|
|
133
|
-
|
|
370
|
+
const spin2 = new Spinner('Installing dependencies...');
|
|
371
|
+
spin2.start();
|
|
134
372
|
try {
|
|
135
|
-
execSync('npm install --production', { cwd: backendDir, stdio: 'ignore' });
|
|
136
|
-
|
|
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
|
+
}
|
|
137
378
|
|
|
138
|
-
console.log('\n✨ Onboarding complete! Run "samarthya gateway" to start your AI Operator.');
|
|
139
379
|
rl.close();
|
|
140
|
-
|
|
380
|
+
|
|
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`);
|
|
408
|
+
|
|
409
|
+
// Load .env and spawn the server
|
|
410
|
+
try { require('dotenv').config({ path: envPath }); } catch (e) { }
|
|
411
|
+
|
|
412
|
+
const gatewayChild = spawn('node', ['server.js'], {
|
|
413
|
+
cwd: backendDir,
|
|
414
|
+
stdio: 'inherit'
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
gatewayChild.on('close', (code) => {
|
|
418
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Auto-start tunnel if Telegram configured
|
|
422
|
+
setTimeout(() => {
|
|
423
|
+
if (envVars['TELEGRAM_BOT_TOKEN'] && envVars['TELEGRAM_BOT_TOKEN'] !== 'dummy') {
|
|
424
|
+
info('Auto-starting tunnel for Telegram webhook...');
|
|
425
|
+
const isWin = process.platform === 'win32';
|
|
426
|
+
const tunnelChild = spawn('npm', ['exec', 'localtunnel', '--', '--port', '5000'], {
|
|
427
|
+
stdio: 'pipe', shell: isWin
|
|
428
|
+
});
|
|
429
|
+
tunnelChild.stdout.on('data', async (data) => {
|
|
430
|
+
const output = data.toString();
|
|
431
|
+
const match = output.match(/your url is: (https:\/\/.+)/);
|
|
432
|
+
if (match && match[1]) {
|
|
433
|
+
const publicUrl = match[1];
|
|
434
|
+
success(`Public URL: ${c.cyan}${c.under}${publicUrl}${c.reset}`);
|
|
435
|
+
if (envVars['TELEGRAM_BOT_TOKEN']) {
|
|
436
|
+
try {
|
|
437
|
+
const tgUrl = `https://api.telegram.org/bot${envVars['TELEGRAM_BOT_TOKEN']}/setWebhook?url=${publicUrl}/api/telegram/webhook`;
|
|
438
|
+
const res = await fetch(tgUrl);
|
|
439
|
+
const result = await res.json();
|
|
440
|
+
if (result.ok) success('Telegram Webhook set!');
|
|
441
|
+
else warn('Telegram Webhook failed: ' + result.description);
|
|
442
|
+
} catch (err) { warn('Webhook error: ' + err.message); }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
tunnelChild.stderr.on('data', (data) => warn('Tunnel: ' + data.toString().trim()));
|
|
447
|
+
}
|
|
448
|
+
}, 3000);
|
|
141
449
|
})();
|
|
142
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`);
|
|
143
458
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const rlModel = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
147
|
-
const qModel = (query) => new Promise(res => rlModel.question(query, res));
|
|
459
|
+
const rl = createRL();
|
|
460
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
148
461
|
|
|
149
462
|
(async () => {
|
|
150
|
-
|
|
151
|
-
console.log(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
console.log(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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();
|
|
169
484
|
}
|
|
170
485
|
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
} else if (aProv === 'anthropic') {
|
|
216
|
-
console.log(" 1) claude-3-5-sonnet-latest");
|
|
217
|
-
console.log(" 2) claude-3-opus-latest");
|
|
218
|
-
const mSel = await qModel("Enter model choice (or type custom ID): ");
|
|
219
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'claude-3-5-sonnet-latest';
|
|
220
|
-
else if (mSel.trim() === '2') aMod = 'claude-3-opus-latest';
|
|
221
|
-
else aMod = mSel.trim();
|
|
222
|
-
} else if (aProv === 'ollama') {
|
|
223
|
-
console.log(" Your models list usually includes:");
|
|
224
|
-
console.log(" 1) dolphin3:8b-llama3.1-q4_K_M (Default)");
|
|
225
|
-
console.log(" 2) llama3:8b");
|
|
226
|
-
console.log(" 3) mistral");
|
|
227
|
-
const mSel = await qModel("Enter model choice (or type custom Ollama tag): ");
|
|
228
|
-
if (mSel.trim() === '1' || mSel.trim() === '') aMod = 'dolphin3:8b-llama3.1-q4_K_M';
|
|
229
|
-
else if (mSel.trim() === '2') aMod = 'llama3:8b';
|
|
230
|
-
else if (mSel.trim() === '3') aMod = 'mistral';
|
|
231
|
-
else aMod = mSel.trim();
|
|
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`);
|
|
494
|
+
|
|
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();
|
|
511
|
+
})();
|
|
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`);
|
|
520
|
+
|
|
521
|
+
const rl = createRL();
|
|
522
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
523
|
+
|
|
524
|
+
(async () => {
|
|
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}`);
|
|
232
530
|
}
|
|
233
531
|
|
|
234
|
-
const
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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');
|
|
543
|
+
}
|
|
544
|
+
rl.close();
|
|
545
|
+
})();
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
550
|
+
// ║ 🟣 DISCORD CONFIG ║
|
|
551
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
552
|
+
case 'discord': {
|
|
553
|
+
console.log(`\n${c.purple}${c.bold} 🟣 Discord Configuration${c.reset}\n`);
|
|
554
|
+
|
|
555
|
+
const rl = createRL();
|
|
556
|
+
const q = (query) => ask(rl, ` ${c.white}${query}${c.reset}`);
|
|
248
557
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
}
|
|
252
565
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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}`);
|
|
256
575
|
} else {
|
|
257
|
-
|
|
576
|
+
info('No changes made');
|
|
258
577
|
}
|
|
259
578
|
|
|
260
|
-
|
|
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');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
rl.close();
|
|
261
588
|
})();
|
|
262
589
|
break;
|
|
590
|
+
}
|
|
263
591
|
|
|
264
|
-
|
|
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
|
+
}
|
|
633
|
+
|
|
634
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
635
|
+
// ║ 🌐 GATEWAY ║
|
|
636
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
637
|
+
case 'gateway': {
|
|
265
638
|
if (isServerRunning()) {
|
|
266
|
-
|
|
267
|
-
|
|
639
|
+
warn('Gateway is already running on port 5000!');
|
|
640
|
+
info(`Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}`);
|
|
268
641
|
process.exit(0);
|
|
269
642
|
}
|
|
270
643
|
|
|
271
|
-
|
|
644
|
+
console.log(MINI_BANNER);
|
|
272
645
|
|
|
273
|
-
|
|
274
|
-
|
|
646
|
+
try { require('dotenv').config({ path: path.join(backendDir, '.env') }); } catch (e) { }
|
|
647
|
+
|
|
648
|
+
const prov = process.env.ACTIVE_PROVIDER?.toUpperCase() || 'GEMINI';
|
|
649
|
+
const mod = process.env.ACTIVE_MODEL || 'gemini-2.5-flash';
|
|
275
650
|
|
|
276
|
-
console.log(
|
|
277
|
-
console.log(
|
|
278
|
-
console.log('💡 Tip: Run "samarthya model" to change your AI provider/model at any time.');
|
|
279
|
-
console.log('🌐 You can access your agent dashboard at http://localhost:5000\n');
|
|
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`);
|
|
280
653
|
|
|
281
|
-
// We use spawn to run the server
|
|
282
654
|
const child = spawn('node', ['server.js'], {
|
|
283
655
|
cwd: backendDir,
|
|
284
656
|
stdio: 'inherit'
|
|
285
657
|
});
|
|
286
658
|
|
|
287
659
|
child.on('close', (code) => {
|
|
288
|
-
console.log(
|
|
660
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
289
661
|
});
|
|
290
662
|
break;
|
|
663
|
+
}
|
|
291
664
|
|
|
292
|
-
|
|
665
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
666
|
+
// ║ 📊 STATUS ║
|
|
667
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
668
|
+
case 'status': {
|
|
669
|
+
console.log();
|
|
293
670
|
if (isServerRunning()) {
|
|
294
|
-
|
|
295
|
-
|
|
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
|
+
}
|
|
296
678
|
} else {
|
|
297
|
-
|
|
679
|
+
error(`${c.bold}SamarthyaBot is ${c.red}OFFLINE${c.reset}`);
|
|
680
|
+
info(`Start with: ${c.saffron}samarthya gateway${c.reset}`);
|
|
298
681
|
}
|
|
682
|
+
console.log();
|
|
299
683
|
break;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
687
|
+
// ║ 🚇 TUNNEL ║
|
|
688
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
689
|
+
case 'tunnel': {
|
|
690
|
+
console.log(`\n${c.saffron}${c.bold} 🚇 LocalTunnel${c.reset}\n`);
|
|
300
691
|
|
|
301
|
-
case 'tunnel':
|
|
302
|
-
console.log('🚇 Starting LocalTunnel to expose port 5000 to the internet...');
|
|
303
692
|
if (!isServerRunning()) {
|
|
304
|
-
|
|
693
|
+
warn('Gateway is not running! Start it first: ' + c.saffron + 'samarthya gateway' + c.reset);
|
|
305
694
|
}
|
|
306
695
|
|
|
307
696
|
try { require('dotenv').config({ path: path.join(backendDir, '.env') }); } catch (e) { }
|
|
308
697
|
|
|
309
|
-
const
|
|
698
|
+
const spin = new Spinner('Connecting to tunnel service...');
|
|
699
|
+
spin.start();
|
|
700
|
+
|
|
701
|
+
const isWin = process.platform === 'win32';
|
|
310
702
|
const tunnelProcess = spawn('npm', ['exec', 'localtunnel', '--', '--port', '5000'], {
|
|
311
|
-
stdio: 'pipe',
|
|
312
|
-
shell: isWindows
|
|
703
|
+
stdio: 'pipe', shell: isWin
|
|
313
704
|
});
|
|
314
705
|
|
|
315
706
|
tunnelProcess.stdout.on('data', async (data) => {
|
|
316
707
|
const output = data.toString();
|
|
317
|
-
console.log(output.trim());
|
|
318
708
|
const match = output.match(/your url is: (https:\/\/.+)/);
|
|
319
709
|
if (match && match[1]) {
|
|
710
|
+
spin.stop('Tunnel connected');
|
|
320
711
|
const publicUrl = match[1];
|
|
321
|
-
|
|
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`);
|
|
322
715
|
|
|
323
|
-
// Set Telegram Webhook Automatically
|
|
324
716
|
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
325
|
-
|
|
717
|
+
const spin2 = new Spinner('Setting Telegram webhook...');
|
|
718
|
+
spin2.start();
|
|
326
719
|
try {
|
|
327
720
|
const tgUrl = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/setWebhook?url=${publicUrl}/api/telegram/webhook`;
|
|
328
721
|
const res = await fetch(tgUrl);
|
|
329
722
|
const result = await res.json();
|
|
330
|
-
if (result.ok)
|
|
331
|
-
|
|
332
|
-
} else {
|
|
333
|
-
console.log('🔴 Failed to set Telegram Webhook:', result.description);
|
|
334
|
-
}
|
|
723
|
+
if (result.ok) spin2.stop('Telegram webhook set!');
|
|
724
|
+
else spin2.fail('Webhook failed: ' + result.description);
|
|
335
725
|
} catch (err) {
|
|
336
|
-
|
|
726
|
+
spin2.fail('Webhook error: ' + err.message);
|
|
337
727
|
}
|
|
338
|
-
} else {
|
|
339
|
-
console.log('ℹ️ TELEGRAM_BOT_TOKEN not found in .env. Skipping Telegram Webhook setup.');
|
|
340
728
|
}
|
|
341
729
|
|
|
342
|
-
console.log(
|
|
343
|
-
console.log(` ${publicUrl}/api/whatsapp/webhook`);
|
|
344
|
-
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`);
|
|
345
731
|
}
|
|
346
732
|
});
|
|
347
733
|
|
|
348
734
|
tunnelProcess.stderr.on('data', (data) => {
|
|
349
|
-
|
|
735
|
+
const msg = data.toString().trim();
|
|
736
|
+
if (msg) warn('Tunnel: ' + msg);
|
|
350
737
|
});
|
|
351
738
|
|
|
352
739
|
tunnelProcess.on('close', (code) => {
|
|
353
|
-
console.log(
|
|
740
|
+
console.log(`\n${c.dim}Tunnel closed with code ${code}${c.reset}`);
|
|
354
741
|
});
|
|
355
|
-
|
|
356
742
|
break;
|
|
743
|
+
}
|
|
357
744
|
|
|
358
|
-
|
|
745
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
746
|
+
// ║ 🛑 STOP ║
|
|
747
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
748
|
+
case 'stop': {
|
|
749
|
+
console.log();
|
|
359
750
|
if (isServerRunning()) {
|
|
360
|
-
|
|
751
|
+
const spin = new Spinner('Stopping SamarthyaBot...');
|
|
752
|
+
spin.start();
|
|
361
753
|
try {
|
|
362
754
|
if (process.platform === 'win32') {
|
|
363
|
-
// Find PID of process listening on port 5000 and kill it
|
|
364
755
|
const netstatOut = execSync('netstat -ano | findstr :5000', { encoding: 'utf-8' });
|
|
365
|
-
|
|
366
|
-
for (const line of lines) {
|
|
756
|
+
netstatOut.split('\n').forEach(line => {
|
|
367
757
|
if (line.includes('LISTENING')) {
|
|
368
|
-
const parts = line.trim().split(
|
|
758
|
+
const parts = line.trim().split(/\s+/);
|
|
369
759
|
const pid = parts[parts.length - 1];
|
|
370
|
-
if (pid && pid !== '0') {
|
|
371
|
-
execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
372
|
-
}
|
|
760
|
+
if (pid && pid !== '0') execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
373
761
|
}
|
|
374
|
-
}
|
|
762
|
+
});
|
|
375
763
|
} else {
|
|
376
|
-
// macOS and Linux
|
|
377
764
|
execSync('lsof -t -i:5000 | xargs kill -9 2>/dev/null || fuser -k 5000/tcp 2>/dev/null');
|
|
378
765
|
}
|
|
379
|
-
|
|
380
|
-
} catch
|
|
381
|
-
|
|
766
|
+
spin.stop('Gateway stopped');
|
|
767
|
+
} catch {
|
|
768
|
+
spin.fail('Failed to stop gateway');
|
|
382
769
|
}
|
|
383
770
|
} else {
|
|
384
|
-
|
|
771
|
+
info('Gateway is not running');
|
|
385
772
|
}
|
|
773
|
+
console.log();
|
|
386
774
|
break;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ╔══════════════════════════════════════════════════════════════╗
|
|
778
|
+
// ║ 🔄 RESTART ║
|
|
779
|
+
// ╚══════════════════════════════════════════════════════════════╝
|
|
780
|
+
case 'restart': {
|
|
781
|
+
console.log();
|
|
782
|
+
const spin = new Spinner('Restarting SamarthyaBot...');
|
|
783
|
+
spin.start();
|
|
387
784
|
|
|
388
|
-
case 'restart':
|
|
389
|
-
console.log('🔄 Restarting SamarthyaBot Gateway...');
|
|
390
785
|
if (isServerRunning()) {
|
|
391
786
|
try {
|
|
392
787
|
if (process.platform === 'win32') {
|
|
393
788
|
const netstatOut = execSync('netstat -ano | findstr :5000', { encoding: 'utf-8' });
|
|
394
|
-
|
|
395
|
-
for (const line of lines) {
|
|
789
|
+
netstatOut.split('\n').forEach(line => {
|
|
396
790
|
if (line.includes('LISTENING')) {
|
|
397
|
-
const parts = line.trim().split(
|
|
791
|
+
const parts = line.trim().split(/\s+/);
|
|
398
792
|
const pid = parts[parts.length - 1];
|
|
399
|
-
if (pid && pid !== '0') {
|
|
400
|
-
execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
401
|
-
}
|
|
793
|
+
if (pid && pid !== '0') execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore' });
|
|
402
794
|
}
|
|
403
|
-
}
|
|
795
|
+
});
|
|
404
796
|
} else {
|
|
405
797
|
execSync('lsof -t -i:5000 | xargs kill -9 2>/dev/null || fuser -k 5000/tcp 2>/dev/null');
|
|
406
798
|
}
|
|
407
|
-
} catch
|
|
799
|
+
} catch { }
|
|
408
800
|
}
|
|
409
|
-
|
|
801
|
+
|
|
410
802
|
setTimeout(() => {
|
|
803
|
+
spin.stop('Restarted');
|
|
411
804
|
const restartChild = spawn('node', ['server.js'], {
|
|
412
805
|
cwd: backendDir,
|
|
413
806
|
stdio: 'inherit'
|
|
414
807
|
});
|
|
415
808
|
restartChild.on('close', (code) => {
|
|
416
|
-
console.log(
|
|
809
|
+
console.log(`\n${c.red}Gateway exited with code ${code}${c.reset}`);
|
|
417
810
|
});
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}, 1000);
|
|
811
|
+
info(`Dashboard: ${c.cyan}${c.under}http://localhost:5000${c.reset}\n`);
|
|
812
|
+
}, 1200);
|
|
421
813
|
break;
|
|
814
|
+
}
|
|
422
815
|
|
|
423
816
|
default:
|
|
424
|
-
|
|
425
|
-
|
|
817
|
+
error(`Unknown command: ${c.bold}${command}${c.reset}`);
|
|
818
|
+
info(`Run ${c.saffron}samarthya${c.reset} to see all commands`);
|
|
426
819
|
process.exit(1);
|
|
427
820
|
}
|