@thelapyae/geniclaw 1.1.11 → 1.2.2
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 +53 -77
- package/bin/geniclaw.js +509 -355
- package/dist/config-manager.d.ts +17 -0
- package/dist/config-manager.d.ts.map +1 -1
- package/dist/config-manager.js +14 -2
- package/dist/config-manager.js.map +1 -1
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +131 -0
- package/dist/doctor.js.map +1 -0
- package/dist/providers/anthropic.d.ts +19 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +60 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +110 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/openai.d.ts +21 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +94 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +15 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/queue-processor.js +78 -221
- package/dist/queue-processor.js.map +1 -1
- package/dist/tools/web-search.d.ts +7 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +40 -0
- package/dist/tools/web-search.js.map +1 -0
- package/package.json +1 -1
package/bin/geniclaw.js
CHANGED
|
@@ -1,82 +1,139 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const {
|
|
3
|
-
const path = require('path');
|
|
2
|
+
const { program } = require('commander');
|
|
4
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
-
const {
|
|
6
|
+
const { spawn, execSync, fork } = require('child_process');
|
|
7
7
|
const inquirer = require('inquirer');
|
|
8
|
-
const
|
|
8
|
+
const { ConfigManager } = require('../dist/config-manager');
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// Paths
|
|
11
11
|
const LOCAL_DIR = path.join(process.cwd(), '.geniclaw');
|
|
12
12
|
const GENICLAW_WORK_DIR = process.env.GENICLAW_WORK_DIR || (fs.existsSync(LOCAL_DIR) ? LOCAL_DIR : path.join(os.homedir(), '.geniclaw'));
|
|
13
13
|
const CONFIG_FILE = path.join(GENICLAW_WORK_DIR, 'config.json');
|
|
14
14
|
const SESSIONS_DIR = path.join(GENICLAW_WORK_DIR, 'sessions');
|
|
15
15
|
const SKILLS_DIR = path.join(GENICLAW_WORK_DIR, 'skills');
|
|
16
|
+
const { runDoctor } = require('../dist/doctor');
|
|
16
17
|
|
|
17
18
|
const SCRIPT_PATH = path.join(__dirname, '../geniclaw.sh');
|
|
18
19
|
|
|
19
20
|
// Helper to run shell script commands
|
|
20
21
|
function runScript(args) {
|
|
21
|
-
|
|
22
|
-
stdio: 'inherit'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return new Promise((resolve) => {
|
|
27
|
-
child.on('exit', (code) => {
|
|
28
|
-
resolve(code);
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn(SCRIPT_PATH, args, { stdio: 'inherit' });
|
|
24
|
+
child.on('close', (code) => {
|
|
25
|
+
if (code === 0) resolve();
|
|
26
|
+
else reject(new Error(`Script exited with code ${code}`));
|
|
29
27
|
});
|
|
28
|
+
child.on('error', reject);
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
//
|
|
32
|
+
// Config Helpers
|
|
34
33
|
function loadConfig() {
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return {};
|
|
34
|
+
return ConfigManager.load();
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
// Save Config
|
|
46
37
|
function saveConfig(config) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
ConfigManager.save(config);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Ensure sessions dir exists
|
|
42
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
43
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function promptContinue() {
|
|
47
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
//
|
|
54
|
-
function
|
|
50
|
+
// Skill Management
|
|
51
|
+
function listSkills() {
|
|
55
52
|
if (!fs.existsSync(SKILLS_DIR)) return [];
|
|
56
53
|
return fs.readdirSync(SKILLS_DIR)
|
|
57
54
|
.filter(f => f.endsWith('.json'))
|
|
58
55
|
.map(f => {
|
|
59
|
-
try {
|
|
60
|
-
|
|
56
|
+
try {
|
|
57
|
+
const data = JSON.parse(fs.readFileSync(path.join(SKILLS_DIR, f), 'utf8'));
|
|
58
|
+
return { name: data.name, filename: f, description: data.description };
|
|
59
|
+
} catch (e) { return null; }
|
|
61
60
|
})
|
|
62
61
|
.filter(s => s !== null);
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
async function manageSkills() {
|
|
65
|
+
const config = loadConfig();
|
|
66
|
+
const skills = listSkills();
|
|
67
|
+
const activeSkill = config.activeSkill || 'None';
|
|
68
|
+
|
|
69
|
+
console.clear();
|
|
70
|
+
console.log('🧠 Skill Manager');
|
|
71
|
+
console.log(`Active Skill: ${activeSkill}`);
|
|
72
|
+
console.log('----------------');
|
|
73
|
+
|
|
74
|
+
const { action } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'action',
|
|
78
|
+
message: 'Choose an action:',
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: 'Activate/Deactivate Skill', value: 'activate' },
|
|
81
|
+
{ name: 'Install New Skill (URL)', value: 'install' },
|
|
82
|
+
{ name: 'List Installed Skills', value: 'list' },
|
|
83
|
+
{ name: 'Back', value: 'back' }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
if (action === 'back') return;
|
|
89
|
+
|
|
90
|
+
if (action === 'activate') {
|
|
91
|
+
const choices = [{ name: 'None (Disable Skills)', value: null }, ...skills.map(s => ({ name: `${s.name} - ${s.description}`, value: s.name }))];
|
|
92
|
+
const { skill } = await inquirer.prompt([
|
|
93
|
+
{ type: 'list', name: 'skill', message: 'Select Skill to Activate:', choices }
|
|
94
|
+
]);
|
|
95
|
+
config.activeSkill = skill; // null if None
|
|
96
|
+
saveConfig(config);
|
|
97
|
+
console.log(`✅ Active Skill set to: ${skill || 'None'}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (action === 'install') {
|
|
101
|
+
const { url } = await inquirer.prompt([
|
|
102
|
+
{ type: 'input', name: 'url', message: 'Enter Skill JSON URL:' }
|
|
103
|
+
]);
|
|
104
|
+
try {
|
|
105
|
+
await installSkillFromUrl(url);
|
|
106
|
+
console.log('✅ Skill installed successfully.');
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(`❌ Error installing skill: ${e.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (action === 'list') {
|
|
113
|
+
skills.forEach(s => console.log(`- ${s.name}: ${s.description}`));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await promptContinue();
|
|
117
|
+
return manageSkills();
|
|
118
|
+
}
|
|
119
|
+
|
|
66
120
|
async function installSkillFromUrl(url) {
|
|
121
|
+
// Simple fetch and save
|
|
122
|
+
// In a real app, use the SkillManager class from src/skill-manager.ts if possible,
|
|
123
|
+
// but here we are in JS land (bin).
|
|
124
|
+
// actually we can require the built JS from dist if needed, but let's keep it simple dependency-free if we can or use the one we have.
|
|
125
|
+
// The previous implementation used a fetch.
|
|
67
126
|
return new Promise((resolve, reject) => {
|
|
127
|
+
const https = require('https');
|
|
68
128
|
https.get(url, (res) => {
|
|
69
129
|
let data = '';
|
|
70
130
|
res.on('data', chunk => data += chunk);
|
|
71
131
|
res.on('end', () => {
|
|
72
132
|
try {
|
|
73
133
|
const skill = JSON.parse(data);
|
|
74
|
-
if (!skill.name || !skill.
|
|
75
|
-
reject(new Error("Invalid skill JSON"));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
134
|
+
if (!skill.name || !skill.prompts) throw new Error('Invalid Skill JSON');
|
|
78
135
|
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
79
|
-
const safeName = skill.name.replace(/[^a-
|
|
136
|
+
const safeName = skill.name.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
80
137
|
fs.writeFileSync(path.join(SKILLS_DIR, `${safeName}.json`), JSON.stringify(skill, null, 2));
|
|
81
138
|
resolve(skill);
|
|
82
139
|
} catch(e) { reject(e); }
|
|
@@ -95,27 +152,35 @@ async function showMenu() {
|
|
|
95
152
|
const config = loadConfig();
|
|
96
153
|
const activeSession = config.activeSession || 'default';
|
|
97
154
|
const activeSkill = config.activeSkill || 'None';
|
|
155
|
+
const activeProvider = config.activeProvider || 'gemini';
|
|
156
|
+
|
|
157
|
+
// Check if the ACTIVE provider has a key
|
|
158
|
+
const activeKeyField = `${activeProvider}ApiKey`;
|
|
159
|
+
const hasKey = config[activeKeyField] || process.env[activeKeyField.toUpperCase().replace(/([A-Z])/g, '_$1').toUpperCase()] || (activeProvider === 'gemini' && process.env.GEMINI_API_KEY);
|
|
98
160
|
|
|
99
|
-
if (!
|
|
100
|
-
console.log(
|
|
161
|
+
if (!hasKey) {
|
|
162
|
+
console.log(`⚠️ Configuration Missing: No API Key found for active provider (${activeProvider}).`);
|
|
101
163
|
const { runSetup } = await inquirer.prompt([
|
|
102
|
-
{ type: 'confirm', name: 'runSetup', message: 'Would you like to
|
|
164
|
+
{ type: 'confirm', name: 'runSetup', message: 'Would you like to configure keys now?', default: true }
|
|
103
165
|
]);
|
|
104
166
|
|
|
105
167
|
if (runSetup) {
|
|
106
|
-
|
|
107
|
-
|
|
168
|
+
// If they say yes, go to keys menu directly? Or setup?
|
|
169
|
+
// Local setup script is gemini-focused. Let's send them to manageApiKey menu.
|
|
170
|
+
await manageApiKeys(activeProvider);
|
|
108
171
|
return showMenu();
|
|
109
172
|
}
|
|
110
173
|
}
|
|
111
174
|
|
|
112
175
|
// Show current status summary
|
|
113
176
|
try {
|
|
114
|
-
console.log(`
|
|
115
|
-
console.log(`Active Session:
|
|
116
|
-
console.log(`Active Skill:
|
|
117
|
-
if (config.
|
|
118
|
-
|
|
177
|
+
console.log(`Active Provider: ${config.activeProvider || 'gemini'} (${config.activeProvider === 'gemini' ? config.geminiModel : (config[config.activeProvider + 'Model'] || 'Default')})`);
|
|
178
|
+
console.log(`Active Session: 📂 ${activeSession}`);
|
|
179
|
+
console.log(`Active Skill: 🧠 ${activeSkill}`);
|
|
180
|
+
if (config.providerRouting && (config.providerRouting.chat !== config.activeProvider)) {
|
|
181
|
+
console.log(`Routing: 🔀 Custom`);
|
|
182
|
+
}
|
|
183
|
+
if (config.geminiApiUrl) console.log(`API URL: 🔌 ${config.geminiApiUrl}`);
|
|
119
184
|
} catch (e) {}
|
|
120
185
|
console.log('');
|
|
121
186
|
|
|
@@ -128,16 +193,22 @@ async function showMenu() {
|
|
|
128
193
|
{ name: 'Start Daemon', value: 'start' },
|
|
129
194
|
{ name: 'Stop Daemon', value: 'stop' },
|
|
130
195
|
{ name: 'Restart Daemon', value: 'restart' },
|
|
131
|
-
{ name: 'Check Status', value: 'status' },
|
|
132
196
|
{ name: 'View Logs', value: 'logs' },
|
|
133
197
|
new inquirer.Separator(),
|
|
134
|
-
{ name: '
|
|
198
|
+
{ name: '🤖 Select AI Provider & Model', value: 'provider' },
|
|
199
|
+
{ name: '🔑 Manage API Keys', value: 'keys' },
|
|
200
|
+
{ name: '🔀 Task Routing (Chat/Heartbeat/Cron)', value: 'routing' },
|
|
201
|
+
{ name: '🔎 Web Search Config', value: 'search' },
|
|
135
202
|
{ name: '🧠 Manage Skills', value: 'skills' },
|
|
136
|
-
{ name: '
|
|
137
|
-
{ name: '
|
|
203
|
+
{ name: '📂 Switch Session', value: 'session' },
|
|
204
|
+
{ name: '➕ Create New Session', value: 'new_session' },
|
|
205
|
+
{ name: '🗑️ Delete Session', value: 'delete_session' },
|
|
206
|
+
new inquirer.Separator(),
|
|
207
|
+
{ name: '🔌 Set Custom API URL', value: 'proxy' },
|
|
138
208
|
{ name: '🔑 Set Gateway/Proxy Key', value: 'gatewayKey' },
|
|
139
209
|
new inquirer.Separator(),
|
|
140
210
|
{ name: '✨ Update GeniClaw', value: 'update' },
|
|
211
|
+
{ name: '🚑 Run Doctor (Fix Issues)', value: 'doctor' },
|
|
141
212
|
{ name: 'Run Setup Wizard', value: 'setup' },
|
|
142
213
|
{ name: 'Exit', value: 'exit' }
|
|
143
214
|
]
|
|
@@ -145,13 +216,20 @@ async function showMenu() {
|
|
|
145
216
|
]);
|
|
146
217
|
|
|
147
218
|
if (action === 'exit') {
|
|
219
|
+
console.log('Bye! 👋');
|
|
148
220
|
process.exit(0);
|
|
149
221
|
}
|
|
150
|
-
|
|
222
|
+
|
|
151
223
|
if (action === 'update') {
|
|
152
224
|
await performUpdate();
|
|
153
225
|
return; // Exit after update to allow restart
|
|
154
226
|
}
|
|
227
|
+
|
|
228
|
+
if (action === 'doctor') {
|
|
229
|
+
await runDoctor();
|
|
230
|
+
await promptContinue();
|
|
231
|
+
return showMenu();
|
|
232
|
+
}
|
|
155
233
|
|
|
156
234
|
if (action === 'setup') {
|
|
157
235
|
await runScript(['setup']);
|
|
@@ -159,125 +237,111 @@ async function showMenu() {
|
|
|
159
237
|
return showMenu();
|
|
160
238
|
}
|
|
161
239
|
|
|
162
|
-
if (action === '
|
|
163
|
-
await
|
|
240
|
+
if (action === 'start' || action === 'stop' || action === 'restart') {
|
|
241
|
+
await runScript([action]);
|
|
164
242
|
await promptContinue();
|
|
165
243
|
return showMenu();
|
|
166
244
|
}
|
|
167
245
|
|
|
168
|
-
if (action === '
|
|
169
|
-
await
|
|
246
|
+
if (action === 'logs') {
|
|
247
|
+
await runScript(['logs']);
|
|
170
248
|
await promptContinue();
|
|
171
249
|
return showMenu();
|
|
172
250
|
}
|
|
173
251
|
|
|
174
|
-
if (action === '
|
|
175
|
-
await
|
|
176
|
-
await promptContinue();
|
|
252
|
+
if (action === 'session') {
|
|
253
|
+
await switchSession();
|
|
177
254
|
return showMenu();
|
|
178
255
|
}
|
|
179
256
|
|
|
180
|
-
if (action === '
|
|
181
|
-
await
|
|
257
|
+
if (action === 'new_session') {
|
|
258
|
+
await createSession();
|
|
182
259
|
return showMenu();
|
|
183
260
|
}
|
|
184
261
|
|
|
262
|
+
if (action === 'delete_session') {
|
|
263
|
+
await deleteSession();
|
|
264
|
+
return showMenu();
|
|
265
|
+
}
|
|
266
|
+
|
|
185
267
|
if (action === 'skills') {
|
|
186
268
|
await manageSkills();
|
|
187
269
|
return showMenu();
|
|
188
270
|
}
|
|
189
271
|
|
|
190
|
-
if (action === '
|
|
191
|
-
await
|
|
272
|
+
if (action === 'provider') {
|
|
273
|
+
await selectProviderAndModel();
|
|
192
274
|
return showMenu();
|
|
193
275
|
}
|
|
194
276
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
277
|
+
if (action === 'keys') {
|
|
278
|
+
await manageApiKeys();
|
|
279
|
+
return showMenu();
|
|
280
|
+
}
|
|
200
281
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
name: 'continue',
|
|
206
|
-
message: 'Press Enter to continue...',
|
|
207
|
-
}
|
|
208
|
-
]);
|
|
209
|
-
}
|
|
282
|
+
if (action === 'routing') {
|
|
283
|
+
await manageTaskRouting();
|
|
284
|
+
return showMenu();
|
|
285
|
+
}
|
|
210
286
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
if (action === 'search') {
|
|
288
|
+
await configureWebSearch();
|
|
289
|
+
return showMenu();
|
|
290
|
+
}
|
|
214
291
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
message: 'Select Gemini Model:',
|
|
220
|
-
default: currentModel,
|
|
221
|
-
choices: [
|
|
222
|
-
'gemini-2.5-flash',
|
|
223
|
-
'gemini-flash-lite-latest',
|
|
224
|
-
'gemini-2.5-pro',
|
|
225
|
-
'gemini-3-flash-preview',
|
|
226
|
-
'gemini-3-pro-preview',
|
|
227
|
-
new inquirer.Separator(),
|
|
228
|
-
{ name: 'Custom (Enter manually)', value: 'custom' }
|
|
229
|
-
]
|
|
230
|
-
}
|
|
231
|
-
]);
|
|
292
|
+
if (action === 'proxy') {
|
|
293
|
+
await setCustomUrl();
|
|
294
|
+
return showMenu();
|
|
295
|
+
}
|
|
232
296
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
{
|
|
237
|
-
type: 'input',
|
|
238
|
-
name: 'customModel',
|
|
239
|
-
message: 'Enter model name:',
|
|
240
|
-
validate: input => input.length > 0
|
|
241
|
-
}
|
|
242
|
-
]);
|
|
243
|
-
selectedModel = customModel;
|
|
297
|
+
if (action === 'gatewayKey') {
|
|
298
|
+
await setGatewayKey();
|
|
299
|
+
return showMenu();
|
|
244
300
|
}
|
|
301
|
+
}
|
|
245
302
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
303
|
+
async function performUpdate() {
|
|
304
|
+
console.log('Checking for updates...');
|
|
305
|
+
try {
|
|
306
|
+
const updateCmd = spawn('npm', ['install', '-g', '@thelapyae/geniclaw@latest'], { stdio: 'inherit' });
|
|
307
|
+
updateCmd.on('close', (code) => {
|
|
308
|
+
if (code === 0) {
|
|
309
|
+
console.log('✅ Update successful! Please restart GeniClaw.');
|
|
310
|
+
process.exit(0);
|
|
311
|
+
} else {
|
|
312
|
+
console.log('❌ Update failed.');
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.log('❌ Error updating: ' + e.message);
|
|
317
|
+
}
|
|
250
318
|
}
|
|
251
319
|
|
|
252
|
-
async function
|
|
320
|
+
async function setCustomUrl() {
|
|
253
321
|
const config = loadConfig();
|
|
254
322
|
const currentUrl = config.geminiApiUrl || 'Default (Google)';
|
|
255
|
-
|
|
323
|
+
|
|
256
324
|
const { url } = await inquirer.prompt([
|
|
257
325
|
{
|
|
258
326
|
type: 'input',
|
|
259
327
|
name: 'url',
|
|
260
328
|
message: `Enter Custom API Base URL (Current: ${currentUrl}):`,
|
|
261
|
-
suffix: '\n(Leave empty to reset to default
|
|
329
|
+
suffix: '\n(Leave empty to reset to default)',
|
|
262
330
|
}
|
|
263
331
|
]);
|
|
264
332
|
|
|
265
|
-
if (
|
|
266
|
-
delete config.geminiApiUrl;
|
|
267
|
-
console.log('✅ Reset to default Google API URL.');
|
|
268
|
-
} else {
|
|
269
|
-
// Basic validation
|
|
270
|
-
if (!url.startsWith('http')) {
|
|
271
|
-
console.log('⚠️ URL should start with http:// or https://');
|
|
272
|
-
}
|
|
333
|
+
if (url && url.trim() !== '') {
|
|
273
334
|
config.geminiApiUrl = url.trim();
|
|
274
|
-
console.log(`✅ API URL
|
|
335
|
+
console.log(`✅ API URL set to: ${url}`);
|
|
336
|
+
} else {
|
|
337
|
+
delete config.geminiApiUrl;
|
|
338
|
+
console.log('✅ API URL reset to default.');
|
|
275
339
|
}
|
|
276
340
|
saveConfig(config);
|
|
277
|
-
|
|
341
|
+
await promptContinue();
|
|
278
342
|
}
|
|
279
343
|
|
|
280
|
-
async function
|
|
344
|
+
async function setGatewayKey() {
|
|
281
345
|
const config = loadConfig();
|
|
282
346
|
const currentKey = config.geminiGatewayKey ? '********' : 'Not Set';
|
|
283
347
|
|
|
@@ -285,308 +349,398 @@ async function changeGatewayKey() {
|
|
|
285
349
|
{
|
|
286
350
|
type: 'password',
|
|
287
351
|
name: 'key',
|
|
288
|
-
message: `Enter Gateway
|
|
289
|
-
suffix: '\n(Leave empty to remove
|
|
352
|
+
message: `Enter Gateway/Proxy Auth Key (Current: ${currentKey}):`,
|
|
353
|
+
suffix: '\n(Leave empty to remove/keep)',
|
|
290
354
|
mask: '*'
|
|
291
355
|
}
|
|
292
356
|
]);
|
|
293
357
|
|
|
294
|
-
if (
|
|
295
|
-
delete config.geminiGatewayKey;
|
|
296
|
-
console.log('✅ Gateway Key removed.');
|
|
297
|
-
} else {
|
|
358
|
+
if (key && key.trim() !== '') {
|
|
298
359
|
config.geminiGatewayKey = key.trim();
|
|
360
|
+
saveConfig(config);
|
|
299
361
|
console.log('✅ Gateway Key updated.');
|
|
362
|
+
} else if (key === '') {
|
|
363
|
+
// Ask if remove? Na, empty usually means keep or skip.
|
|
364
|
+
// Let's add specific logic: if empty, do nothing.
|
|
365
|
+
// If they want to clear, they might need a clear option.
|
|
366
|
+
// For CLI simplicity, let's keep it as is.
|
|
367
|
+
console.log('No change.');
|
|
300
368
|
}
|
|
301
|
-
|
|
302
|
-
console.log('Note: Restart daemon to apply changes.');
|
|
369
|
+
await promptContinue();
|
|
303
370
|
}
|
|
304
371
|
|
|
305
|
-
async function
|
|
372
|
+
async function selectProviderAndModel() {
|
|
306
373
|
const config = loadConfig();
|
|
307
|
-
|
|
308
|
-
const sessions = fs.existsSync(SESSIONS_DIR)
|
|
309
|
-
? fs.readdirSync(SESSIONS_DIR).filter(file => fs.statSync(path.join(SESSIONS_DIR, file)).isDirectory())
|
|
310
|
-
: ['default'];
|
|
374
|
+
let provider = config.activeProvider || 'gemini';
|
|
311
375
|
|
|
312
|
-
|
|
376
|
+
// 1. Select Provider
|
|
377
|
+
const { newProvider } = await inquirer.prompt([
|
|
313
378
|
{
|
|
314
379
|
type: 'list',
|
|
315
|
-
name: '
|
|
316
|
-
message:
|
|
380
|
+
name: 'newProvider',
|
|
381
|
+
message: 'Select Active AI Provider:',
|
|
382
|
+
default: provider,
|
|
317
383
|
choices: [
|
|
318
|
-
{ name: '
|
|
319
|
-
{ name: '
|
|
320
|
-
{ name: '
|
|
384
|
+
{ name: 'Google Gemini', value: 'gemini' },
|
|
385
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
386
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
387
|
+
{ name: 'Groq', value: 'groq' },
|
|
388
|
+
{ name: 'Deepseek', value: 'deepseek' },
|
|
389
|
+
{ name: 'xAI (Grok)', value: 'grok' },
|
|
321
390
|
{ name: 'Back', value: 'back' }
|
|
322
391
|
]
|
|
323
392
|
}
|
|
324
393
|
]);
|
|
325
394
|
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
395
|
+
if (newProvider === 'back') return;
|
|
396
|
+
config.activeProvider = newProvider;
|
|
397
|
+
saveConfig(config);
|
|
398
|
+
console.log(`✅ Active Provider set to: ${newProvider}`);
|
|
399
|
+
|
|
400
|
+
// 2. Select Model for that Provider
|
|
401
|
+
if (newProvider === 'gemini') {
|
|
402
|
+
const { model } = await inquirer.prompt([
|
|
330
403
|
{
|
|
331
404
|
type: 'list',
|
|
332
|
-
name: '
|
|
333
|
-
message: 'Select
|
|
334
|
-
|
|
405
|
+
name: 'model',
|
|
406
|
+
message: 'Select Gemini Model:',
|
|
407
|
+
default: config.geminiModel,
|
|
408
|
+
choices: [
|
|
409
|
+
{ name: 'Gemini 3 Pro Preview', value: 'gemini-3-pro-preview' },
|
|
410
|
+
{ name: 'Gemini 3 Flash Preview', value: 'gemini-3-flash-preview' },
|
|
411
|
+
{ name: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro' },
|
|
412
|
+
{ name: 'Gemini 2.5 Flash', value: 'gemini-2.5-flash' },
|
|
413
|
+
{ name: 'Gemini 2.5 Flash Lite', value: 'gemini-2.5-flash-lite' }
|
|
414
|
+
]
|
|
335
415
|
}
|
|
336
416
|
]);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
saveConfig(config);
|
|
341
|
-
console.log(`✅ Switched to session: ${session}`);
|
|
342
|
-
} else {
|
|
343
|
-
console.log('Already on this session.');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (action === 'create') {
|
|
348
|
-
const { name } = await inquirer.prompt([
|
|
417
|
+
config.geminiModel = model;
|
|
418
|
+
} else if (newProvider === 'openai') {
|
|
419
|
+
const { model } = await inquirer.prompt([
|
|
349
420
|
{
|
|
350
|
-
type: '
|
|
351
|
-
name: '
|
|
352
|
-
message: '
|
|
353
|
-
|
|
421
|
+
type: 'list',
|
|
422
|
+
name: 'model',
|
|
423
|
+
message: 'Select OpenAI Model:',
|
|
424
|
+
default: config.openaiModel,
|
|
425
|
+
choices: [
|
|
426
|
+
{ name: 'GPT-5.3 Codex (Best for Code)', value: 'gpt-5.3-codex' },
|
|
427
|
+
{ name: 'GPT-5.2 Pro', value: 'gpt-5.2-pro' },
|
|
428
|
+
{ name: 'GPT-5.2', value: 'gpt-5.2' },
|
|
429
|
+
{ name: 'GPT-4.1 Nano', value: 'gpt-4.1-nano' },
|
|
430
|
+
{ name: 'OpenAI o3-mini', value: 'o3-mini' }
|
|
431
|
+
]
|
|
354
432
|
}
|
|
355
433
|
]);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
434
|
+
config.openaiModel = model;
|
|
435
|
+
} else if (newProvider === 'anthropic') {
|
|
436
|
+
const { model } = await inquirer.prompt([
|
|
437
|
+
{
|
|
438
|
+
type: 'list',
|
|
439
|
+
name: 'model',
|
|
440
|
+
message: 'Select Claude Model:',
|
|
441
|
+
default: config.anthropicModel,
|
|
442
|
+
choices: [
|
|
443
|
+
{ name: 'Claude Opus 4.6', value: 'claude-opus-4.6' },
|
|
444
|
+
{ name: 'Claude Sonnet 4.5', value: 'claude-sonnet-4.5' },
|
|
445
|
+
{ name: 'Claude Haiku 4.5', value: 'claude-haiku-4.5' }
|
|
446
|
+
]
|
|
369
447
|
}
|
|
370
|
-
|
|
448
|
+
]);
|
|
449
|
+
config.anthropicModel = model;
|
|
450
|
+
} else if (newProvider === 'groq') {
|
|
451
|
+
const { model } = await inquirer.prompt([
|
|
452
|
+
{
|
|
453
|
+
type: 'list',
|
|
454
|
+
name: 'model',
|
|
455
|
+
message: 'Select Groq Model:',
|
|
456
|
+
default: config.groqModel,
|
|
457
|
+
choices: [
|
|
458
|
+
{ name: 'Llama 3.3 70B Versatile', value: 'llama-3.3-70b-versatile' },
|
|
459
|
+
{ name: 'Llama 3.1 8B Instant', value: 'llama-3.1-8b-instant' },
|
|
460
|
+
{ name: 'OpenAI GPT-OSS 120B', value: 'openai/gpt-oss-120b' },
|
|
461
|
+
{ name: 'Mixtral 8x7B', value: 'mixtral-8x7b-32768' }
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
]);
|
|
465
|
+
config.groqModel = model;
|
|
466
|
+
} else if (newProvider === 'deepseek') {
|
|
467
|
+
const { model } = await inquirer.prompt([
|
|
468
|
+
{
|
|
469
|
+
type: 'list',
|
|
470
|
+
name: 'model',
|
|
471
|
+
message: 'Select DeepSeek Model:',
|
|
472
|
+
default: config.deepseekModel,
|
|
473
|
+
choices: [
|
|
474
|
+
{ name: 'DeepSeek V3 (General)', value: 'deepseek-chat' },
|
|
475
|
+
{ name: 'DeepSeek R1 (Reasoning)', value: 'deepseek-reasoner' },
|
|
476
|
+
{ name: 'DeepSeek Coder V3', value: 'deepseek-coder' }
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
]);
|
|
480
|
+
config.deepseekModel = model;
|
|
481
|
+
} else if (newProvider === 'grok') {
|
|
482
|
+
const { model } = await inquirer.prompt([
|
|
483
|
+
{
|
|
484
|
+
type: 'list',
|
|
485
|
+
name: 'model',
|
|
486
|
+
message: 'Select Grok Model:',
|
|
487
|
+
default: config.grokModel,
|
|
488
|
+
choices: [
|
|
489
|
+
{ name: 'Grok 4', value: 'grok-4' },
|
|
490
|
+
{ name: 'Grok 4.1 Fast', value: 'grok-4-1-fast' },
|
|
491
|
+
{ name: 'Grok 3', value: 'grok-3' },
|
|
492
|
+
{ name: 'Grok 2', value: 'grok-2-1212' }
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
]);
|
|
496
|
+
config.grokModel = model;
|
|
371
497
|
}
|
|
372
498
|
|
|
373
|
-
|
|
374
|
-
|
|
499
|
+
saveConfig(config);
|
|
500
|
+
console.log(`✅ Model set to: ${config[newProvider + 'Model'] || config.geminiModel}`);
|
|
501
|
+
await promptContinue();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
async function manageApiKeys(targetProvider = null) {
|
|
506
|
+
const config = loadConfig();
|
|
507
|
+
let provider = targetProvider;
|
|
508
|
+
|
|
509
|
+
if (!provider) {
|
|
510
|
+
const result = await inquirer.prompt([
|
|
375
511
|
{
|
|
376
512
|
type: 'list',
|
|
377
|
-
name: '
|
|
378
|
-
message: 'Select
|
|
379
|
-
choices:
|
|
513
|
+
name: 'provider',
|
|
514
|
+
message: 'Select Provider to set API Key:',
|
|
515
|
+
choices: [
|
|
516
|
+
{ name: 'Google Gemini', value: 'gemini' },
|
|
517
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
518
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
519
|
+
{ name: 'Groq', value: 'groq' },
|
|
520
|
+
{ name: 'Deepseek', value: 'deepseek' },
|
|
521
|
+
{ name: 'xAI (Grok)', value: 'grok' },
|
|
522
|
+
{ name: 'Brave Search', value: 'brave' },
|
|
523
|
+
{ name: 'Back', value: 'back' }
|
|
524
|
+
]
|
|
380
525
|
}
|
|
381
526
|
]);
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
527
|
+
if (result.provider === 'back') return;
|
|
528
|
+
provider = result.provider;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const keyField = `${provider}ApiKey`;
|
|
532
|
+
const currentKey = config[keyField] ? '********' : 'Not Set';
|
|
533
|
+
|
|
534
|
+
const { key } = await inquirer.prompt([
|
|
535
|
+
{
|
|
536
|
+
type: 'password',
|
|
537
|
+
name: 'key',
|
|
538
|
+
message: `Enter API Key for ${provider} (Current: ${currentKey}):`,
|
|
539
|
+
suffix: '\n(Leave empty to keep current)',
|
|
540
|
+
mask: '*'
|
|
390
541
|
}
|
|
542
|
+
]);
|
|
543
|
+
|
|
544
|
+
if (key && key.trim() !== '') {
|
|
545
|
+
config[keyField] = key.trim();
|
|
546
|
+
saveConfig(config);
|
|
547
|
+
console.log(`✅ API Key for ${provider} updated.`);
|
|
548
|
+
} else {
|
|
549
|
+
console.log('No change.');
|
|
391
550
|
}
|
|
392
|
-
|
|
551
|
+
|
|
552
|
+
if (!targetProvider) await promptContinue();
|
|
393
553
|
}
|
|
394
554
|
|
|
395
|
-
async function
|
|
555
|
+
async function manageTaskRouting() {
|
|
396
556
|
const config = loadConfig();
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const { action } = await inquirer.prompt([
|
|
557
|
+
const routing = config.providerRouting || { chat: 'gemini', heartbeat: 'gemini', cron: 'gemini' };
|
|
558
|
+
|
|
559
|
+
const { task } = await inquirer.prompt([
|
|
401
560
|
{
|
|
402
561
|
type: 'list',
|
|
403
|
-
name: '
|
|
404
|
-
message:
|
|
562
|
+
name: 'task',
|
|
563
|
+
message: 'Select Task to Route:',
|
|
405
564
|
choices: [
|
|
406
|
-
{ name:
|
|
407
|
-
{ name:
|
|
408
|
-
{ name:
|
|
409
|
-
{ name: 'List Installed Skills', value: 'list' },
|
|
410
|
-
{ name: 'Delete Skill', value: 'delete' },
|
|
565
|
+
{ name: `Chat (Current: ${routing.chat || 'default'})`, value: 'chat' },
|
|
566
|
+
{ name: `Heartbeat (Current: ${routing.heartbeat || 'default'})`, value: 'heartbeat' },
|
|
567
|
+
{ name: `Cron Jobs (Current: ${routing.cron || 'default'})`, value: 'cron' },
|
|
411
568
|
{ name: 'Back', value: 'back' }
|
|
412
569
|
]
|
|
413
570
|
}
|
|
414
571
|
]);
|
|
415
572
|
|
|
416
|
-
if (
|
|
573
|
+
if (task === 'back') return;
|
|
417
574
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
name: '
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
575
|
+
const { provider } = await inquirer.prompt([
|
|
576
|
+
{
|
|
577
|
+
type: 'list',
|
|
578
|
+
name: 'provider',
|
|
579
|
+
message: `Select Provider for ${task}:`,
|
|
580
|
+
choices: [
|
|
581
|
+
{ name: 'Use Active Provider (Default)', value: null },
|
|
582
|
+
new inquirer.Separator(),
|
|
583
|
+
{ name: 'Google Gemini', value: 'gemini' },
|
|
584
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
585
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
586
|
+
{ name: 'Groq', value: 'groq' },
|
|
587
|
+
{ name: 'Deepseek', value: 'deepseek' },
|
|
588
|
+
{ name: 'xAI (Grok)', value: 'grok' }
|
|
589
|
+
]
|
|
590
|
+
}
|
|
591
|
+
]);
|
|
433
592
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
593
|
+
if (provider) {
|
|
594
|
+
routing[task] = provider;
|
|
595
|
+
} else {
|
|
596
|
+
delete routing[task];
|
|
437
597
|
}
|
|
598
|
+
|
|
599
|
+
config.providerRouting = routing;
|
|
600
|
+
saveConfig(config);
|
|
601
|
+
console.log(`✅ Routing updated.`);
|
|
602
|
+
|
|
603
|
+
return manageTaskRouting();
|
|
604
|
+
}
|
|
438
605
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
} catch(e) {
|
|
455
|
-
console.log(`❌ Failed: ${e.message}`);
|
|
606
|
+
async function configureWebSearch() {
|
|
607
|
+
const config = loadConfig();
|
|
608
|
+
const currentKey = config.braveApiKey ? '********' : 'Not Set';
|
|
609
|
+
|
|
610
|
+
console.log('configured Web Search uses Brave Search API.');
|
|
611
|
+
console.log('Get a free key at: https://brave.com/search/api/');
|
|
612
|
+
|
|
613
|
+
const { key } = await inquirer.prompt([
|
|
614
|
+
{
|
|
615
|
+
type: 'password',
|
|
616
|
+
name: 'key',
|
|
617
|
+
message: `Enter Brave Search API Key (Current: ${currentKey}):`,
|
|
618
|
+
suffix: '\n(Leave empty to remove/keep)',
|
|
619
|
+
mask: '*'
|
|
456
620
|
}
|
|
621
|
+
]);
|
|
622
|
+
|
|
623
|
+
if (key && key.trim() !== '') {
|
|
624
|
+
config.braveApiKey = key.trim();
|
|
625
|
+
saveConfig(config);
|
|
626
|
+
console.log('✅ Brave API Key set.');
|
|
627
|
+
} else if (key === '') {
|
|
628
|
+
// do nothing
|
|
629
|
+
console.log('No change.');
|
|
630
|
+
} else {
|
|
631
|
+
// e.g. space to clear? For now TUI doesn't support clear easily.
|
|
457
632
|
}
|
|
633
|
+
await promptContinue();
|
|
634
|
+
}
|
|
458
635
|
|
|
459
|
-
if (action === 'create') {
|
|
460
|
-
// Simple creation wizard
|
|
461
|
-
const answers = await inquirer.prompt([
|
|
462
|
-
{ type: 'input', name: 'name', message: 'Skill Name:', validate: i => /^[a-z0-9_-]+$/.test(i) },
|
|
463
|
-
{ type: 'input', name: 'description', message: 'Description:' },
|
|
464
|
-
{ type: 'editor', name: 'instruction', message: 'System Instruction (Persona):' }
|
|
465
|
-
]);
|
|
466
|
-
|
|
467
|
-
const skill = {
|
|
468
|
-
name: answers.name,
|
|
469
|
-
version: "1.0.0",
|
|
470
|
-
description: answers.description,
|
|
471
|
-
author: "local",
|
|
472
|
-
systemInstruction: answers.instruction,
|
|
473
|
-
examples: []
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
if (!fs.existsSync(SKILLS_DIR)) fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
477
|
-
fs.writeFileSync(path.join(SKILLS_DIR, `${skill.name}.json`), JSON.stringify(skill, null, 2));
|
|
478
|
-
console.log(`✅ Skill '${skill.name}' created.`);
|
|
479
|
-
}
|
|
480
636
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
637
|
+
// --- Main Logic ---
|
|
638
|
+
|
|
639
|
+
function switchSession() {
|
|
640
|
+
const sessions = fs.readdirSync(SESSIONS_DIR).filter(f => fs.statSync(path.join(SESSIONS_DIR, f)).isDirectory());
|
|
641
|
+
const config = loadConfig();
|
|
642
|
+
const activeSession = config.activeSession || 'default';
|
|
643
|
+
|
|
644
|
+
return inquirer.prompt([
|
|
645
|
+
{
|
|
646
|
+
type: 'list',
|
|
647
|
+
name: 'session',
|
|
648
|
+
message: 'Select Session:',
|
|
649
|
+
choices: sessions.map(s => ({ name: s === activeSession ? `${s} (Active)` : s, value: s })).concat([{ name: 'Back', value: 'back' }])
|
|
650
|
+
}
|
|
651
|
+
]).then(answer => {
|
|
652
|
+
if (answer.session === 'back') return;
|
|
653
|
+
config.activeSession = answer.session;
|
|
654
|
+
saveConfig(config);
|
|
655
|
+
console.log(`Switched to session: ${answer.session}`);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function createSession() {
|
|
660
|
+
const config = loadConfig();
|
|
661
|
+
const { name } = await inquirer.prompt([
|
|
662
|
+
{
|
|
663
|
+
type: 'input',
|
|
664
|
+
name: 'name',
|
|
665
|
+
message: 'Enter new session name (alphanumeric):',
|
|
666
|
+
validate: input => /^[a-zA-Z0-9_-]+$/.test(input) ? true : 'Invalid name.'
|
|
667
|
+
}
|
|
668
|
+
]);
|
|
669
|
+
|
|
670
|
+
const sessionPath = path.join(SESSIONS_DIR, name);
|
|
671
|
+
if (fs.existsSync(sessionPath)) {
|
|
672
|
+
console.log('❌ Session already exists.');
|
|
673
|
+
} else {
|
|
674
|
+
fs.mkdirSync(sessionPath, { recursive: true });
|
|
675
|
+
console.log(`✅ Session '${name}' created.`);
|
|
676
|
+
const { switchTo } = await inquirer.prompt([
|
|
677
|
+
{ type: 'confirm', name: 'switchTo', message: 'Switch to this session now?', default: true }
|
|
497
678
|
]);
|
|
498
|
-
if (
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
]);
|
|
502
|
-
if (confirm) {
|
|
503
|
-
fs.unlinkSync(path.join(SKILLS_DIR, `${skill}.json`));
|
|
504
|
-
if (config.activeSkill === skill) {
|
|
505
|
-
delete config.activeSkill;
|
|
506
|
-
saveConfig(config);
|
|
507
|
-
}
|
|
508
|
-
console.log(`🗑️ Skill deleted.`);
|
|
509
|
-
}
|
|
679
|
+
if (switchTo) {
|
|
680
|
+
config.activeSession = name;
|
|
681
|
+
saveConfig(config);
|
|
510
682
|
}
|
|
511
683
|
}
|
|
512
|
-
|
|
513
|
-
return manageSkills();
|
|
514
684
|
}
|
|
515
685
|
|
|
516
|
-
async function
|
|
517
|
-
|
|
686
|
+
async function deleteSession() {
|
|
687
|
+
const config = loadConfig();
|
|
688
|
+
const activeSession = config.activeSession || 'default';
|
|
689
|
+
const sessions = fs.readdirSync(SESSIONS_DIR).filter(f => fs.statSync(path.join(SESSIONS_DIR, f)).isDirectory());
|
|
690
|
+
|
|
691
|
+
const { session } = await inquirer.prompt([
|
|
518
692
|
{
|
|
519
693
|
type: 'list',
|
|
520
|
-
name: '
|
|
521
|
-
message: '
|
|
522
|
-
choices: [
|
|
523
|
-
{ name: 'Telegram', value: 'telegram' },
|
|
524
|
-
{ name: 'Queue Processor', value: 'queue' },
|
|
525
|
-
{ name: 'Daemon', value: 'daemon' },
|
|
526
|
-
{ name: 'Back', value: 'back' }
|
|
527
|
-
]
|
|
694
|
+
name: 'session',
|
|
695
|
+
message: 'Select session to delete:',
|
|
696
|
+
choices: sessions.filter(s => s !== 'default' && s !== activeSession).concat([{ name: 'Cancel', value: 'cancel' }])
|
|
528
697
|
}
|
|
529
698
|
]);
|
|
530
699
|
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
console.log('Press Ctrl+C to exit logs.');
|
|
534
|
-
await runScript(['logs', logType]);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
async function performUpdate() {
|
|
538
|
-
console.log('🔄 Checking for updates and installing latest version...');
|
|
539
|
-
console.log('📦 Running: npm install -g @thelapyae/geniclaw@latest');
|
|
700
|
+
if (session === 'cancel') return;
|
|
540
701
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
console.log(
|
|
549
|
-
|
|
550
|
-
const { trySudo } = await inquirer.prompt([
|
|
551
|
-
{ type: 'confirm', name: 'trySudo', message: 'Try again with sudo?', default: false }
|
|
552
|
-
]);
|
|
553
|
-
|
|
554
|
-
if (trySudo) {
|
|
555
|
-
try {
|
|
556
|
-
execSync('sudo npm install -g @thelapyae/geniclaw@latest', { stdio: 'inherit' });
|
|
557
|
-
console.log('\n✅ Update completed! Please restart geniclaw.');
|
|
558
|
-
process.exit(0);
|
|
559
|
-
} catch (e) {
|
|
560
|
-
console.error('\n❌ Sudo update also failed.');
|
|
561
|
-
}
|
|
562
|
-
}
|
|
702
|
+
// confirm
|
|
703
|
+
const { confirm } = await inquirer.prompt([
|
|
704
|
+
{ type: 'confirm', name: 'confirm', message: `Are you sure you want to delete '${session}' and all its history?`, default: false }
|
|
705
|
+
]);
|
|
706
|
+
|
|
707
|
+
if (confirm) {
|
|
708
|
+
fs.rmSync(path.join(SESSIONS_DIR, session), { recursive: true, force: true });
|
|
709
|
+
console.log(`🗑️ Session '${session}' deleted.`);
|
|
563
710
|
}
|
|
564
711
|
}
|
|
565
712
|
|
|
566
713
|
|
|
567
|
-
// CLI
|
|
568
|
-
const packageJson = require('../package.json');
|
|
714
|
+
// CLI Args Handling
|
|
569
715
|
program
|
|
570
|
-
.version(
|
|
571
|
-
.
|
|
572
|
-
.
|
|
573
|
-
.action(async (command) => {
|
|
716
|
+
.version(require('../package.json').version)
|
|
717
|
+
.argument('[command]', 'Command to run (start, stop, logs, etc)')
|
|
718
|
+
.argument('[args...]', 'Arguments for the command')
|
|
719
|
+
.action(async (command, args) => {
|
|
574
720
|
if (!command) {
|
|
575
|
-
// No args -> TUI Mode
|
|
576
721
|
await showMenu();
|
|
577
722
|
} else {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
723
|
+
if (command === 'provider') {
|
|
724
|
+
await selectProviderAndModel();
|
|
725
|
+
} else if (command === 'keys') {
|
|
726
|
+
await manageApiKeys();
|
|
727
|
+
} else if (command === 'routing') {
|
|
728
|
+
await manageTaskRouting();
|
|
729
|
+
} else if (command === 'search') {
|
|
730
|
+
await configureWebSearch();
|
|
731
|
+
} else if (command === 'services') {
|
|
732
|
+
// Not implemented directly yet, maybe mapping to start/stop?
|
|
733
|
+
// Just fall through to script for now
|
|
734
|
+
await runScript(args);
|
|
735
|
+
} else if (command === 'doctor') {
|
|
736
|
+
await runDoctor();
|
|
737
|
+
} else if (command === 'update') {
|
|
738
|
+
await performUpdate();
|
|
739
|
+
} else {
|
|
740
|
+
// Pass through to shell script
|
|
741
|
+
const allArgs = [command, ...args];
|
|
742
|
+
await runScript(allArgs);
|
|
743
|
+
}
|
|
590
744
|
}
|
|
591
745
|
});
|
|
592
746
|
|