lollmsbot 0.0.1__py3-none-any.whl
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.
- lollmsbot/__init__.py +1 -0
- lollmsbot/agent.py +1682 -0
- lollmsbot/channels/__init__.py +22 -0
- lollmsbot/channels/discord.py +408 -0
- lollmsbot/channels/http_api.py +449 -0
- lollmsbot/channels/telegram.py +272 -0
- lollmsbot/cli.py +217 -0
- lollmsbot/config.py +90 -0
- lollmsbot/gateway.py +606 -0
- lollmsbot/guardian.py +692 -0
- lollmsbot/heartbeat.py +826 -0
- lollmsbot/lollms_client.py +37 -0
- lollmsbot/skills.py +1483 -0
- lollmsbot/soul.py +482 -0
- lollmsbot/storage/__init__.py +245 -0
- lollmsbot/storage/sqlite_store.py +332 -0
- lollmsbot/tools/__init__.py +151 -0
- lollmsbot/tools/calendar.py +717 -0
- lollmsbot/tools/filesystem.py +663 -0
- lollmsbot/tools/http.py +498 -0
- lollmsbot/tools/shell.py +519 -0
- lollmsbot/ui/__init__.py +11 -0
- lollmsbot/ui/__main__.py +121 -0
- lollmsbot/ui/app.py +1122 -0
- lollmsbot/ui/routes.py +39 -0
- lollmsbot/wizard.py +1493 -0
- lollmsbot-0.0.1.dist-info/METADATA +25 -0
- lollmsbot-0.0.1.dist-info/RECORD +32 -0
- lollmsbot-0.0.1.dist-info/WHEEL +5 -0
- lollmsbot-0.0.1.dist-info/entry_points.txt +2 -0
- lollmsbot-0.0.1.dist-info/licenses/LICENSE +201 -0
- lollmsbot-0.0.1.dist-info/top_level.txt +1 -0
lollmsbot/wizard.py
ADDED
|
@@ -0,0 +1,1493 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
lollmsBot Interactive Setup Wizard - Skills Edition
|
|
4
|
+
|
|
5
|
+
Now includes:
|
|
6
|
+
- Binding-first backend configuration (remote vs local bindings)
|
|
7
|
+
- Soul configuration (personality, identity, values)
|
|
8
|
+
- Heartbeat settings (self-maintenance frequency, tasks)
|
|
9
|
+
- Memory monitoring (compression, retention, optimization)
|
|
10
|
+
- Skills management (browse, test, create, configure)
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import json
|
|
16
|
+
import hashlib
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
from rich.prompt import Prompt, Confirm, IntPrompt, FloatPrompt
|
|
25
|
+
from rich.table import Table
|
|
26
|
+
from rich.text import Text
|
|
27
|
+
from rich.tree import Tree
|
|
28
|
+
import questionary
|
|
29
|
+
from questionary import Choice
|
|
30
|
+
except ImportError:
|
|
31
|
+
print("❌ Install dev deps: pip install -e .[dev]")
|
|
32
|
+
exit(1)
|
|
33
|
+
|
|
34
|
+
from lollmsbot.config import LollmsSettings
|
|
35
|
+
from lollmsbot.lollms_client import build_lollms_client
|
|
36
|
+
from lollmsbot.soul import Soul, PersonalityTrait, TraitIntensity, ValueStatement, CommunicationStyle, ExpertiseDomain
|
|
37
|
+
from lollmsbot.heartbeat import Heartbeat, HeartbeatConfig, MaintenanceTask, get_heartbeat
|
|
38
|
+
from lollmsbot.skills import SkillRegistry, SkillComplexity, get_skill_registry, SkillLearner
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
console = Console()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class BindingInfo:
|
|
46
|
+
"""Information about an LLM binding."""
|
|
47
|
+
name: str
|
|
48
|
+
display_name: str
|
|
49
|
+
category: str # "remote", "local_server", "local_direct"
|
|
50
|
+
description: str
|
|
51
|
+
default_host: Optional[str] = None
|
|
52
|
+
requires_api_key: bool = True
|
|
53
|
+
supports_ssl_verify: bool = True
|
|
54
|
+
requires_models_path: bool = False
|
|
55
|
+
default_model: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Binding registry - all available bindings
|
|
59
|
+
AVAILABLE_BINDINGS: Dict[str, BindingInfo] = {
|
|
60
|
+
# Remote / SaaS bindings
|
|
61
|
+
"lollms": BindingInfo(
|
|
62
|
+
name="lollms",
|
|
63
|
+
display_name="🔗 LoLLMS (Default)",
|
|
64
|
+
category="remote",
|
|
65
|
+
description="LoLLMS WebUI - Local or remote LoLLMS server",
|
|
66
|
+
default_host="http://localhost:9600",
|
|
67
|
+
requires_api_key=False, # Optional for local, required for remote
|
|
68
|
+
supports_ssl_verify=True,
|
|
69
|
+
default_model=None,
|
|
70
|
+
),
|
|
71
|
+
"openai": BindingInfo(
|
|
72
|
+
name="openai",
|
|
73
|
+
display_name="🤖 OpenAI",
|
|
74
|
+
category="remote",
|
|
75
|
+
description="OpenAI GPT models (GPT-4, GPT-3.5, etc.)",
|
|
76
|
+
default_host="https://api.openai.com/v1",
|
|
77
|
+
requires_api_key=True,
|
|
78
|
+
supports_ssl_verify=True,
|
|
79
|
+
default_model="gpt-4o-mini",
|
|
80
|
+
),
|
|
81
|
+
"azure_openai": BindingInfo(
|
|
82
|
+
name="azure_openai",
|
|
83
|
+
display_name="☁️ Azure OpenAI",
|
|
84
|
+
category="remote",
|
|
85
|
+
description="Microsoft Azure OpenAI Service",
|
|
86
|
+
default_host="https://YOUR_RESOURCE.openai.azure.com/",
|
|
87
|
+
requires_api_key=True,
|
|
88
|
+
supports_ssl_verify=True,
|
|
89
|
+
default_model="gpt-4",
|
|
90
|
+
),
|
|
91
|
+
"claude": BindingInfo(
|
|
92
|
+
name="claude",
|
|
93
|
+
display_name="🧠 Anthropic Claude",
|
|
94
|
+
category="remote",
|
|
95
|
+
description="Anthropic Claude models",
|
|
96
|
+
default_host="https://api.anthropic.com",
|
|
97
|
+
requires_api_key=True,
|
|
98
|
+
supports_ssl_verify=True,
|
|
99
|
+
default_model="claude-3-5-sonnet-20241022",
|
|
100
|
+
),
|
|
101
|
+
"gemini": BindingInfo(
|
|
102
|
+
name="gemini",
|
|
103
|
+
display_name="💎 Google Gemini",
|
|
104
|
+
category="remote",
|
|
105
|
+
description="Google Gemini models",
|
|
106
|
+
default_host="https://generativelanguage.googleapis.com",
|
|
107
|
+
requires_api_key=True,
|
|
108
|
+
supports_ssl_verify=True,
|
|
109
|
+
default_model="gemini-1.5-flash",
|
|
110
|
+
),
|
|
111
|
+
"groq": BindingInfo(
|
|
112
|
+
name="groq",
|
|
113
|
+
display_name="⚡ Groq",
|
|
114
|
+
category="remote",
|
|
115
|
+
description="Groq ultra-fast inference",
|
|
116
|
+
default_host="https://api.groq.com/openai/v1",
|
|
117
|
+
requires_api_key=True,
|
|
118
|
+
supports_ssl_verify=True,
|
|
119
|
+
default_model="llama-3.1-8b-instant",
|
|
120
|
+
),
|
|
121
|
+
"grok": BindingInfo(
|
|
122
|
+
name="grok",
|
|
123
|
+
display_name="🐦 xAI Grok",
|
|
124
|
+
category="remote",
|
|
125
|
+
description="xAI Grok models",
|
|
126
|
+
default_host="https://api.x.ai/v1",
|
|
127
|
+
requires_api_key=True,
|
|
128
|
+
supports_ssl_verify=True,
|
|
129
|
+
default_model="grok-2",
|
|
130
|
+
),
|
|
131
|
+
"mistral": BindingInfo(
|
|
132
|
+
name="mistral",
|
|
133
|
+
display_name="🌊 Mistral AI",
|
|
134
|
+
category="remote",
|
|
135
|
+
description="Mistral AI models",
|
|
136
|
+
default_host="https://api.mistral.ai/v1",
|
|
137
|
+
requires_api_key=True,
|
|
138
|
+
supports_ssl_verify=True,
|
|
139
|
+
default_model="mistral-small-latest",
|
|
140
|
+
),
|
|
141
|
+
"ollama": BindingInfo(
|
|
142
|
+
name="ollama",
|
|
143
|
+
display_name="🦙 Ollama",
|
|
144
|
+
category="local_server",
|
|
145
|
+
description="Ollama local LLM server",
|
|
146
|
+
default_host="http://localhost:11434",
|
|
147
|
+
requires_api_key=False, # Local by default, key optional for proxy
|
|
148
|
+
supports_ssl_verify=False, # Usually local
|
|
149
|
+
default_model="llama3.2",
|
|
150
|
+
),
|
|
151
|
+
"open_router": BindingInfo(
|
|
152
|
+
name="open_router",
|
|
153
|
+
display_name="🌐 OpenRouter",
|
|
154
|
+
category="remote",
|
|
155
|
+
description="OpenRouter - unified API for many models",
|
|
156
|
+
default_host="https://openrouter.ai/api/v1",
|
|
157
|
+
requires_api_key=True,
|
|
158
|
+
supports_ssl_verify=True,
|
|
159
|
+
default_model="meta-llama/llama-3.1-8b-instruct",
|
|
160
|
+
),
|
|
161
|
+
"perplexity": BindingInfo(
|
|
162
|
+
name="perplexity",
|
|
163
|
+
display_name="❓ Perplexity",
|
|
164
|
+
category="remote",
|
|
165
|
+
description="Perplexity AI API",
|
|
166
|
+
default_host="https://api.perplexity.ai",
|
|
167
|
+
requires_api_key=True,
|
|
168
|
+
supports_ssl_verify=True,
|
|
169
|
+
default_model="llama-3.1-sonar-small-128k-online",
|
|
170
|
+
),
|
|
171
|
+
"novita_ai": BindingInfo(
|
|
172
|
+
name="novita_ai",
|
|
173
|
+
display_name="✨ Novita AI",
|
|
174
|
+
category="remote",
|
|
175
|
+
description="Novita AI inference platform",
|
|
176
|
+
default_host="https://api.novita.ai/v3/openai",
|
|
177
|
+
requires_api_key=True,
|
|
178
|
+
supports_ssl_verify=True,
|
|
179
|
+
default_model="meta-llama/llama-3.1-8b-instruct",
|
|
180
|
+
),
|
|
181
|
+
"litellm": BindingInfo(
|
|
182
|
+
name="litellm",
|
|
183
|
+
display_name="📡 LiteLLM",
|
|
184
|
+
category="remote",
|
|
185
|
+
description="LiteLLM proxy/gateway",
|
|
186
|
+
default_host="http://localhost:4000",
|
|
187
|
+
requires_api_key=True,
|
|
188
|
+
supports_ssl_verify=True,
|
|
189
|
+
default_model=None,
|
|
190
|
+
),
|
|
191
|
+
"hugging_face_inference_api": BindingInfo(
|
|
192
|
+
name="hugging_face_inference_api",
|
|
193
|
+
display_name="🤗 Hugging Face",
|
|
194
|
+
category="remote",
|
|
195
|
+
description="Hugging Face Inference API",
|
|
196
|
+
default_host="https://api-inference.huggingface.co",
|
|
197
|
+
requires_api_key=True,
|
|
198
|
+
supports_ssl_verify=True,
|
|
199
|
+
default_model=None,
|
|
200
|
+
),
|
|
201
|
+
"openllm": BindingInfo(
|
|
202
|
+
name="openllm",
|
|
203
|
+
display_name="🔧 OpenLLM",
|
|
204
|
+
category="local_server",
|
|
205
|
+
description="BentoML OpenLLM serving",
|
|
206
|
+
default_host="http://localhost:3000",
|
|
207
|
+
requires_api_key=False,
|
|
208
|
+
supports_ssl_verify=True,
|
|
209
|
+
default_model=None,
|
|
210
|
+
),
|
|
211
|
+
"openwebui": BindingInfo(
|
|
212
|
+
name="openwebui",
|
|
213
|
+
display_name="🌟 OpenWebUI",
|
|
214
|
+
category="local_server",
|
|
215
|
+
description="OpenWebUI backend",
|
|
216
|
+
default_host="http://localhost:8080",
|
|
217
|
+
requires_api_key=True, # OpenWebUI uses API keys
|
|
218
|
+
supports_ssl_verify=True,
|
|
219
|
+
default_model=None,
|
|
220
|
+
),
|
|
221
|
+
# Local direct bindings
|
|
222
|
+
"llama_cpp_server": BindingInfo(
|
|
223
|
+
name="llama_cpp_server",
|
|
224
|
+
display_name="🦙 Llama.cpp (Server)",
|
|
225
|
+
category="local_server",
|
|
226
|
+
description="llama.cpp server mode (local)",
|
|
227
|
+
default_host="http://localhost:8080",
|
|
228
|
+
requires_api_key=False,
|
|
229
|
+
supports_ssl_verify=False,
|
|
230
|
+
requires_models_path=True,
|
|
231
|
+
default_model=None,
|
|
232
|
+
),
|
|
233
|
+
"vllm": BindingInfo(
|
|
234
|
+
name="vllm",
|
|
235
|
+
display_name="🔥 vLLM",
|
|
236
|
+
category="local_server",
|
|
237
|
+
description="vLLM high-throughput inference",
|
|
238
|
+
default_host="http://localhost:8000",
|
|
239
|
+
requires_api_key=False,
|
|
240
|
+
supports_ssl_verify=True,
|
|
241
|
+
requires_models_path=False,
|
|
242
|
+
default_model=None,
|
|
243
|
+
),
|
|
244
|
+
"tensor_rt": BindingInfo(
|
|
245
|
+
name="tensor_rt",
|
|
246
|
+
display_name="🚀 TensorRT",
|
|
247
|
+
category="local_direct",
|
|
248
|
+
description="NVIDIA TensorRT LLM (local)",
|
|
249
|
+
default_host=None,
|
|
250
|
+
requires_api_key=False,
|
|
251
|
+
supports_ssl_verify=False,
|
|
252
|
+
requires_models_path=True,
|
|
253
|
+
default_model=None,
|
|
254
|
+
),
|
|
255
|
+
"transformers": BindingInfo(
|
|
256
|
+
name="transformers",
|
|
257
|
+
display_name="🤗 Transformers",
|
|
258
|
+
category="local_direct",
|
|
259
|
+
description="Hugging Face Transformers (local)",
|
|
260
|
+
default_host=None,
|
|
261
|
+
requires_api_key=False,
|
|
262
|
+
supports_ssl_verify=False,
|
|
263
|
+
requires_models_path=True,
|
|
264
|
+
default_model=None,
|
|
265
|
+
),
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class Wizard:
|
|
270
|
+
"""Interactive setup wizard for lollmsBot services - Full 7 Pillars Edition."""
|
|
271
|
+
|
|
272
|
+
def __init__(self):
|
|
273
|
+
self.config_path = Path.home() / ".lollmsbot" / "config.json"
|
|
274
|
+
self.config_path.parent.mkdir(exist_ok=True)
|
|
275
|
+
self.config: Dict[str, Dict[str, Any]] = self._load_config()
|
|
276
|
+
|
|
277
|
+
# Initialize subsystems for configuration
|
|
278
|
+
self.soul = Soul()
|
|
279
|
+
self.heartbeat = get_heartbeat()
|
|
280
|
+
self.skill_registry = get_skill_registry()
|
|
281
|
+
|
|
282
|
+
# Track what's been configured
|
|
283
|
+
self._configured: set = set()
|
|
284
|
+
|
|
285
|
+
def _load_config(self) -> Dict[str, Dict[str, Any]]:
|
|
286
|
+
if self.config_path.exists():
|
|
287
|
+
return json.loads(self.config_path.read_text())
|
|
288
|
+
return {}
|
|
289
|
+
|
|
290
|
+
def _save_config(self) -> None:
|
|
291
|
+
self.config_path.write_text(json.dumps(self.config, indent=2))
|
|
292
|
+
|
|
293
|
+
def run_wizard(self) -> None:
|
|
294
|
+
"""Main wizard loop - Full Edition with all 7 Pillars."""
|
|
295
|
+
console.clear()
|
|
296
|
+
|
|
297
|
+
# Beautiful animated banner
|
|
298
|
+
banner = Panel.fit(
|
|
299
|
+
Text.assemble(
|
|
300
|
+
("🧬 ", "bold magenta"),
|
|
301
|
+
("lollmsBot", "bold cyan"),
|
|
302
|
+
(" Setup Wizard\n", "bold blue"),
|
|
303
|
+
("Configure your ", "dim"),
|
|
304
|
+
("sovereign AI companion", "italic green"),
|
|
305
|
+
),
|
|
306
|
+
border_style="bright_blue",
|
|
307
|
+
padding=(1, 4),
|
|
308
|
+
)
|
|
309
|
+
console.print(banner)
|
|
310
|
+
console.print()
|
|
311
|
+
|
|
312
|
+
# Show current status
|
|
313
|
+
self._show_status_tree()
|
|
314
|
+
|
|
315
|
+
while True:
|
|
316
|
+
action = questionary.select(
|
|
317
|
+
"What would you like to configure?",
|
|
318
|
+
choices=[
|
|
319
|
+
Choice("🔗 AI Backend (Select Binding First)", "lollms"),
|
|
320
|
+
Choice("🤖 Discord Channel", "discord"),
|
|
321
|
+
Choice("✈️ Telegram Channel", "telegram"),
|
|
322
|
+
Choice("🧬 Soul (Personality & Identity)", "soul"),
|
|
323
|
+
Choice("💓 Heartbeat (Self-Maintenance)", "heartbeat"),
|
|
324
|
+
Choice("🧠 Memory (Storage & Retention)", "memory"),
|
|
325
|
+
Choice("📚 Skills (Capabilities & Learning)", "skills"),
|
|
326
|
+
Choice("🔍 Test Connections", "test"),
|
|
327
|
+
Choice("📄 View Full Configuration", "view"),
|
|
328
|
+
Choice("💾 Save & Exit", "save"),
|
|
329
|
+
Choice("❌ Quit Without Saving", "quit"),
|
|
330
|
+
],
|
|
331
|
+
use_indicator=True,
|
|
332
|
+
).ask()
|
|
333
|
+
|
|
334
|
+
if action == "lollms":
|
|
335
|
+
self.configure_backend() # New binding-first configuration
|
|
336
|
+
elif action == "discord":
|
|
337
|
+
self.configure_service("discord")
|
|
338
|
+
elif action == "telegram":
|
|
339
|
+
self.configure_service("telegram")
|
|
340
|
+
elif action == "soul":
|
|
341
|
+
self.configure_soul()
|
|
342
|
+
elif action == "heartbeat":
|
|
343
|
+
self.configure_heartbeat()
|
|
344
|
+
elif action == "memory":
|
|
345
|
+
self.configure_memory()
|
|
346
|
+
elif action == "skills":
|
|
347
|
+
self.configure_skills()
|
|
348
|
+
elif action == "test":
|
|
349
|
+
self.test_connections()
|
|
350
|
+
elif action == "view":
|
|
351
|
+
self.show_full_config()
|
|
352
|
+
elif action == "save":
|
|
353
|
+
self._save_all()
|
|
354
|
+
console.print("\n[bold green]✅ All configurations saved![/]")
|
|
355
|
+
console.print(f"[dim]Location: {self.config_path}[/]")
|
|
356
|
+
break
|
|
357
|
+
elif action == "quit":
|
|
358
|
+
if questionary.confirm("Discard unsaved changes?", default=False).ask():
|
|
359
|
+
break
|
|
360
|
+
|
|
361
|
+
console.print("\n[bold cyan]🚀 Ready to start your lollmsBot journey![/]")
|
|
362
|
+
console.print("[dim]Run: lollmsbot gateway[/]")
|
|
363
|
+
|
|
364
|
+
def _show_status_tree(self) -> None:
|
|
365
|
+
"""Show configuration status as a tree."""
|
|
366
|
+
tree = Tree("📊 Configuration Status")
|
|
367
|
+
|
|
368
|
+
# Core services
|
|
369
|
+
services = tree.add("[bold]Services[/]")
|
|
370
|
+
for key in ["lollms", "discord", "telegram"]:
|
|
371
|
+
configured = key in self.config and self.config[key]
|
|
372
|
+
status = "✅" if configured else "⭕"
|
|
373
|
+
color = "green" if configured else "dim"
|
|
374
|
+
display_name = "AI Backend" if key == "lollms" else key.title()
|
|
375
|
+
services.add(f"[{color}]{status} {display_name}[/{color}]")
|
|
376
|
+
|
|
377
|
+
# Show current binding if configured
|
|
378
|
+
if "lollms" in self.config and "binding_name" in self.config["lollms"]:
|
|
379
|
+
binding = self.config["lollms"]["binding_name"]
|
|
380
|
+
services.add(f" [dim cyan]↳ Using: {binding}[/]")
|
|
381
|
+
|
|
382
|
+
# 7 Pillars
|
|
383
|
+
pillars = tree.add("[bold]7 Pillars[/]")
|
|
384
|
+
soul_ok = self.soul.name != "LollmsBot" or len(self.soul.traits) > 4
|
|
385
|
+
pillars.add(f"{'✅' if soul_ok else '⭕'} [cyan]Soul[/] (identity)")
|
|
386
|
+
pillars.add("✅ [cyan]Guardian[/] (security) - always active")
|
|
387
|
+
|
|
388
|
+
hb_config = self.heartbeat.config
|
|
389
|
+
pillars.add(f"{'✅' if hb_config.enabled else '⭕'} [cyan]Heartbeat[/] ({hb_config.interval_minutes}min)")
|
|
390
|
+
pillars.add(f"⭕ [dim]Memory[/] (configure in Heartbeat)")
|
|
391
|
+
|
|
392
|
+
# Skills
|
|
393
|
+
skill_count = len(self.skill_registry._skills)
|
|
394
|
+
pillars.add(f"{'✅' if skill_count > 5 else '⭕'} [cyan]Skills[/] ({skill_count} loaded)")
|
|
395
|
+
|
|
396
|
+
pillars.add("⭕ [dim]Tools[/] (enabled by default)")
|
|
397
|
+
pillars.add("⭕ [dim]Identity[/] (configure in Soul)")
|
|
398
|
+
|
|
399
|
+
console.print(tree)
|
|
400
|
+
console.print()
|
|
401
|
+
|
|
402
|
+
def configure_backend(self) -> None:
|
|
403
|
+
"""Configure AI backend with binding-first selection."""
|
|
404
|
+
console.print("\n[bold blue]🔗 AI Backend Configuration[/]")
|
|
405
|
+
console.print("[dim]Select your LLM provider and configure connection details[/]")
|
|
406
|
+
console.print()
|
|
407
|
+
|
|
408
|
+
# Step 1: Select binding category
|
|
409
|
+
console.print("[bold]Step 1: Choose binding category[/]")
|
|
410
|
+
|
|
411
|
+
category = questionary.select(
|
|
412
|
+
"What type of backend?",
|
|
413
|
+
choices=[
|
|
414
|
+
Choice("🌐 Remote / Cloud APIs (OpenAI, Claude, etc.)", "remote"),
|
|
415
|
+
Choice("🏠 Local Server (Ollama, vLLM, Llama.cpp, etc.)", "local_server"),
|
|
416
|
+
Choice("💻 Local Direct (Transformers, TensorRT - no server)", "local_direct"),
|
|
417
|
+
],
|
|
418
|
+
use_indicator=True,
|
|
419
|
+
).ask()
|
|
420
|
+
|
|
421
|
+
# Step 2: Select specific binding from category
|
|
422
|
+
console.print(f"\n[bold]Step 2: Select {category.replace('_', ' ').title()} binding[/]")
|
|
423
|
+
|
|
424
|
+
# Filter bindings by category
|
|
425
|
+
category_bindings = {
|
|
426
|
+
name: info for name, info in AVAILABLE_BINDINGS.items()
|
|
427
|
+
if info.category == category
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# Create choices with descriptions
|
|
431
|
+
binding_choices = [
|
|
432
|
+
Choice(
|
|
433
|
+
f"{info.display_name} - {info.description}",
|
|
434
|
+
name
|
|
435
|
+
)
|
|
436
|
+
for name, info in sorted(
|
|
437
|
+
category_bindings.items(),
|
|
438
|
+
key=lambda x: x[1].display_name
|
|
439
|
+
)
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
binding_name = questionary.select(
|
|
443
|
+
"Which binding?",
|
|
444
|
+
choices=binding_choices,
|
|
445
|
+
use_indicator=True,
|
|
446
|
+
).ask()
|
|
447
|
+
|
|
448
|
+
binding_info = AVAILABLE_BINDINGS[binding_name]
|
|
449
|
+
|
|
450
|
+
# Step 3: Configure based on binding type
|
|
451
|
+
console.print(f"\n[bold]Step 3: Configure {binding_info.display_name}[/]")
|
|
452
|
+
|
|
453
|
+
lollms_config = self.config.setdefault("lollms", {})
|
|
454
|
+
lollms_config["binding_name"] = binding_name
|
|
455
|
+
|
|
456
|
+
# Common configuration
|
|
457
|
+
console.print(Panel(
|
|
458
|
+
f"[bold]{binding_info.display_name}[/]\n"
|
|
459
|
+
f"Category: {binding_info.category.replace('_', ' ').title()}\n"
|
|
460
|
+
f"Description: {binding_info.description}",
|
|
461
|
+
title="Selected Binding",
|
|
462
|
+
border_style="green"
|
|
463
|
+
))
|
|
464
|
+
|
|
465
|
+
# Model name (required for all)
|
|
466
|
+
default_model = binding_info.default_model or ""
|
|
467
|
+
current_model = lollms_config.get("model_name", default_model)
|
|
468
|
+
model_name = questionary.text(
|
|
469
|
+
"Model name",
|
|
470
|
+
default=current_model,
|
|
471
|
+
instruction="e.g., gpt-4o-mini, llama3.2, claude-3-5-sonnet-20241022"
|
|
472
|
+
).ask()
|
|
473
|
+
lollms_config["model_name"] = model_name
|
|
474
|
+
|
|
475
|
+
# Host address (for remote and local_server)
|
|
476
|
+
if binding_info.category in ("remote", "local_server"):
|
|
477
|
+
default_host = binding_info.default_host or "http://localhost:8080"
|
|
478
|
+
current_host = lollms_config.get("host_address", default_host)
|
|
479
|
+
host_address = questionary.text(
|
|
480
|
+
"Host address / API endpoint",
|
|
481
|
+
default=current_host,
|
|
482
|
+
instruction="Full URL including http:// or https://"
|
|
483
|
+
).ask()
|
|
484
|
+
lollms_config["host_address"] = host_address
|
|
485
|
+
|
|
486
|
+
# API key / service key
|
|
487
|
+
if binding_info.requires_api_key:
|
|
488
|
+
has_key = questionary.confirm(
|
|
489
|
+
"Do you have an API key / service key?",
|
|
490
|
+
default=True
|
|
491
|
+
).ask()
|
|
492
|
+
|
|
493
|
+
if has_key:
|
|
494
|
+
current_key = lollms_config.get("api_key", "")
|
|
495
|
+
api_key = questionary.password(
|
|
496
|
+
"API / Service key",
|
|
497
|
+
default=current_key,
|
|
498
|
+
).ask()
|
|
499
|
+
lollms_config["api_key"] = api_key
|
|
500
|
+
else:
|
|
501
|
+
console.print("[yellow]⚠️ Most remote APIs require a key. You can add one later.[/]")
|
|
502
|
+
lollms_config["api_key"] = ""
|
|
503
|
+
else:
|
|
504
|
+
# Optional key (e.g., for local servers with optional auth)
|
|
505
|
+
current_key = lollms_config.get("api_key", "")
|
|
506
|
+
if current_key or questionary.confirm(
|
|
507
|
+
"Add optional API key? (for authenticated servers/proxies)",
|
|
508
|
+
default=bool(current_key)
|
|
509
|
+
).ask():
|
|
510
|
+
api_key = questionary.password(
|
|
511
|
+
"API / Service key (optional)",
|
|
512
|
+
default=current_key,
|
|
513
|
+
).ask()
|
|
514
|
+
lollms_config["api_key"] = api_key
|
|
515
|
+
|
|
516
|
+
# SSL verification (if supported)
|
|
517
|
+
if binding_info.supports_ssl_verify:
|
|
518
|
+
default_verify = lollms_config.get("verify_ssl", True)
|
|
519
|
+
# For local servers, default to False for convenience
|
|
520
|
+
if binding_info.category == "local_server" and "localhost" in host_address:
|
|
521
|
+
default_verify = lollms_config.get("verify_ssl", False)
|
|
522
|
+
|
|
523
|
+
verify_ssl = questionary.confirm(
|
|
524
|
+
"Verify SSL certificates?",
|
|
525
|
+
default=default_verify
|
|
526
|
+
).ask()
|
|
527
|
+
lollms_config["verify_ssl"] = verify_ssl
|
|
528
|
+
|
|
529
|
+
if not verify_ssl:
|
|
530
|
+
console.print("[yellow]⚠️ SSL verification disabled. Only use for trusted local servers.[/]")
|
|
531
|
+
|
|
532
|
+
# Custom certificate (advanced)
|
|
533
|
+
if questionary.confirm("Use custom SSL certificate file? (advanced)", default=False).ask():
|
|
534
|
+
cert_path = questionary.text("Path to certificate file (.pem, .crt):").ask()
|
|
535
|
+
lollms_config["certificate_file_path"] = cert_path
|
|
536
|
+
|
|
537
|
+
# Models path (for local direct bindings and some local servers)
|
|
538
|
+
if binding_info.requires_models_path or (
|
|
539
|
+
binding_info.category == "local_direct" and
|
|
540
|
+
questionary.confirm("Specify models folder path?", default=True).ask()
|
|
541
|
+
):
|
|
542
|
+
default_path = str(Path.home() / "models")
|
|
543
|
+
current_path = lollms_config.get("models_path", default_path)
|
|
544
|
+
models_path = questionary.text(
|
|
545
|
+
"Models folder path",
|
|
546
|
+
default=current_path,
|
|
547
|
+
instruction="Directory containing .gguf, .bin, or model files"
|
|
548
|
+
).ask()
|
|
549
|
+
lollms_config["models_path"] = models_path
|
|
550
|
+
|
|
551
|
+
# Expand user path
|
|
552
|
+
models_path_expanded = os.path.expanduser(models_path)
|
|
553
|
+
if not Path(models_path_expanded).exists():
|
|
554
|
+
console.print(f"[yellow]⚠️ Path doesn't exist yet: {models_path_expanded}[/]")
|
|
555
|
+
if questionary.confirm("Create this directory?", default=True).ask():
|
|
556
|
+
Path(models_path_expanded).mkdir(parents=True, exist_ok=True)
|
|
557
|
+
console.print("[green]✅ Directory created[/]")
|
|
558
|
+
|
|
559
|
+
# Step 4: Optional advanced settings
|
|
560
|
+
console.print("\n[bold]Step 4: Advanced settings (optional)[/]")
|
|
561
|
+
|
|
562
|
+
if questionary.confirm("Configure advanced options?", default=False).ask():
|
|
563
|
+
# Context size
|
|
564
|
+
current_ctx = lollms_config.get("context_size", 4096)
|
|
565
|
+
context_size = IntPrompt.ask(
|
|
566
|
+
"Context size (tokens)",
|
|
567
|
+
default=current_ctx
|
|
568
|
+
)
|
|
569
|
+
lollms_config["context_size"] = context_size
|
|
570
|
+
|
|
571
|
+
# Temperature
|
|
572
|
+
current_temp = lollms_config.get("temperature", 0.7)
|
|
573
|
+
temperature = FloatPrompt.ask(
|
|
574
|
+
"Default temperature (0-2)",
|
|
575
|
+
default=current_temp
|
|
576
|
+
)
|
|
577
|
+
lollms_config["temperature"] = max(0.0, min(2.0, temperature))
|
|
578
|
+
|
|
579
|
+
# Summary and test
|
|
580
|
+
console.print("\n[bold green]✅ Backend configured![/]")
|
|
581
|
+
self._configured.add("lollms")
|
|
582
|
+
|
|
583
|
+
# Show configuration summary
|
|
584
|
+
self._show_backend_summary(lollms_config, binding_info)
|
|
585
|
+
|
|
586
|
+
# Offer to test
|
|
587
|
+
if questionary.confirm("Test connection now?", default=True).ask():
|
|
588
|
+
self._test_backend_connection(lollms_config)
|
|
589
|
+
|
|
590
|
+
def _show_backend_summary(self, config: Dict[str, Any], binding_info: BindingInfo) -> None:
|
|
591
|
+
"""Show a summary of the backend configuration."""
|
|
592
|
+
table = Table(title="Backend Configuration Summary")
|
|
593
|
+
table.add_column("Setting", style="cyan")
|
|
594
|
+
table.add_column("Value", style="green")
|
|
595
|
+
|
|
596
|
+
table.add_row("Binding", binding_info.display_name)
|
|
597
|
+
table.add_row("Model", config.get("model_name", "Not set") or "Not set")
|
|
598
|
+
|
|
599
|
+
if binding_info.category in ("remote", "local_server"):
|
|
600
|
+
table.add_row("Host", config.get("host_address", "Not set") or "Not set")
|
|
601
|
+
has_key = bool(config.get("api_key"))
|
|
602
|
+
table.add_row("API Key", "✅ Set" if has_key else "⭕ Not set")
|
|
603
|
+
if binding_info.supports_ssl_verify:
|
|
604
|
+
table.add_row("SSL Verify", "✅ Yes" if config.get("verify_ssl", True) else "❌ No")
|
|
605
|
+
|
|
606
|
+
if binding_info.requires_models_path or config.get("models_path"):
|
|
607
|
+
table.add_row("Models Path", config.get("models_path", "Not set") or "Not set")
|
|
608
|
+
|
|
609
|
+
table.add_row("Context Size", str(config.get("context_size", 4096)))
|
|
610
|
+
table.add_row("Temperature", str(config.get("temperature", 0.7)))
|
|
611
|
+
|
|
612
|
+
console.print(table)
|
|
613
|
+
|
|
614
|
+
def _test_backend_connection(self, config: Dict[str, Any]) -> None:
|
|
615
|
+
"""Test the backend connection."""
|
|
616
|
+
console.print("\n[bold]🧪 Testing connection...[/]")
|
|
617
|
+
|
|
618
|
+
try:
|
|
619
|
+
# Build settings from config
|
|
620
|
+
settings = LollmsSettings(
|
|
621
|
+
host_address=config.get("host_address", ""),
|
|
622
|
+
api_key=config.get("api_key"),
|
|
623
|
+
binding_name=config.get("binding_name"),
|
|
624
|
+
model_name=config.get("model_name"),
|
|
625
|
+
context_size=config.get("context_size", 4096),
|
|
626
|
+
verify_ssl=config.get("verify_ssl", True),
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
# Try to build client
|
|
630
|
+
client = build_lollms_client(settings)
|
|
631
|
+
|
|
632
|
+
if client:
|
|
633
|
+
console.print("[bold green]✅ Client initialized successfully![/]")
|
|
634
|
+
console.print("[dim]Connection appears valid. Full test requires running gateway.[/]")
|
|
635
|
+
else:
|
|
636
|
+
console.print("[yellow]⚠️ Could not initialize client - check configuration[/]")
|
|
637
|
+
|
|
638
|
+
except Exception as e:
|
|
639
|
+
console.print(f"[red]❌ Connection test failed: {e}[/]")
|
|
640
|
+
console.print("[dim]Tip: Ensure the backend service is running and accessible[/]")
|
|
641
|
+
|
|
642
|
+
# Legacy method - kept for backward compatibility but not used in main flow
|
|
643
|
+
def configure_service(self, service_name: str) -> None:
|
|
644
|
+
"""Configure a non-backend service (Discord, Telegram)."""
|
|
645
|
+
if service_name == "lollms":
|
|
646
|
+
# Redirect to new binding-first configuration
|
|
647
|
+
return self.configure_backend()
|
|
648
|
+
|
|
649
|
+
# Legacy configuration for other services
|
|
650
|
+
SERVICES_CONFIG: Dict[str, Dict[str, Any]] = {
|
|
651
|
+
"discord": {
|
|
652
|
+
"title": "🤖 Discord Bot",
|
|
653
|
+
"fields": [
|
|
654
|
+
{"name": "bot_token", "prompt": "Discord Bot Token", "secret": True},
|
|
655
|
+
{"name": "allowed_users", "prompt": "Allowed User IDs (comma-separated, optional)", "optional": True},
|
|
656
|
+
{"name": "allowed_guilds", "prompt": "Allowed Server IDs (comma-separated, optional)", "optional": True},
|
|
657
|
+
],
|
|
658
|
+
"setup_instructions": """🤖 Discord Setup (2 min):
|
|
659
|
+
|
|
660
|
+
1. https://discord.com/developers/applications → [+ New Application]
|
|
661
|
+
2. Bot → [Add Bot] → Copy **TOKEN** (MTIz... format)
|
|
662
|
+
3. Bot → Privileged Gateway Intents → ✅ Message Content
|
|
663
|
+
4. OAuth2 → URL Generator → bot scope → Invite to server""",
|
|
664
|
+
},
|
|
665
|
+
"telegram": {
|
|
666
|
+
"title": "✈️ Telegram Bot",
|
|
667
|
+
"fields": [
|
|
668
|
+
{"name": "bot_token", "prompt": "Telegram Bot Token (from @BotFather)", "secret": True},
|
|
669
|
+
{"name": "allowed_users", "prompt": "Allowed User IDs (comma-separated, optional)", "optional": True},
|
|
670
|
+
],
|
|
671
|
+
"setup_instructions": """✈️ Telegram Setup (1 min):
|
|
672
|
+
|
|
673
|
+
1. Message @BotFather on Telegram
|
|
674
|
+
2. Send /newbot and follow instructions
|
|
675
|
+
3. Copy the HTTP API token provided""",
|
|
676
|
+
},
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
service = SERVICES_CONFIG.get(service_name)
|
|
680
|
+
if not service:
|
|
681
|
+
console.print(f"[red]Unknown service: {service_name}[/]")
|
|
682
|
+
return
|
|
683
|
+
|
|
684
|
+
console.print(f"\n[bold yellow]{service['title']}[/]")
|
|
685
|
+
|
|
686
|
+
if "setup_instructions" in service:
|
|
687
|
+
console.print(Panel(service["setup_instructions"], title="📋 Instructions"))
|
|
688
|
+
|
|
689
|
+
service_config = self.config.setdefault(service_name, {})
|
|
690
|
+
|
|
691
|
+
for field in service["fields"]:
|
|
692
|
+
current = service_config.get(field["name"])
|
|
693
|
+
default = str(current) if current is not None else field.get("default", "")
|
|
694
|
+
|
|
695
|
+
if field.get("type") == "bool":
|
|
696
|
+
value = questionary.confirm(field["prompt"], default=field.get("default", False)).ask()
|
|
697
|
+
elif field.get("secret"):
|
|
698
|
+
value = questionary.password(field["prompt"], default=default).ask()
|
|
699
|
+
else:
|
|
700
|
+
value = questionary.text(field["prompt"], default=default).ask()
|
|
701
|
+
|
|
702
|
+
# Parse comma-separated lists
|
|
703
|
+
if "users" in field["name"] or "guilds" in field["name"]:
|
|
704
|
+
if value:
|
|
705
|
+
value = [v.strip() for v in value.split(",") if v.strip()]
|
|
706
|
+
else:
|
|
707
|
+
value = []
|
|
708
|
+
elif field.get("optional") and not value:
|
|
709
|
+
continue
|
|
710
|
+
|
|
711
|
+
service_config[field["name"]] = value
|
|
712
|
+
|
|
713
|
+
self._configured.add(service_name)
|
|
714
|
+
console.print("[green]✅ Updated![/]")
|
|
715
|
+
|
|
716
|
+
def configure_soul(self) -> None:
|
|
717
|
+
"""Interactive Soul (personality) configuration."""
|
|
718
|
+
console.print("\n[bold magenta]🧬 Soul Configuration[/]")
|
|
719
|
+
|
|
720
|
+
while True:
|
|
721
|
+
section = questionary.select(
|
|
722
|
+
"Configure aspect:",
|
|
723
|
+
choices=[
|
|
724
|
+
"🎭 Core Identity (name, purpose, origin)",
|
|
725
|
+
"🌈 Personality Traits",
|
|
726
|
+
"⚖️ Core Values",
|
|
727
|
+
"💬 Communication Style",
|
|
728
|
+
"🎓 Expertise Domains",
|
|
729
|
+
"👥 Relationship Stances",
|
|
730
|
+
"🔍 Preview System Prompt",
|
|
731
|
+
"💾 Save & Return",
|
|
732
|
+
]
|
|
733
|
+
).ask()
|
|
734
|
+
|
|
735
|
+
if section == "🎭 Core Identity (name, purpose, origin)":
|
|
736
|
+
self._configure_core_identity()
|
|
737
|
+
elif section == "🌈 Personality Traits":
|
|
738
|
+
self._configure_personality_traits()
|
|
739
|
+
elif section == "⚖️ Core Values":
|
|
740
|
+
self._configure_values()
|
|
741
|
+
elif section == "💬 Communication Style":
|
|
742
|
+
self._configure_communication()
|
|
743
|
+
elif section == "🎓 Expertise Domains":
|
|
744
|
+
self._configure_expertise()
|
|
745
|
+
elif section == "👥 Relationship Stances":
|
|
746
|
+
self._configure_relationships()
|
|
747
|
+
elif section == "🔍 Preview System Prompt":
|
|
748
|
+
self._preview_soul_prompt()
|
|
749
|
+
else:
|
|
750
|
+
self.soul._save()
|
|
751
|
+
self._configured.add("soul")
|
|
752
|
+
console.print("[green]✅ Soul saved![/]")
|
|
753
|
+
break
|
|
754
|
+
|
|
755
|
+
def _configure_core_identity(self) -> None:
|
|
756
|
+
"""Configure name, purpose, and origin story."""
|
|
757
|
+
console.print("\n[bold]Core Identity[/]")
|
|
758
|
+
|
|
759
|
+
self.soul.name = questionary.text("AI Name", default=self.soul.name).ask()
|
|
760
|
+
self.soul.purpose = questionary.text("Primary Purpose", default=self.soul.purpose).ask()
|
|
761
|
+
self.soul.origin_story = questionary.text("Origin Story", default=self.soul.origin_story).ask()
|
|
762
|
+
|
|
763
|
+
def _configure_personality_traits(self) -> None:
|
|
764
|
+
"""Add, edit, or remove personality traits."""
|
|
765
|
+
console.print("\n[bold]Personality Traits[/]")
|
|
766
|
+
|
|
767
|
+
while True:
|
|
768
|
+
table = Table(title="Current Traits")
|
|
769
|
+
table.add_column("Trait")
|
|
770
|
+
table.add_column("Intensity")
|
|
771
|
+
table.add_column("Description")
|
|
772
|
+
|
|
773
|
+
for trait in self.soul.traits:
|
|
774
|
+
intensity_emoji = {
|
|
775
|
+
TraitIntensity.SUBTLE: "◐",
|
|
776
|
+
TraitIntensity.MODERATE: "◑",
|
|
777
|
+
TraitIntensity.STRONG: "◕",
|
|
778
|
+
TraitIntensity.EXTREME: "⬤",
|
|
779
|
+
}.get(trait.intensity, "◑")
|
|
780
|
+
table.add_row(trait.name, f"{intensity_emoji} {trait.intensity.name.lower()}", trait.description[:40])
|
|
781
|
+
|
|
782
|
+
console.print(table)
|
|
783
|
+
|
|
784
|
+
action = questionary.select(
|
|
785
|
+
"Action:",
|
|
786
|
+
choices=["➕ Add Trait", "✏️ Edit Trait", "🗑️ Remove Trait", "🔙 Back"]
|
|
787
|
+
).ask()
|
|
788
|
+
|
|
789
|
+
if action == "➕ Add Trait":
|
|
790
|
+
name = questionary.text("Trait name (e.g., 'curiosity', 'pragmatism')").ask()
|
|
791
|
+
description = questionary.text("How does this manifest?").ask()
|
|
792
|
+
intensity = questionary.select(
|
|
793
|
+
"Intensity",
|
|
794
|
+
choices=["subtle", "moderate", "strong", "extreme"],
|
|
795
|
+
default="moderate"
|
|
796
|
+
).ask()
|
|
797
|
+
|
|
798
|
+
trait = PersonalityTrait(
|
|
799
|
+
name=name,
|
|
800
|
+
description=description,
|
|
801
|
+
intensity=TraitIntensity[intensity.upper()],
|
|
802
|
+
)
|
|
803
|
+
self.soul.traits.append(trait)
|
|
804
|
+
|
|
805
|
+
elif action == "✏️ Edit Trait" and self.soul.traits:
|
|
806
|
+
trait_names = [t.name for t in self.soul.traits]
|
|
807
|
+
to_edit = questionary.select("Edit which trait?", choices=trait_names).ask()
|
|
808
|
+
trait = next(t for t in self.soul.traits if t.name == to_edit)
|
|
809
|
+
|
|
810
|
+
trait.description = questionary.text("Description", default=trait.description).ask()
|
|
811
|
+
new_intensity = questionary.select(
|
|
812
|
+
"Intensity",
|
|
813
|
+
choices=["subtle", "moderate", "strong", "extreme"],
|
|
814
|
+
default=trait.intensity.name.lower()
|
|
815
|
+
).ask()
|
|
816
|
+
trait.intensity = TraitIntensity[new_intensity.upper()]
|
|
817
|
+
|
|
818
|
+
elif action == "🗑️ Remove Trait" and self.soul.traits:
|
|
819
|
+
to_remove = questionary.select(
|
|
820
|
+
"Remove which trait?",
|
|
821
|
+
choices=[t.name for t in self.soul.traits]
|
|
822
|
+
).ask()
|
|
823
|
+
self.soul.traits = [t for t in self.soul.traits if t.name != to_remove]
|
|
824
|
+
else:
|
|
825
|
+
break
|
|
826
|
+
|
|
827
|
+
def _configure_values(self) -> None:
|
|
828
|
+
"""Configure core ethical values."""
|
|
829
|
+
console.print("\n[bold]Core Values[/]")
|
|
830
|
+
|
|
831
|
+
while True:
|
|
832
|
+
table = Table(title="Current Values (by priority)")
|
|
833
|
+
table.add_column("Priority")
|
|
834
|
+
table.add_column("Value")
|
|
835
|
+
table.add_column("Category")
|
|
836
|
+
|
|
837
|
+
for v in sorted(self.soul.values, key=lambda x: -x.priority):
|
|
838
|
+
priority_color = "red" if v.priority >= 9 else "yellow" if v.priority >= 7 else "green"
|
|
839
|
+
table.add_row(f"[{priority_color}]{v.priority}[/{priority_color}]", v.statement[:50], v.category)
|
|
840
|
+
|
|
841
|
+
console.print(table)
|
|
842
|
+
|
|
843
|
+
action = questionary.select(
|
|
844
|
+
"Action:",
|
|
845
|
+
choices=["➕ Add Value", "✏️ Edit Priority", "🗑️ Remove Value", "🔙 Back"]
|
|
846
|
+
).ask()
|
|
847
|
+
|
|
848
|
+
if action == "➕ Add Value":
|
|
849
|
+
statement = questionary.text("Value statement").ask()
|
|
850
|
+
category = questionary.text("Category", default="general").ask()
|
|
851
|
+
priority = IntPrompt.ask("Priority (1-10)", default=5)
|
|
852
|
+
self.soul.values.append(ValueStatement(statement, category, max(1, min(10, priority))))
|
|
853
|
+
|
|
854
|
+
elif action == "✏️ Edit Priority" and self.soul.values:
|
|
855
|
+
statements = [v.statement[:40] + "..." for v in self.soul.values]
|
|
856
|
+
to_edit = questionary.select("Edit which value?", choices=statements).ask()
|
|
857
|
+
val = next(v for v in self.soul.values if v.statement.startswith(to_edit[:20]))
|
|
858
|
+
val.priority = IntPrompt.ask("New priority (1-10)", default=val.priority)
|
|
859
|
+
|
|
860
|
+
elif action == "🗑️ Remove Value" and self.soul.values:
|
|
861
|
+
to_remove = questionary.select(
|
|
862
|
+
"Remove which value?",
|
|
863
|
+
choices=[v.statement[:40] for v in self.soul.values]
|
|
864
|
+
).ask()
|
|
865
|
+
self.soul.values = [v for v in self.soul.values if not v.statement.startswith(to_remove[:20])]
|
|
866
|
+
else:
|
|
867
|
+
break
|
|
868
|
+
|
|
869
|
+
def _configure_communication(self) -> None:
|
|
870
|
+
"""Configure communication style."""
|
|
871
|
+
style = self.soul.communication
|
|
872
|
+
|
|
873
|
+
style.formality = questionary.select(
|
|
874
|
+
"Formality",
|
|
875
|
+
choices=["formal", "casual", "technical", "playful"],
|
|
876
|
+
default=style.formality
|
|
877
|
+
).ask()
|
|
878
|
+
|
|
879
|
+
style.verbosity = questionary.select(
|
|
880
|
+
"Default verbosity",
|
|
881
|
+
choices=["terse", "concise", "detailed", "exhaustive"],
|
|
882
|
+
default=style.verbosity
|
|
883
|
+
).ask()
|
|
884
|
+
|
|
885
|
+
humor = questionary.select(
|
|
886
|
+
"Humor style",
|
|
887
|
+
choices=["None (serious)", "witty", "dry", "punny", "absurdist"],
|
|
888
|
+
default=style.humor_style or "None (serious)"
|
|
889
|
+
).ask()
|
|
890
|
+
style.humor_style = None if humor == "None (serious)" else humor
|
|
891
|
+
|
|
892
|
+
style.emoji_usage = questionary.select(
|
|
893
|
+
"Emoji usage",
|
|
894
|
+
choices=["none", "minimal", "moderate", "liberal"],
|
|
895
|
+
default=style.emoji_usage
|
|
896
|
+
).ask()
|
|
897
|
+
|
|
898
|
+
def _configure_expertise(self) -> None:
|
|
899
|
+
"""Configure knowledge domains."""
|
|
900
|
+
console.print("\n[bold]Expertise Domains[/]")
|
|
901
|
+
|
|
902
|
+
while True:
|
|
903
|
+
table = Table(title="Current Expertise")
|
|
904
|
+
table.add_column("Domain")
|
|
905
|
+
table.add_column("Level")
|
|
906
|
+
table.add_column("Specialties")
|
|
907
|
+
|
|
908
|
+
for e in self.soul.expertise:
|
|
909
|
+
level_color = {
|
|
910
|
+
"novice": "red", "competent": "yellow", "expert": "green",
|
|
911
|
+
"authority": "blue", "pioneer": "magenta",
|
|
912
|
+
}.get(e.level, "white")
|
|
913
|
+
table.add_row(e.domain, f"[{level_color}]{e.level}[/{level_color}]", ", ".join(e.specialties[:2]))
|
|
914
|
+
|
|
915
|
+
console.print(table)
|
|
916
|
+
|
|
917
|
+
action = questionary.select(
|
|
918
|
+
"Action:",
|
|
919
|
+
choices=["➕ Add Domain", "🔙 Back"]
|
|
920
|
+
).ask()
|
|
921
|
+
|
|
922
|
+
if action == "➕ Add Domain":
|
|
923
|
+
domain = questionary.text("Domain name").ask()
|
|
924
|
+
level = questionary.select(
|
|
925
|
+
"Competence level",
|
|
926
|
+
choices=["novice", "competent", "expert", "authority", "pioneer"],
|
|
927
|
+
default="competent"
|
|
928
|
+
).ask()
|
|
929
|
+
specialties = [s.strip() for s in questionary.text("Specialties (comma-separated)").ask().split(",") if s.strip()]
|
|
930
|
+
|
|
931
|
+
self.soul.expertise.append(ExpertiseDomain(domain=domain, level=level, specialties=specialties))
|
|
932
|
+
else:
|
|
933
|
+
break
|
|
934
|
+
|
|
935
|
+
def _configure_relationships(self) -> None:
|
|
936
|
+
"""Configure relationship stances."""
|
|
937
|
+
console.print("\n[bold]Relationship Stances[/]")
|
|
938
|
+
console.print("[dim]Simplified configuration - full implementation in soul.md[/]")
|
|
939
|
+
|
|
940
|
+
def _preview_soul_prompt(self) -> None:
|
|
941
|
+
"""Preview the generated system prompt."""
|
|
942
|
+
prompt = self.soul.generate_system_prompt()
|
|
943
|
+
preview = prompt[:1000] + ("..." if len(prompt) > 1000 else "")
|
|
944
|
+
console.print(Panel(preview, title="System Prompt", border_style="cyan"))
|
|
945
|
+
|
|
946
|
+
def configure_heartbeat(self) -> None:
|
|
947
|
+
"""Configure self-maintenance heartbeat."""
|
|
948
|
+
console.print("\n[bold magenta]💓 Heartbeat Configuration[/]")
|
|
949
|
+
|
|
950
|
+
config = self.heartbeat.config
|
|
951
|
+
|
|
952
|
+
config.enabled = questionary.confirm("Enable automatic self-maintenance?", default=config.enabled).ask()
|
|
953
|
+
if not config.enabled:
|
|
954
|
+
self.heartbeat._save_config()
|
|
955
|
+
return
|
|
956
|
+
|
|
957
|
+
config.interval_minutes = FloatPrompt.ask("Maintenance interval (minutes)", default=config.interval_minutes)
|
|
958
|
+
|
|
959
|
+
console.print("\n[bold]Maintenance Tasks[/]")
|
|
960
|
+
for task in MaintenanceTask:
|
|
961
|
+
task_name = task.name.replace("_", " ").title()
|
|
962
|
+
config.tasks_enabled[task] = questionary.confirm(
|
|
963
|
+
f"Enable {task_name}?", default=config.tasks_enabled.get(task, True)
|
|
964
|
+
).ask()
|
|
965
|
+
|
|
966
|
+
console.print("\n[bold]Self-Healing Behavior[/]")
|
|
967
|
+
config.auto_heal_minor = questionary.confirm("Auto-fix minor issues?", default=config.auto_heal_minor).ask()
|
|
968
|
+
config.confirm_heal_major = questionary.confirm("Confirm before major changes?", default=config.confirm_heal_major).ask()
|
|
969
|
+
|
|
970
|
+
self.heartbeat.update_config(**{
|
|
971
|
+
k: getattr(config, k) for k in [
|
|
972
|
+
"enabled", "interval_minutes", "tasks_enabled",
|
|
973
|
+
"auto_heal_minor", "confirm_heal_major"
|
|
974
|
+
]
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
self.config["heartbeat"] = {
|
|
978
|
+
"enabled": config.enabled,
|
|
979
|
+
"interval_minutes": config.interval_minutes,
|
|
980
|
+
"tasks_enabled": [t.name for t, v in config.tasks_enabled.items() if v],
|
|
981
|
+
}
|
|
982
|
+
self._configured.add("heartbeat")
|
|
983
|
+
console.print("[green]✅ Heartbeat configured![/]")
|
|
984
|
+
|
|
985
|
+
def configure_memory(self) -> None:
|
|
986
|
+
"""Configure memory and retention settings."""
|
|
987
|
+
console.print("\n[bold magenta]🧠 Memory Configuration[/]")
|
|
988
|
+
|
|
989
|
+
hb_config = self.heartbeat.config
|
|
990
|
+
|
|
991
|
+
hb_config.memory_pressure_threshold = FloatPrompt.ask(
|
|
992
|
+
"Memory pressure threshold (0-1)", default=hb_config.memory_pressure_threshold
|
|
993
|
+
)
|
|
994
|
+
hb_config.log_retention_days = IntPrompt.ask(
|
|
995
|
+
"Audit log retention (days)", default=hb_config.log_retention_days
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
console.print("\n[bold]Forgetting Curve Parameters[/]")
|
|
999
|
+
halflife = FloatPrompt.ask("Memory half-life (days)", default=7.0)
|
|
1000
|
+
strength_mult = FloatPrompt.ask("Review strength multiplier", default=2.0)
|
|
1001
|
+
|
|
1002
|
+
self.heartbeat.memory_monitor.retention_halflife_days = halflife
|
|
1003
|
+
self.heartbeat.memory_monitor.strength_multiplier = strength_mult
|
|
1004
|
+
self.heartbeat._save_config()
|
|
1005
|
+
|
|
1006
|
+
self.config["memory"] = {
|
|
1007
|
+
"pressure_threshold": hb_config.memory_pressure_threshold,
|
|
1008
|
+
"log_retention_days": hb_config.log_retention_days,
|
|
1009
|
+
"retention_halflife_days": halflife,
|
|
1010
|
+
"strength_multiplier": strength_mult,
|
|
1011
|
+
}
|
|
1012
|
+
self._configured.add("memory")
|
|
1013
|
+
console.print("[green]✅ Memory configured![/]")
|
|
1014
|
+
|
|
1015
|
+
def configure_skills(self) -> None:
|
|
1016
|
+
"""Configure Skills - browse, test, and manage capabilities."""
|
|
1017
|
+
console.print("\n[bold magenta]📚 Skills Configuration[/]")
|
|
1018
|
+
console.print("[dim]Browse, test, and configure LollmsBot's capabilities[/]")
|
|
1019
|
+
|
|
1020
|
+
while True:
|
|
1021
|
+
# Show skill statistics
|
|
1022
|
+
stats = self._get_skill_stats()
|
|
1023
|
+
|
|
1024
|
+
table = Table(title=f"Skills Library ({stats['total']} total)")
|
|
1025
|
+
table.add_column("Category")
|
|
1026
|
+
table.add_column("Built-in")
|
|
1027
|
+
table.add_column("User-created")
|
|
1028
|
+
table.add_column("Avg Confidence")
|
|
1029
|
+
|
|
1030
|
+
for cat, data in sorted(stats['by_category'].items()):
|
|
1031
|
+
table.add_row(
|
|
1032
|
+
cat,
|
|
1033
|
+
str(data['builtin']),
|
|
1034
|
+
str(data['user']),
|
|
1035
|
+
f"{data['avg_confidence']:.0%}"
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
console.print(table)
|
|
1039
|
+
|
|
1040
|
+
action = questionary.select(
|
|
1041
|
+
"Skills action:",
|
|
1042
|
+
choices=[
|
|
1043
|
+
"🔍 Browse & Search Skills",
|
|
1044
|
+
"📖 View Skill Details",
|
|
1045
|
+
"🧪 Test Skill Execution",
|
|
1046
|
+
"➕ Compose New Skill (from existing)",
|
|
1047
|
+
"📤 Export Skill Library",
|
|
1048
|
+
"📥 Import Skills",
|
|
1049
|
+
"⚙️ Skill Preferences",
|
|
1050
|
+
"🔙 Back to Main Menu",
|
|
1051
|
+
]
|
|
1052
|
+
).ask()
|
|
1053
|
+
|
|
1054
|
+
if action == "🔍 Browse & Search Skills":
|
|
1055
|
+
self._browse_skills()
|
|
1056
|
+
elif action == "📖 View Skill Details":
|
|
1057
|
+
self._view_skill_details()
|
|
1058
|
+
elif action == "🧪 Test Skill Execution":
|
|
1059
|
+
self._test_skill()
|
|
1060
|
+
elif action == "➕ Compose New Skill (from existing)":
|
|
1061
|
+
self._compose_skill()
|
|
1062
|
+
elif action == "📤 Export Skill Library":
|
|
1063
|
+
self._export_skills()
|
|
1064
|
+
elif action == "📥 Import Skills":
|
|
1065
|
+
self._import_skills()
|
|
1066
|
+
elif action == "⚙️ Skill Preferences":
|
|
1067
|
+
self._skill_preferences()
|
|
1068
|
+
else:
|
|
1069
|
+
self._configured.add("skills")
|
|
1070
|
+
break
|
|
1071
|
+
|
|
1072
|
+
def _get_skill_stats(self) -> Dict[str, Any]:
|
|
1073
|
+
"""Get statistics about loaded skills."""
|
|
1074
|
+
skills = list(self.skill_registry._skills.values())
|
|
1075
|
+
|
|
1076
|
+
by_category: Dict[str, Dict[str, Any]] = {}
|
|
1077
|
+
for skill in skills:
|
|
1078
|
+
for cat in skill.metadata.categories or ["uncategorized"]:
|
|
1079
|
+
if cat not in by_category:
|
|
1080
|
+
by_category[cat] = {'builtin': 0, 'user': 0, 'confidence_sum': 0, 'count': 0}
|
|
1081
|
+
# Simplified: would track builtin vs user properly
|
|
1082
|
+
by_category[cat]['count'] += 1
|
|
1083
|
+
by_category[cat]['confidence_sum'] += skill.metadata.confidence_score
|
|
1084
|
+
|
|
1085
|
+
# Calculate averages
|
|
1086
|
+
for cat in by_category:
|
|
1087
|
+
data = by_category[cat]
|
|
1088
|
+
data['avg_confidence'] = data['confidence_sum'] / data['count'] if data['count'] > 0 else 0
|
|
1089
|
+
|
|
1090
|
+
return {
|
|
1091
|
+
'total': len(skills),
|
|
1092
|
+
'by_category': by_category,
|
|
1093
|
+
'by_complexity': {
|
|
1094
|
+
c.name: len(self.skill_registry.list_skills(complexity=c))
|
|
1095
|
+
for c in SkillComplexity
|
|
1096
|
+
},
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
def _browse_skills(self) -> None:
|
|
1100
|
+
"""Browse and search skills interactively."""
|
|
1101
|
+
search = questionary.text("Search skills (empty for all):").ask()
|
|
1102
|
+
|
|
1103
|
+
if search:
|
|
1104
|
+
results = self.skill_registry.search(search)
|
|
1105
|
+
skills = [s for s, _ in results]
|
|
1106
|
+
else:
|
|
1107
|
+
category = questionary.select(
|
|
1108
|
+
"Filter by category:",
|
|
1109
|
+
choices=["All"] + list(self.skill_registry._categories.keys())
|
|
1110
|
+
).ask()
|
|
1111
|
+
if category == "All":
|
|
1112
|
+
skills = list(self.skill_registry._skills.values())
|
|
1113
|
+
else:
|
|
1114
|
+
skills = self.skill_registry.list_skills(category=category)
|
|
1115
|
+
|
|
1116
|
+
# Display results
|
|
1117
|
+
table = Table(title=f"Skills ({len(skills)} found)")
|
|
1118
|
+
table.add_column("Name")
|
|
1119
|
+
table.add_column("Complexity")
|
|
1120
|
+
table.add_column("Description")
|
|
1121
|
+
table.add_column("Confidence")
|
|
1122
|
+
|
|
1123
|
+
for skill in skills[:20]: # Limit display
|
|
1124
|
+
conf_color = "green" if skill.metadata.confidence_score > 0.8 else "yellow" if skill.metadata.confidence_score > 0.5 else "red"
|
|
1125
|
+
table.add_row(
|
|
1126
|
+
skill.name,
|
|
1127
|
+
skill.metadata.complexity.name,
|
|
1128
|
+
skill.metadata.description[:40],
|
|
1129
|
+
f"[{conf_color}]{skill.metadata.confidence_score:.0%}[/{conf_color}]"
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
console.print(table)
|
|
1133
|
+
|
|
1134
|
+
def _view_skill_details(self) -> None:
|
|
1135
|
+
"""View detailed information about a specific skill."""
|
|
1136
|
+
skill_name = questionary.select(
|
|
1137
|
+
"Select skill:",
|
|
1138
|
+
choices=list(self.skill_registry._skills.keys())
|
|
1139
|
+
).ask()
|
|
1140
|
+
|
|
1141
|
+
skill = self.skill_registry.get(skill_name)
|
|
1142
|
+
if not skill:
|
|
1143
|
+
console.print("[red]Skill not found[/]")
|
|
1144
|
+
return
|
|
1145
|
+
|
|
1146
|
+
md = skill.metadata
|
|
1147
|
+
|
|
1148
|
+
details = f"""
|
|
1149
|
+
[bold]{md.name}[/] v{md.version}
|
|
1150
|
+
[dim]{md.description}[/]
|
|
1151
|
+
|
|
1152
|
+
[bold]Complexity:[/] {md.complexity.name}
|
|
1153
|
+
[bold]Categories:[/] {', '.join(md.categories)}
|
|
1154
|
+
[bold]Tags:[/] {', '.join(md.tags)}
|
|
1155
|
+
|
|
1156
|
+
[bold]When to use:[/] {md.when_to_use or 'N/A'}
|
|
1157
|
+
[bold]When NOT to use:[/] {md.when_not_to_use or 'N/A'}
|
|
1158
|
+
|
|
1159
|
+
[bold]Parameters:[/]
|
|
1160
|
+
{chr(10).join(f" • {p.name} ({p.type}){' [required]' if p.required else ''}: {p.description}" for p in md.parameters)}
|
|
1161
|
+
|
|
1162
|
+
[bold]Dependencies:[/]
|
|
1163
|
+
{chr(10).join(f" • {d.kind}:{d.name}{' (optional)' if d.optional else ''}" for d in md.dependencies)}
|
|
1164
|
+
|
|
1165
|
+
[bold]Statistics:[/]
|
|
1166
|
+
• Executed: {md.execution_count} times
|
|
1167
|
+
• Success rate: {md.success_rate:.1%}
|
|
1168
|
+
• Confidence score: {md.confidence_score:.0%}
|
|
1169
|
+
"""
|
|
1170
|
+
console.print(Panel(details, title=f"Skill: {md.name}", border_style="blue"))
|
|
1171
|
+
|
|
1172
|
+
# Show examples if any
|
|
1173
|
+
if md.examples:
|
|
1174
|
+
console.print("\n[bold]Examples:[/]")
|
|
1175
|
+
for i, ex in enumerate(md.examples[:2], 1):
|
|
1176
|
+
console.print(Panel(
|
|
1177
|
+
f"Input: {json.dumps(ex.input_params, indent=2)}\n"
|
|
1178
|
+
f"Output: {json.dumps(ex.expected_output, indent=2)}",
|
|
1179
|
+
title=f"Example {i}"
|
|
1180
|
+
))
|
|
1181
|
+
|
|
1182
|
+
def _test_skill(self) -> None:
|
|
1183
|
+
"""Test execute a skill with sample inputs."""
|
|
1184
|
+
console.print("[yellow]Note: Full execution requires running agent. Showing validation only.[/]")
|
|
1185
|
+
|
|
1186
|
+
skill_name = questionary.select(
|
|
1187
|
+
"Select skill to test:",
|
|
1188
|
+
choices=list(self.skill_registry._skills.keys())
|
|
1189
|
+
).ask()
|
|
1190
|
+
|
|
1191
|
+
skill = self.skill_registry.get(skill_name)
|
|
1192
|
+
|
|
1193
|
+
# Gather inputs
|
|
1194
|
+
inputs = {}
|
|
1195
|
+
for param in skill.metadata.parameters:
|
|
1196
|
+
if not param.required:
|
|
1197
|
+
if not questionary.confirm(f"Provide optional parameter '{param.name}'?", default=False).ask():
|
|
1198
|
+
continue
|
|
1199
|
+
|
|
1200
|
+
value = questionary.text(f"{param.name} ({param.type}): {param.description}").ask()
|
|
1201
|
+
|
|
1202
|
+
# Simple type coercion
|
|
1203
|
+
if param.type == "number":
|
|
1204
|
+
value = float(value) if '.' in value else int(value)
|
|
1205
|
+
elif param.type == "boolean":
|
|
1206
|
+
value = value.lower() in ('true', 'yes', '1', 'on')
|
|
1207
|
+
elif param.type == "array":
|
|
1208
|
+
value = [v.strip() for v in value.split(',')]
|
|
1209
|
+
elif param.type == "object":
|
|
1210
|
+
try:
|
|
1211
|
+
value = json.loads(value)
|
|
1212
|
+
except:
|
|
1213
|
+
value = {"raw": value}
|
|
1214
|
+
|
|
1215
|
+
inputs[param.name] = value
|
|
1216
|
+
|
|
1217
|
+
# Validate
|
|
1218
|
+
valid, errors = skill.validate_inputs(inputs)
|
|
1219
|
+
if valid:
|
|
1220
|
+
console.print("[green]✅ Inputs valid![/]")
|
|
1221
|
+
|
|
1222
|
+
# Check dependencies
|
|
1223
|
+
# Would need actual agent/tools to check properly
|
|
1224
|
+
console.print("[dim]Dependency check: would validate against available tools[/]")
|
|
1225
|
+
else:
|
|
1226
|
+
console.print("[red]❌ Validation failed:[/]")
|
|
1227
|
+
for err in errors:
|
|
1228
|
+
console.print(f" • {err}")
|
|
1229
|
+
|
|
1230
|
+
def _compose_skill(self) -> None:
|
|
1231
|
+
"""Create new skill by composing existing skills."""
|
|
1232
|
+
console.print("\n[bold]Compose New Skill[/]")
|
|
1233
|
+
console.print("[dim]Combine existing skills into a workflow[/]")
|
|
1234
|
+
|
|
1235
|
+
name = questionary.text("Name for new skill:").ask()
|
|
1236
|
+
description = questionary.text("What does this skill do?").ask()
|
|
1237
|
+
|
|
1238
|
+
# Select component skills
|
|
1239
|
+
available = list(self.skill_registry._skills.keys())
|
|
1240
|
+
components = []
|
|
1241
|
+
|
|
1242
|
+
while True:
|
|
1243
|
+
remaining = [s for s in available if s not in components]
|
|
1244
|
+
if not remaining:
|
|
1245
|
+
break
|
|
1246
|
+
|
|
1247
|
+
choice = questionary.select(
|
|
1248
|
+
"Add component skill (or Done):",
|
|
1249
|
+
choices=["Done"] + remaining
|
|
1250
|
+
).ask()
|
|
1251
|
+
|
|
1252
|
+
if choice == "Done":
|
|
1253
|
+
break
|
|
1254
|
+
|
|
1255
|
+
components.append(choice)
|
|
1256
|
+
console.print(f"[green]Added: {choice}[/]")
|
|
1257
|
+
|
|
1258
|
+
if len(components) < 1:
|
|
1259
|
+
console.print("[yellow]Need at least one component[/]")
|
|
1260
|
+
return
|
|
1261
|
+
|
|
1262
|
+
# Define data flow (simplified)
|
|
1263
|
+
console.print("\n[dim]Data flow would be configured here - mapping outputs to inputs[/]")
|
|
1264
|
+
|
|
1265
|
+
# Preview and confirm
|
|
1266
|
+
console.print(Panel(
|
|
1267
|
+
f"Name: {name}\n"
|
|
1268
|
+
f"Description: {description}\n"
|
|
1269
|
+
f"Components: {' → '.join(components)}",
|
|
1270
|
+
title="New Skill Preview"
|
|
1271
|
+
))
|
|
1272
|
+
|
|
1273
|
+
if questionary.confirm("Create this skill?", default=True).ask():
|
|
1274
|
+
# Would call skill learner
|
|
1275
|
+
console.print("[green]✅ Skill composition recorded (implementation in code)[/]")
|
|
1276
|
+
|
|
1277
|
+
def _export_skills(self) -> None:
|
|
1278
|
+
"""Export skills to file."""
|
|
1279
|
+
export_path = Path.home() / ".lollmsbot" / "skills_export.json"
|
|
1280
|
+
|
|
1281
|
+
data = {
|
|
1282
|
+
"export_date": datetime.now().isoformat(),
|
|
1283
|
+
"skills": [skill.to_dict() for skill in self.skill_registry._skills.values()],
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
export_path.write_text(json.dumps(data, indent=2))
|
|
1287
|
+
console.print(f"[green]✅ Exported {len(data['skills'])} skills to {export_path}[/]")
|
|
1288
|
+
|
|
1289
|
+
def _import_skills(self) -> None:
|
|
1290
|
+
"""Import skills from file."""
|
|
1291
|
+
import_path = questionary.text("Path to skills file:").ask()
|
|
1292
|
+
path = Path(import_path)
|
|
1293
|
+
|
|
1294
|
+
if not path.exists():
|
|
1295
|
+
console.print("[red]File not found[/]")
|
|
1296
|
+
return
|
|
1297
|
+
|
|
1298
|
+
try:
|
|
1299
|
+
data = json.loads(path.read_text())
|
|
1300
|
+
count = len(data.get("skills", []))
|
|
1301
|
+
console.print(f"[green]✅ Found {count} skills to import[/]")
|
|
1302
|
+
console.print("[dim]Import would validate and register skills here[/]")
|
|
1303
|
+
except Exception as e:
|
|
1304
|
+
console.print(f"[red]Import failed: {e}[/]")
|
|
1305
|
+
|
|
1306
|
+
def _skill_preferences(self) -> None:
|
|
1307
|
+
"""Configure skill execution preferences."""
|
|
1308
|
+
console.print("\n[bold]Skill Preferences[/]")
|
|
1309
|
+
|
|
1310
|
+
# Would configure: auto-skill vs manual, confidence thresholds, etc.
|
|
1311
|
+
prefs = {
|
|
1312
|
+
"auto_skill_selection": questionary.confirm("Allow automatic skill selection?", default=True).ask(),
|
|
1313
|
+
"min_confidence_threshold": FloatPrompt.ask("Minimum skill confidence (0-1)", default=0.6),
|
|
1314
|
+
"confirm_complex_skills": questionary.confirm("Confirm before complex skill execution?", default=True).ask(),
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
self.config["skill_preferences"] = prefs
|
|
1318
|
+
console.print("[green]✅ Preferences saved[/]")
|
|
1319
|
+
|
|
1320
|
+
def test_connections(self) -> None:
|
|
1321
|
+
"""Test all configured connections."""
|
|
1322
|
+
table = Table(title="🧪 Connection Tests")
|
|
1323
|
+
table.add_column("Service")
|
|
1324
|
+
table.add_column("Status")
|
|
1325
|
+
table.add_column("Details")
|
|
1326
|
+
|
|
1327
|
+
# Test backend first
|
|
1328
|
+
lollms_config = self.config.get("lollms", {})
|
|
1329
|
+
if lollms_config:
|
|
1330
|
+
status, details = self._test_single("lollms", lollms_config)
|
|
1331
|
+
# Show binding name in details
|
|
1332
|
+
binding_name = lollms_config.get("binding_name", "unknown")
|
|
1333
|
+
details = f"{binding_name}: {details}"
|
|
1334
|
+
table.add_row("AI Backend", status, details)
|
|
1335
|
+
|
|
1336
|
+
# Test other services
|
|
1337
|
+
for service_name, svc_config in self.config.items():
|
|
1338
|
+
if service_name in ["lollms", "heartbeat", "memory", "soul", "skill_preferences"]:
|
|
1339
|
+
continue
|
|
1340
|
+
|
|
1341
|
+
status, details = self._test_single(service_name, svc_config)
|
|
1342
|
+
display_name = "Discord" if service_name == "discord" else "Telegram" if service_name == "telegram" else service_name.title()
|
|
1343
|
+
table.add_row(display_name, status, details)
|
|
1344
|
+
|
|
1345
|
+
# Test Soul
|
|
1346
|
+
soul_hash = hashlib.sha256(
|
|
1347
|
+
json.dumps(self.soul.to_dict(), sort_keys=True).encode()
|
|
1348
|
+
).hexdigest()[:16]
|
|
1349
|
+
table.add_row("Soul", "✅ VALID", f"Hash: {soul_hash}")
|
|
1350
|
+
|
|
1351
|
+
# Test Heartbeat
|
|
1352
|
+
hb_status = self.heartbeat.get_status()
|
|
1353
|
+
table.add_row(
|
|
1354
|
+
"Heartbeat",
|
|
1355
|
+
"✅ ACTIVE" if hb_status["running"] else "⭕ STOPPED",
|
|
1356
|
+
f"Interval: {hb_status['interval_minutes']}min"
|
|
1357
|
+
)
|
|
1358
|
+
|
|
1359
|
+
# Test Skills
|
|
1360
|
+
skill_stats = self._get_skill_stats()
|
|
1361
|
+
table.add_row(
|
|
1362
|
+
"Skills",
|
|
1363
|
+
"✅ LOADED",
|
|
1364
|
+
f"{skill_stats['total']} skills, {len(skill_stats['by_category'])} categories"
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
console.print(table)
|
|
1368
|
+
|
|
1369
|
+
def _test_single(self, service_name: str, config: Dict[str, Any]) -> tuple[str, str]:
|
|
1370
|
+
"""Test a single service connection."""
|
|
1371
|
+
try:
|
|
1372
|
+
if service_name == "lollms":
|
|
1373
|
+
settings = LollmsSettings(
|
|
1374
|
+
host_address=config.get("host_address", ""),
|
|
1375
|
+
api_key=config.get("api_key"),
|
|
1376
|
+
binding_name=config.get("binding_name"),
|
|
1377
|
+
model_name=config.get("model_name"),
|
|
1378
|
+
context_size=config.get("context_size", 4096),
|
|
1379
|
+
verify_ssl=config.get("verify_ssl", True),
|
|
1380
|
+
)
|
|
1381
|
+
client = build_lollms_client(settings)
|
|
1382
|
+
|
|
1383
|
+
if client:
|
|
1384
|
+
# Show model and binding info
|
|
1385
|
+
binding = config.get("binding_name", "unknown")
|
|
1386
|
+
model = config.get("model_name", "default")
|
|
1387
|
+
return ("✅ READY", f"{binding}/{model}")
|
|
1388
|
+
else:
|
|
1389
|
+
return ("❌ ERROR", "Client initialization failed")
|
|
1390
|
+
|
|
1391
|
+
elif service_name == "discord":
|
|
1392
|
+
token = config.get("bot_token", "")
|
|
1393
|
+
has_token = bool(token)
|
|
1394
|
+
allowed_users = config.get("allowed_users", [])
|
|
1395
|
+
return (
|
|
1396
|
+
"🔍 CONFIGURED" if has_token else "⭕ NO TOKEN",
|
|
1397
|
+
f"Token: {'✅' if has_token else '❌'}, Users: {len(allowed_users)}"
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1400
|
+
elif service_name == "telegram":
|
|
1401
|
+
token = config.get("bot_token", "")
|
|
1402
|
+
has_token = bool(token)
|
|
1403
|
+
return (
|
|
1404
|
+
"🔍 CONFIGURED" if has_token else "⭕ NO TOKEN",
|
|
1405
|
+
f"Token: {'✅' if has_token else '❌'}"
|
|
1406
|
+
)
|
|
1407
|
+
|
|
1408
|
+
return ("❓ SKIP", "-")
|
|
1409
|
+
|
|
1410
|
+
except Exception as e:
|
|
1411
|
+
return ("❌ ERROR", str(e)[:40])
|
|
1412
|
+
|
|
1413
|
+
def show_full_config(self) -> None:
|
|
1414
|
+
"""Display complete configuration."""
|
|
1415
|
+
console.print("\n[bold]📄 Full Configuration[/]")
|
|
1416
|
+
|
|
1417
|
+
# Backend with binding details
|
|
1418
|
+
if "lollms" in self.config:
|
|
1419
|
+
lollms = self.config["lollms"]
|
|
1420
|
+
console.print(Panel(
|
|
1421
|
+
json.dumps(lollms, indent=2),
|
|
1422
|
+
title=f"AI Backend: {lollms.get('binding_name', 'unknown')}",
|
|
1423
|
+
border_style="blue"
|
|
1424
|
+
))
|
|
1425
|
+
|
|
1426
|
+
# Other services
|
|
1427
|
+
for svc in ["discord", "telegram"]:
|
|
1428
|
+
if svc in self.config:
|
|
1429
|
+
# Mask secrets
|
|
1430
|
+
safe_config = {k: (v if "token" not in k else "***") for k, v in self.config[svc].items()}
|
|
1431
|
+
console.print(Panel(
|
|
1432
|
+
json.dumps(safe_config, indent=2),
|
|
1433
|
+
title=svc.title(),
|
|
1434
|
+
border_style="blue"
|
|
1435
|
+
))
|
|
1436
|
+
|
|
1437
|
+
# Soul
|
|
1438
|
+
console.print(Panel(
|
|
1439
|
+
json.dumps(self.soul.to_dict(), indent=2),
|
|
1440
|
+
title=f"Soul: {self.soul.name}",
|
|
1441
|
+
border_style="magenta"
|
|
1442
|
+
))
|
|
1443
|
+
|
|
1444
|
+
# Heartbeat
|
|
1445
|
+
hb_status = self.heartbeat.get_status()
|
|
1446
|
+
console.print(Panel(
|
|
1447
|
+
json.dumps(hb_status, indent=2),
|
|
1448
|
+
title="Heartbeat Status",
|
|
1449
|
+
border_style="green"
|
|
1450
|
+
))
|
|
1451
|
+
|
|
1452
|
+
# Skills
|
|
1453
|
+
skill_stats = self._get_skill_stats()
|
|
1454
|
+
console.print(Panel(
|
|
1455
|
+
json.dumps(skill_stats, indent=2),
|
|
1456
|
+
title=f"Skills Library ({skill_stats['total']} skills)",
|
|
1457
|
+
border_style="yellow"
|
|
1458
|
+
))
|
|
1459
|
+
|
|
1460
|
+
def _save_all(self) -> None:
|
|
1461
|
+
"""Save all configurations."""
|
|
1462
|
+
self.soul._save()
|
|
1463
|
+
self.heartbeat._save_config()
|
|
1464
|
+
|
|
1465
|
+
self.config["soul"] = {
|
|
1466
|
+
"name": self.soul.name,
|
|
1467
|
+
"version": self.soul.version,
|
|
1468
|
+
"trait_count": len(self.soul.traits),
|
|
1469
|
+
"value_count": len(self.soul.values),
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
self.config["skills"] = {
|
|
1473
|
+
"total_loaded": len(self.skill_registry._skills),
|
|
1474
|
+
"categories": list(self.skill_registry._categories.keys()),
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
self._save_config()
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
# Entry point
|
|
1481
|
+
def run_wizard() -> None:
|
|
1482
|
+
"""CLI entrypoint."""
|
|
1483
|
+
try:
|
|
1484
|
+
Wizard().run_wizard()
|
|
1485
|
+
except KeyboardInterrupt:
|
|
1486
|
+
console.print("\n[yellow]👋 Bye![/]")
|
|
1487
|
+
except Exception as e:
|
|
1488
|
+
console.print(f"[red]Error: {e}[/]")
|
|
1489
|
+
raise
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
if __name__ == "__main__":
|
|
1493
|
+
run_wizard()
|