@thelapyae/geniclaw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/bin/geniclaw.js +323 -0
- package/dist/config-manager.d.ts +20 -0
- package/dist/config-manager.d.ts.map +1 -0
- package/dist/config-manager.js +52 -0
- package/dist/config-manager.js.map +1 -0
- package/dist/migrate-config.d.ts +2 -0
- package/dist/migrate-config.d.ts.map +1 -0
- package/dist/migrate-config.js +57 -0
- package/dist/migrate-config.js.map +1 -0
- package/dist/queue-processor.d.ts +7 -0
- package/dist/queue-processor.d.ts.map +1 -0
- package/dist/queue-processor.js +311 -0
- package/dist/queue-processor.js.map +1 -0
- package/dist/session-manager.d.ts +15 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +84 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +67 -0
- package/dist/setup.js.map +1 -0
- package/dist/telegram-client.d.ts +7 -0
- package/dist/telegram-client.d.ts.map +1 -0
- package/dist/telegram-client.js +229 -0
- package/dist/telegram-client.js.map +1 -0
- package/dist/whatsapp-client.d.ts +7 -0
- package/dist/whatsapp-client.d.ts.map +1 -0
- package/dist/whatsapp-client.js +173 -0
- package/dist/whatsapp-client.js.map +1 -0
- package/geniclaw.sh +388 -0
- package/heartbeat-cron.sh +71 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# 🦁 GeniClaw - Your AI Terminal Assistant
|
|
2
|
+
|
|
3
|
+
**GeniClaw** is a powerful, local-first AI assistant manager that connects **Google Gemini** to your workflow. It runs as a background service (daemon) on your machine, managing conversation history, project sessions, and token usage efficiently.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
- **🧠 Multi-Model Support**: Switch between `gemini-2.5-flash`, `pro`, and other models instantly.
|
|
8
|
+
- **📂 Session Management**: Create separate "folders" (sessions) for different projects or topics. Each session has its own isolated memory.
|
|
9
|
+
- **💾 Smart Memory**: Automatically manages conversation context. It keeps recent history while pruning older messages to stay within token limits (default ~100k tokens), enabling long-term usage without crashing.
|
|
10
|
+
- **🖥️ Terminal UI (TUI)**: An interactive command-line interface to manage everything.
|
|
11
|
+
- **⚡ Queue-Based**: Robust message processing ensures no data is lost, even if you are offline.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🛠️ Setup
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
|
|
19
|
+
- **Node.js** (v18 or higher)
|
|
20
|
+
- **Google Gemini API Key**: Get one for free at [aistudio.google.com](https://aistudio.google.com/).
|
|
21
|
+
- **Telegram Bot Token** (Optional, for chatting via Telegram): Get one from [@BotFather](https://t.me/botfather).
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
1. **Install via NPM**:
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @thelapyae/geniclaw
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. **Run Setup**:
|
|
31
|
+
This will guide you through entering your API keys.
|
|
32
|
+
```bash
|
|
33
|
+
geniclaw setup
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🎮 How to Use
|
|
39
|
+
|
|
40
|
+
GeniClaw is managed entirely through its **Interactive TUI**.
|
|
41
|
+
|
|
42
|
+
### 1. Start the TUI
|
|
43
|
+
Run the following command in your terminal:
|
|
44
|
+
```bash
|
|
45
|
+
./bin/geniclaw.js
|
|
46
|
+
# Or if you linked it:
|
|
47
|
+
geniclaw
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Main Menu Options
|
|
51
|
+
- **Start Daemon**: Launches the background process (Queue Processor & Telegram Bot).
|
|
52
|
+
- **Stop Daemon**: Stops all background processes.
|
|
53
|
+
- **Check Status**: Shows if the daemon is running and recent activity.
|
|
54
|
+
- **📂 Manage Sessions**: Create or switch between different conversation contexts.
|
|
55
|
+
- **🧠 Change Gemini Model**: Select the AI model you want to use.
|
|
56
|
+
- **View Logs**: Inspect what's happening under the hood.
|
|
57
|
+
|
|
58
|
+
### 3. Chatting
|
|
59
|
+
Once the daemon is running (`Start Daemon`), you can interact with it via:
|
|
60
|
+
- **Telegram**: If configured, just chat with your bot.
|
|
61
|
+
- **CLI (Manual)**: You can send a one-off message from the terminal:
|
|
62
|
+
```bash
|
|
63
|
+
./geniclaw.sh send "Hello Gemini, how are you?"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 📂 Session Management (Folders)
|
|
69
|
+
|
|
70
|
+
GeniClaw allows you to organize multiple conversations.
|
|
71
|
+
|
|
72
|
+
1. Open the TUI: `geniclaw`
|
|
73
|
+
2. Select **Manage Sessions**.
|
|
74
|
+
3. **Create New Session**: Name it (e.g., `coding-project`, `personal-notes`).
|
|
75
|
+
4. **Switch Session**: Select it to make it active.
|
|
76
|
+
|
|
77
|
+
**How it works**:
|
|
78
|
+
- When you switch to `coding-project`, GeniClaw creates a folder at `~/.geniclaw/sessions/coding-project/`.
|
|
79
|
+
- All history and memory for that conversation are stored there.
|
|
80
|
+
- Switching back to `default` restores your previous context instantly.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## ⚙️ Memory & Token Management
|
|
85
|
+
|
|
86
|
+
To support **Long Term Usage**, GeniClaw monitors your token usage.
|
|
87
|
+
|
|
88
|
+
- **Automatic Pruning**: If a conversation exceeds the safe limit (default ~100k tokens), GeniClaw removes the oldest messages to make room for new ones. This prevents "Context Length Exceeded" errors.
|
|
89
|
+
- **Check Usage**:
|
|
90
|
+
- Type `/stats` in the chat to see current token usage.
|
|
91
|
+
- **Reset**:
|
|
92
|
+
- Type `/reset` or use the **Reset** command in TUI to clear the current session's memory.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🏗️ Architecture & Storage
|
|
97
|
+
|
|
98
|
+
### Where are my files?
|
|
99
|
+
By default, GeniClaw stores everything locally in your home directory:
|
|
100
|
+
`~/.geniclaw/`
|
|
101
|
+
|
|
102
|
+
- `config.json`: Your settings (API keys, active session).
|
|
103
|
+
- `sessions/`: Folders containing history files for each session.
|
|
104
|
+
- `queue/`: Temporary files for messages being processed.
|
|
105
|
+
- `logs/`: Log files for debugging.
|
|
106
|
+
|
|
107
|
+
### How it works
|
|
108
|
+
1. **Input**: You send a message (Telegram/CLI).
|
|
109
|
+
2. **Queue**: The message is written to `queue/incoming/`.
|
|
110
|
+
3. **Processor**: The `queue-processor` picks it up.
|
|
111
|
+
- It loads the **Active Session** history.
|
|
112
|
+
- It checks **Token Limits**.
|
|
113
|
+
- It sends context + message to **Gemini API**.
|
|
114
|
+
4. **Response**: Gemini's reply is saved to history and written to `queue/outgoing/`.
|
|
115
|
+
5. **Output**: The interface (Telegram/CLI) sends the reply back to you.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## ⚠️ Things to Take Care Of
|
|
120
|
+
|
|
121
|
+
1. **API Costs**: While many Gemini models have a free tier, heavy usage (especially with large contexts) might incur costs if you are on a paid plan. Use `/stats` to monitor usage.
|
|
122
|
+
2. **Daemon**: The background process must be running to reply. If it stops, just run **Start Daemon** again.
|
|
123
|
+
3. **Privacy**: Your history is stored **locally** in plain text JSON files in `~/.geniclaw`. Keep your computer secure.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
**Enjoy your AI Assistant!** 🦁
|
package/bin/geniclaw.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn, execSync } = require('child_process');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { program } = require('commander');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
|
|
9
|
+
// Config Paths
|
|
10
|
+
const LOCAL_DIR = path.join(process.cwd(), '.geniclaw');
|
|
11
|
+
const GENICLAW_WORK_DIR = process.env.GENICLAW_WORK_DIR || (fs.existsSync(LOCAL_DIR) ? LOCAL_DIR : path.join(os.homedir(), '.geniclaw'));
|
|
12
|
+
const CONFIG_FILE = path.join(GENICLAW_WORK_DIR, 'config.json');
|
|
13
|
+
const SESSIONS_DIR = path.join(GENICLAW_WORK_DIR, 'sessions');
|
|
14
|
+
|
|
15
|
+
const SCRIPT_PATH = path.join(__dirname, '../geniclaw.sh');
|
|
16
|
+
|
|
17
|
+
// Helper to run shell script commands
|
|
18
|
+
function runScript(args) {
|
|
19
|
+
const child = spawn(SCRIPT_PATH, args, {
|
|
20
|
+
stdio: 'inherit',
|
|
21
|
+
env: process.env
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
child.on('exit', (code) => {
|
|
26
|
+
resolve(code);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Load Config
|
|
32
|
+
function loadConfig() {
|
|
33
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Save Config
|
|
44
|
+
function saveConfig(config) {
|
|
45
|
+
if (!fs.existsSync(GENICLAW_WORK_DIR)) {
|
|
46
|
+
fs.mkdirSync(GENICLAW_WORK_DIR, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Session Management Helpers
|
|
52
|
+
function ensureSessionsDir() {
|
|
53
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
54
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
if (!fs.existsSync(path.join(SESSIONS_DIR, 'default'))) {
|
|
57
|
+
fs.mkdirSync(path.join(SESSIONS_DIR, 'default'), { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getSessions() {
|
|
62
|
+
ensureSessionsDir();
|
|
63
|
+
return fs.readdirSync(SESSIONS_DIR).filter(file => fs.statSync(path.join(SESSIONS_DIR, file)).isDirectory());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Interactive Menu
|
|
67
|
+
async function showMenu() {
|
|
68
|
+
console.clear();
|
|
69
|
+
console.log('🦁 GeniClaw CLI TUI');
|
|
70
|
+
console.log('===================');
|
|
71
|
+
|
|
72
|
+
// Show current status summary
|
|
73
|
+
try {
|
|
74
|
+
const config = loadConfig();
|
|
75
|
+
const activeSession = config.activeSession || 'default';
|
|
76
|
+
console.log(`Current Model: ${config.geminiModel || 'Default (gemini-2.5-flash)'}`);
|
|
77
|
+
console.log(`Active Session: 📂 ${activeSession}`);
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
console.log('');
|
|
80
|
+
|
|
81
|
+
const { action } = await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'list',
|
|
84
|
+
name: 'action',
|
|
85
|
+
message: 'What would you like to do?',
|
|
86
|
+
choices: [
|
|
87
|
+
{ name: 'Start Daemon', value: 'start' },
|
|
88
|
+
{ name: 'Stop Daemon', value: 'stop' },
|
|
89
|
+
{ name: 'Restart Daemon', value: 'restart' },
|
|
90
|
+
{ name: 'Check Status', value: 'status' },
|
|
91
|
+
{ name: 'View Logs', value: 'logs' },
|
|
92
|
+
new inquirer.Separator(),
|
|
93
|
+
{ name: '📂 Manage Sessions', value: 'sessions' },
|
|
94
|
+
{ name: '🧠 Change Gemini Model', value: 'model' },
|
|
95
|
+
new inquirer.Separator(),
|
|
96
|
+
{ name: 'Exit', value: 'exit' }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
if (action === 'exit') {
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (action === 'model') {
|
|
106
|
+
await changeModel();
|
|
107
|
+
await promptContinue();
|
|
108
|
+
return showMenu();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (action === 'sessions') {
|
|
112
|
+
await manageSessions();
|
|
113
|
+
return showMenu();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (action === 'logs') {
|
|
117
|
+
await showLogsMenu();
|
|
118
|
+
return showMenu();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Process other actions via script
|
|
122
|
+
await runScript([action]);
|
|
123
|
+
await promptContinue();
|
|
124
|
+
return showMenu();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function promptContinue() {
|
|
128
|
+
await inquirer.prompt([
|
|
129
|
+
{
|
|
130
|
+
type: 'input',
|
|
131
|
+
name: 'continue',
|
|
132
|
+
message: 'Press Enter to continue...',
|
|
133
|
+
}
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function changeModel() {
|
|
138
|
+
const config = loadConfig();
|
|
139
|
+
const currentModel = config.geminiModel || 'gemini-2.5-flash';
|
|
140
|
+
|
|
141
|
+
const { model } = await inquirer.prompt([
|
|
142
|
+
{
|
|
143
|
+
type: 'list',
|
|
144
|
+
name: 'model',
|
|
145
|
+
message: 'Select Gemini Model:',
|
|
146
|
+
default: currentModel,
|
|
147
|
+
choices: [
|
|
148
|
+
'gemini-2.5-flash',
|
|
149
|
+
'gemini-flash-lite-latest',
|
|
150
|
+
'gemini-2.5-pro',
|
|
151
|
+
'gemini-3-flash-preview',
|
|
152
|
+
'gemini-3-pro-preview',
|
|
153
|
+
new inquirer.Separator(),
|
|
154
|
+
{ name: 'Custom (Enter manually)', value: 'custom' }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
let selectedModel = model;
|
|
160
|
+
if (model === 'custom') {
|
|
161
|
+
const { customModel } = await inquirer.prompt([
|
|
162
|
+
{
|
|
163
|
+
type: 'input',
|
|
164
|
+
name: 'customModel',
|
|
165
|
+
message: 'Enter model name:',
|
|
166
|
+
validate: input => input.length > 0
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
selectedModel = customModel;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
config.geminiModel = selectedModel;
|
|
173
|
+
saveConfig(config);
|
|
174
|
+
console.log(`✅ Model updated to: ${selectedModel}`);
|
|
175
|
+
console.log('Note: You may need to restart the daemon for changes to take effect.');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function manageSessions() {
|
|
179
|
+
const config = loadConfig();
|
|
180
|
+
const activeSession = config.activeSession || 'default';
|
|
181
|
+
const sessions = getSessions();
|
|
182
|
+
|
|
183
|
+
const { action } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: 'list',
|
|
186
|
+
name: 'action',
|
|
187
|
+
message: `Manage Sessions (Active: ${activeSession})`,
|
|
188
|
+
choices: [
|
|
189
|
+
{ name: 'Switch Session', value: 'switch' },
|
|
190
|
+
{ name: 'Create New Session', value: 'create' },
|
|
191
|
+
{ name: 'Delete Session', value: 'delete' },
|
|
192
|
+
{ name: 'Back', value: 'back' }
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
if (action === 'back') return;
|
|
198
|
+
|
|
199
|
+
if (action === 'switch') {
|
|
200
|
+
const { session } = await inquirer.prompt([
|
|
201
|
+
{
|
|
202
|
+
type: 'list',
|
|
203
|
+
name: 'session',
|
|
204
|
+
message: 'Select session to switch to:',
|
|
205
|
+
choices: sessions.map(s => s === activeSession ? { name: `${s} (active)`, value: s } : s)
|
|
206
|
+
}
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
if (session !== activeSession) {
|
|
210
|
+
config.activeSession = session;
|
|
211
|
+
saveConfig(config);
|
|
212
|
+
console.log(`✅ Switched to session: ${session}`);
|
|
213
|
+
// We should ideally restart the queue processor or signal it, but usually it re-reads config per message
|
|
214
|
+
// Wait, our queue processor reads config inside processMessage, so it will pick it up instantly for NEW messages!
|
|
215
|
+
// BUT HistoryManager.load() might need to know.
|
|
216
|
+
// In our TS code, HistoryManager.getHistoryPath() calls SessionManager.getActiveSessionPath() which loads config.
|
|
217
|
+
// So it supports hot-swapping!
|
|
218
|
+
} else {
|
|
219
|
+
console.log('Already on this session.');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (action === 'create') {
|
|
224
|
+
const { name } = await inquirer.prompt([
|
|
225
|
+
{
|
|
226
|
+
type: 'input',
|
|
227
|
+
name: 'name',
|
|
228
|
+
message: 'Enter new session name (alphanumeric):',
|
|
229
|
+
validate: input => /^[a-zA-Z0-9_-]+$/.test(input) ? true : 'Invalid name. Use only letters, numbers, _, -'
|
|
230
|
+
}
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
const sessionPath = path.join(SESSIONS_DIR, name);
|
|
234
|
+
if (fs.existsSync(sessionPath)) {
|
|
235
|
+
console.log('❌ Session already exists.');
|
|
236
|
+
} else {
|
|
237
|
+
fs.mkdirSync(sessionPath, { recursive: true });
|
|
238
|
+
console.log(`✅ Session '${name}' created.`);
|
|
239
|
+
|
|
240
|
+
const { switchTo } = await inquirer.prompt([
|
|
241
|
+
{ type: 'confirm', name: 'switchTo', message: 'Switch to this session now?', default: true }
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
if (switchTo) {
|
|
245
|
+
config.activeSession = name;
|
|
246
|
+
saveConfig(config);
|
|
247
|
+
console.log(`✅ Switched to session: ${name}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (action === 'delete') {
|
|
253
|
+
const { session } = await inquirer.prompt([
|
|
254
|
+
{
|
|
255
|
+
type: 'list',
|
|
256
|
+
name: 'session',
|
|
257
|
+
message: 'Select session to delete:',
|
|
258
|
+
choices: sessions.filter(s => s !== 'default' && s !== activeSession) // Cannot delete default or active
|
|
259
|
+
}
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
if (!session) {
|
|
263
|
+
console.log("No deletable sessions available (cannot delete 'default' or active session).");
|
|
264
|
+
} else {
|
|
265
|
+
const { confirm } = await inquirer.prompt([
|
|
266
|
+
{ type: 'confirm', name: 'confirm', message: `Are you sure you want to PERMANENTLY delete '${session}' and all its history?`, default: false }
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
if (confirm) {
|
|
270
|
+
fs.rmSync(path.join(SESSIONS_DIR, session), { recursive: true, force: true });
|
|
271
|
+
console.log(`🗑️ Session '${session}' deleted.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await promptContinue();
|
|
277
|
+
return manageSessions(); // Loop back
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function showLogsMenu() {
|
|
281
|
+
const { logType } = await inquirer.prompt([
|
|
282
|
+
{
|
|
283
|
+
type: 'list',
|
|
284
|
+
name: 'logType',
|
|
285
|
+
message: 'Which logs to view?',
|
|
286
|
+
choices: [
|
|
287
|
+
{ name: 'Telegram', value: 'telegram' },
|
|
288
|
+
{ name: 'Queue Processor', value: 'queue' },
|
|
289
|
+
{ name: 'Daemon', value: 'daemon' },
|
|
290
|
+
{ name: 'Back', value: 'back' }
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
if (logType === 'back') return;
|
|
296
|
+
|
|
297
|
+
console.log('Press Ctrl+C to exit logs.');
|
|
298
|
+
await runScript(['logs', logType]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
// CLI Configuration
|
|
303
|
+
program
|
|
304
|
+
.version('1.0.0')
|
|
305
|
+
.description('GeniClaw Management CLI')
|
|
306
|
+
.arguments('[command]')
|
|
307
|
+
.action(async (command) => {
|
|
308
|
+
if (!command) {
|
|
309
|
+
// No args -> TUI Mode
|
|
310
|
+
await showMenu();
|
|
311
|
+
} else {
|
|
312
|
+
if (command === 'model') {
|
|
313
|
+
await changeModel();
|
|
314
|
+
} else if (command === 'sessions') {
|
|
315
|
+
await manageSessions();
|
|
316
|
+
} else {
|
|
317
|
+
const args = process.argv.slice(2);
|
|
318
|
+
await runScript(args);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const GENICLAW_WORK_DIR: string;
|
|
2
|
+
export declare const CONFIG_FILE: string;
|
|
3
|
+
export interface AppConfig {
|
|
4
|
+
geminiApiKey?: string;
|
|
5
|
+
geminiModel?: string;
|
|
6
|
+
telegramBotToken?: string;
|
|
7
|
+
telegramAllowedUserId?: string;
|
|
8
|
+
activeSession?: string;
|
|
9
|
+
userName?: string;
|
|
10
|
+
botName?: string;
|
|
11
|
+
botPurpose?: string;
|
|
12
|
+
onboardingState: 'INIT' | 'AWAITING_NAME' | 'AWAITING_BOT_NAME' | 'AWAITING_PURPOSE' | 'READY';
|
|
13
|
+
}
|
|
14
|
+
export declare class ConfigManager {
|
|
15
|
+
private static cachedConfig;
|
|
16
|
+
static load(): AppConfig;
|
|
17
|
+
static save(config: AppConfig): void;
|
|
18
|
+
static update(updates: Partial<AppConfig>): void;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../src/config-manager.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,iBAAiB,QAAiH,CAAC;AAChJ,eAAO,MAAM,WAAW,QAA8C,CAAC;AAGvE,MAAM,WAAW,SAAS;IAEtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,eAAe,GAAG,mBAAmB,GAAG,kBAAkB,GAAG,OAAO,CAAC;CAClG;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAAC,YAAY,CAA0B;IAErD,MAAM,CAAC,IAAI,IAAI,SAAS;IAiBxB,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAcpC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;CAKnD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConfigManager = exports.CONFIG_FILE = exports.GENICLAW_WORK_DIR = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
// Config Paths
|
|
11
|
+
const LOCAL_DIR = path_1.default.join(process.cwd(), '.geniclaw');
|
|
12
|
+
exports.GENICLAW_WORK_DIR = process.env.GENICLAW_WORK_DIR || (fs_1.default.existsSync(LOCAL_DIR) ? LOCAL_DIR : path_1.default.join(os_1.default.homedir(), '.geniclaw'));
|
|
13
|
+
exports.CONFIG_FILE = path_1.default.join(exports.GENICLAW_WORK_DIR, 'config.json');
|
|
14
|
+
class ConfigManager {
|
|
15
|
+
static cachedConfig = null;
|
|
16
|
+
static load() {
|
|
17
|
+
if (this.cachedConfig)
|
|
18
|
+
return this.cachedConfig;
|
|
19
|
+
if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
|
|
20
|
+
try {
|
|
21
|
+
const data = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf8');
|
|
22
|
+
this.cachedConfig = JSON.parse(data);
|
|
23
|
+
return this.cachedConfig;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error(`Failed to load config: ${e.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Default config
|
|
30
|
+
return { onboardingState: 'INIT' };
|
|
31
|
+
}
|
|
32
|
+
static save(config) {
|
|
33
|
+
try {
|
|
34
|
+
// Ensure directory exists
|
|
35
|
+
if (!fs_1.default.existsSync(exports.GENICLAW_WORK_DIR)) {
|
|
36
|
+
fs_1.default.mkdirSync(exports.GENICLAW_WORK_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
39
|
+
this.cachedConfig = config;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.error(`Failed to save config: ${e.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static update(updates) {
|
|
46
|
+
const current = this.load();
|
|
47
|
+
const updated = { ...current, ...updates };
|
|
48
|
+
this.save(updated);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.ConfigManager = ConfigManager;
|
|
52
|
+
//# sourceMappingURL=config-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-manager.js","sourceRoot":"","sources":["../src/config-manager.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AACxB,4CAAoB;AAEpB,eAAe;AACf,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;AAC3C,QAAA,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;AACnI,QAAA,WAAW,GAAG,cAAI,CAAC,IAAI,CAAC,yBAAiB,EAAE,aAAa,CAAC,CAAC;AAoBvE,MAAa,aAAa;IACd,MAAM,CAAC,YAAY,GAAqB,IAAI,CAAC;IAErD,MAAM,CAAC,IAAI;QACP,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC,YAAY,CAAC;QAEhD,IAAI,YAAE,CAAC,UAAU,CAAC,mBAAW,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,YAAE,CAAC,YAAY,CAAC,mBAAW,EAAE,MAAM,CAAC,CAAC;gBAClD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrC,OAAO,IAAI,CAAC,YAAa,CAAC;YAC9B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,OAAO,CAAC,KAAK,CAAC,0BAA2B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YACpE,CAAC;QACL,CAAC;QAED,iBAAiB;QACjB,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,MAAiB;QACzB,IAAI,CAAC;YACD,0BAA0B;YAC1B,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,yBAAiB,CAAC,EAAE,CAAC;gBACpC,YAAE,CAAC,SAAS,CAAC,yBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;YAED,YAAE,CAAC,aAAa,CAAC,mBAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAC/D,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC;QAC/B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,0BAA2B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;QACpE,CAAC;IACL,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,OAA2B;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;;AAtCL,sCAuCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-config.d.ts","sourceRoot":"","sources":["../src/migrate-config.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
const config_manager_1 = require("./config-manager");
|
|
10
|
+
const ENV_FILE_INTERNAL = path_1.default.join(config_manager_1.GENICLAW_WORK_DIR, '.env');
|
|
11
|
+
const ENV_FILE_PARENT = path_1.default.join(path_1.default.dirname(config_manager_1.GENICLAW_WORK_DIR), '.env');
|
|
12
|
+
// Prefer parent .env if work dir is .geniclaw (common pattern)
|
|
13
|
+
const ENV_FILE = fs_1.default.existsSync(ENV_FILE_PARENT) ? ENV_FILE_PARENT : ENV_FILE_INTERNAL;
|
|
14
|
+
const SETTINGS_FILE = path_1.default.join(config_manager_1.GENICLAW_WORK_DIR, 'settings.json');
|
|
15
|
+
console.log('🔄 Starting configuration migration...');
|
|
16
|
+
// 1. Load .env
|
|
17
|
+
let envConfig = {};
|
|
18
|
+
if (fs_1.default.existsSync(ENV_FILE)) {
|
|
19
|
+
console.log('Found .env, reading keys...');
|
|
20
|
+
const env = dotenv_1.default.parse(fs_1.default.readFileSync(ENV_FILE));
|
|
21
|
+
envConfig.geminiApiKey = env.GEMINI_API_KEY;
|
|
22
|
+
envConfig.telegramBotToken = env.TELEGRAM_BOT_TOKEN;
|
|
23
|
+
envConfig.telegramAllowedUserId = env.TELEGRAM_ALLOWED_USER_ID;
|
|
24
|
+
}
|
|
25
|
+
// 2. Load settings.json
|
|
26
|
+
let settingsConfig = {};
|
|
27
|
+
if (fs_1.default.existsSync(SETTINGS_FILE)) {
|
|
28
|
+
console.log('Found settings.json, reading onboarding data...');
|
|
29
|
+
try {
|
|
30
|
+
const settings = JSON.parse(fs_1.default.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
31
|
+
settingsConfig = settings;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error('Error reading settings.json:', e);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 3. Merge into ConfigManager
|
|
38
|
+
if (Object.keys(envConfig).length > 0 || Object.keys(settingsConfig).length > 0) {
|
|
39
|
+
const currentConfig = config_manager_1.ConfigManager.load();
|
|
40
|
+
const newConfig = { ...currentConfig, ...envConfig, ...settingsConfig };
|
|
41
|
+
config_manager_1.ConfigManager.save(newConfig);
|
|
42
|
+
console.log(`✅ Config saved to ${config_manager_1.CONFIG_FILE}`);
|
|
43
|
+
// 4. Backup old files
|
|
44
|
+
if (fs_1.default.existsSync(ENV_FILE)) {
|
|
45
|
+
fs_1.default.renameSync(ENV_FILE, `${ENV_FILE}.migrated`);
|
|
46
|
+
console.log('Moved .env to .env.migrated');
|
|
47
|
+
}
|
|
48
|
+
if (fs_1.default.existsSync(SETTINGS_FILE)) {
|
|
49
|
+
fs_1.default.renameSync(SETTINGS_FILE, `${SETTINGS_FILE}.migrated`);
|
|
50
|
+
console.log('Moved settings.json to settings.json.migrated');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('No legacy config files found to migrate.');
|
|
55
|
+
}
|
|
56
|
+
console.log('Migration complete.');
|
|
57
|
+
//# sourceMappingURL=migrate-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-config.js","sourceRoot":"","sources":["../src/migrate-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-processor.d.ts","sourceRoot":"","sources":["../src/queue-processor.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|