@innvisor/conny-ai 9.7.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/.env.example +68 -0
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/brand-assets/A_dark_luxury_web_background_202605210700.jpeg +0 -0
- package/brand-assets/Conny.web.logo.png +0 -0
- package/brand-assets/Logo_Conny_Petalo_Claro.png +0 -0
- package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/manifest.json +22 -0
- package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +11 -0
- package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +11 -0
- package/brand-assets/conny-demo/manifest.json +22 -0
- package/brand-assets/conny-demo/processed/business-identity.txt +7 -0
- package/brand-assets/conny-demo/raw/business-identity.txt +7 -0
- package/brand-assets/conny-logo.png +0 -0
- package/brand-assets/web.background.png +0 -0
- package/brand_assets.py +323 -0
- package/conny +28 -0
- package/conny-chat.py +579 -0
- package/conny-omni.py +3843 -0
- package/conny.py +113 -0
- package/conny_agents/__init__.py +1 -0
- package/conny_agents/agenda.py +1 -0
- package/conny_agents/captacion.py +1 -0
- package/conny_agents/conocimiento.py +1 -0
- package/conny_agents/escalacion.py +1 -0
- package/conny_agents/objeciones.py +1 -0
- package/conny_agents/seguimiento.py +1 -0
- package/conny_app.py +287 -0
- package/conny_audio.py +350 -0
- package/conny_audio_learn.py +84 -0
- package/conny_brain_v10.py +804 -0
- package/conny_bridge.py +656 -0
- package/conny_calendar.py +169 -0
- package/conny_cli.py +11784 -0
- package/conny_cli_bb.py +437 -0
- package/conny_commands.py +243 -0
- package/conny_config.py +215 -0
- package/conny_core/__init__.py +3 -0
- package/conny_core/conversation_engine.py +446 -0
- package/conny_core/first_turn_ops.py +287 -0
- package/conny_core/persona_registry.py +157 -0
- package/conny_core/prompt_ops.py +561 -0
- package/conny_cron.py +72 -0
- package/conny_demo_v2.py +209 -0
- package/conny_demo_voice.py +134 -0
- package/conny_design.py +43 -0
- package/conny_doctor.py +319 -0
- package/conny_domino.py +696 -0
- package/conny_generator.py +447 -0
- package/conny_google_auth.py +159 -0
- package/conny_i18n.py +619 -0
- package/conny_init.py +509 -0
- package/conny_integrations/__init__.py +4 -0
- package/conny_integrations/llm.py +1 -0
- package/conny_integrations/vault.py +77 -0
- package/conny_integrations/whatsapp.py +1 -0
- package/conny_intelligence.py +65 -0
- package/conny_learning.py +154 -0
- package/conny_memory.py +243 -0
- package/conny_memory_engine.py +292 -0
- package/conny_nova_proxy.py +170 -0
- package/conny_nuke_robot_phrases.py +493 -0
- package/conny_pairing.py +253 -0
- package/conny_patch.py +291 -0
- package/conny_persona_cli.py +150 -0
- package/conny_router.py +308 -0
- package/conny_runtime_ops.py +271 -0
- package/conny_session.py +516 -0
- package/conny_skills/__init__.py +1 -0
- package/conny_skills/demo_mode.py +35 -0
- package/conny_skills/text_processing.py +1 -0
- package/conny_skills/tone_detection.py +1 -0
- package/conny_smart_features.py +333 -0
- package/conny_studio.py +161 -0
- package/conny_sync_fix.py +306 -0
- package/conny_tui.py +512 -0
- package/conny_tui_select.py +202 -0
- package/conny_ultra_config.py +411 -0
- package/conny_uncertainty.py +174 -0
- package/conny_utils.py +87 -0
- package/conny_voice.py +156 -0
- package/conny_voice_engine.py +124 -0
- package/conny_web_search.py +66 -0
- package/conny_weekly_report.py +85 -0
- package/conny_worm.py +88 -0
- package/core/__init__.py +25 -0
- package/ecosystem.config.js +24 -0
- package/fix_init.py +27 -0
- package/install.sh +78 -0
- package/knowledge_base.py +330 -0
- package/nova/rules/default.yaml +37 -0
- package/nova_bridge.py +509 -0
- package/npm/conny.js +471 -0
- package/package.json +102 -0
- package/personas/conny/base/default.yaml +35 -0
- package/personas/conny/base/estetica_whatsapp.yaml +36 -0
- package/requirements.txt +14 -0
- package/run.sh +47 -0
- package/search.py +465 -0
- package/smart_handoff.py +1150 -0
- package/src/__init__.py +0 -0
- package/src/conny/__init__.py +0 -0
- package/src/conny/admin/__init__.py +0 -0
- package/src/conny/admin/api.py +234 -0
- package/src/conny/admin/dashboard.py +772 -0
- package/src/conny/api/__init__.py +0 -0
- package/src/conny/api/routes.py +8851 -0
- package/src/conny/brain/__init__.py +15 -0
- package/src/conny/brain/engine.py +804 -0
- package/src/conny/brain/learning.py +154 -0
- package/src/conny/brain/memory.py +324 -0
- package/src/conny/brain/smart_features.py +333 -0
- package/src/conny/brain/uncertainty.py +167 -0
- package/src/conny/channels/__init__.py +0 -0
- package/src/conny/channels/audio.py +316 -0
- package/src/conny/channels/cli.py +11795 -0
- package/src/conny/channels/logo_art.py +11 -0
- package/src/conny/channels/voice.py +156 -0
- package/src/conny/core/__init__.py +0 -0
- package/src/conny/core/config.py +215 -0
- package/src/conny/core/cron.py +72 -0
- package/src/conny/core/messenger.py +563 -0
- package/src/conny/core/router.py +297 -0
- package/src/conny/core/session.py +312 -0
- package/src/conny/demo/__init__.py +0 -0
- package/src/conny/demo/handler.py +3110 -0
- package/src/conny/integrations/__init__.py +19 -0
- package/src/conny/integrations/calendar.py +169 -0
- package/src/conny/integrations/knowledge.py +312 -0
- package/src/conny/integrations/search.py +66 -0
- package/src/conny/personas/__init__.py +0 -0
- package/src/conny/personas/generator.py +447 -0
- package/src/conny/production/__init__.py +0 -0
- package/src/conny/production/domino.py +696 -0
- package/src/conny/production/guard.py +550 -0
- package/src/conny/production/handoff.py +1150 -0
- package/src/conny/production/monitor.py +353 -0
- package/src/conny/utils/__init__.py +2 -0
- package/src/conny/utils/helpers.py +75 -0
- package/src/conny/utils/i18n.py +619 -0
- package/src/core/admin_engines.py +772 -0
- package/src/core/globals.py +11845 -0
- package/src/core/orchestrator.py +273 -0
- package/src/core/production_monitor.py +353 -0
- package/src/core/runtime.py +5487 -0
- package/src/domain/onboarding_flow.py +230 -0
- package/src/domain/prompts/__init__.py +1 -0
- package/src/domain/prompts/prospect_pitch.py +282 -0
- package/src/domain/send_guard.py +636 -0
- package/src/domain/swarm/queen.py +96 -0
- package/src/infrastructure/llm_providers/engine.py +487 -0
- package/src/interfaces/mcp_server.py +73 -0
- package/src/interfaces/nova_bridge.py +58 -0
- package/src/interfaces/web/admin_api.py +1379 -0
- package/src/interfaces/web/app.py +9408 -0
- package/src/interfaces/web/demo_handler.py +3450 -0
- package/src/interfaces/web/static/generate_avatars.py +46 -0
- package/v7/__init__.py +46 -0
- package/v7/agents/__init__.py +46 -0
- package/v7/agents/agenda.py +77 -0
- package/v7/agents/base.py +216 -0
- package/v7/agents/captacion.py +60 -0
- package/v7/agents/conocimiento.py +69 -0
- package/v7/agents/escalacion.py +83 -0
- package/v7/agents/objeciones.py +109 -0
- package/v7/agents/seguimiento.py +71 -0
- package/v7/memory/__init__.py +46 -0
- package/v7/memory/patient_profile.py +200 -0
- package/v7/orchestrator.py +275 -0
- package/v7/postprocess.py +127 -0
- package/v7/router.py +239 -0
- package/verify_conversation_impl.py +48 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Runtime inspection helpers for Conny CLI, doctor and config surfaces."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import socket
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CONNY_HOME = Path(os.getenv("CONNY_HOME", str(Path.home() / ".conny")))
|
|
17
|
+
CONNY_DIR = Path(os.getenv("CONNY_DIR", str(Path(__file__).resolve().parent)))
|
|
18
|
+
INSTANCES_DIR = Path(os.getenv("INSTANCES_DIR", str(Path.home() / "conny-instances")))
|
|
19
|
+
|
|
20
|
+
_TUNNEL_PORT_PATTERNS = (
|
|
21
|
+
re.compile(r"localhost:(\d+)"),
|
|
22
|
+
re.compile(r"127\.0\.0\.1:(\d+)"),
|
|
23
|
+
re.compile(r"0\.0\.0\.0:(\d+)"),
|
|
24
|
+
re.compile(r"ngrok\s+http\s+(?:https?://)?(?:localhost:)?(\d+)", re.I),
|
|
25
|
+
re.compile(r"cloudflared\s+tunnel.*--url\s+(?:https?://)?(?:localhost:)?(\d+)", re.I),
|
|
26
|
+
re.compile(r"lt\s+--port\s+(\d+)", re.I),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_env_file(path: Path) -> Dict[str, str]:
|
|
31
|
+
data: Dict[str, str] = {}
|
|
32
|
+
if not path.exists():
|
|
33
|
+
return data
|
|
34
|
+
for raw_line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
35
|
+
line = raw_line.strip()
|
|
36
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
37
|
+
continue
|
|
38
|
+
key, value = line.split("=", 1)
|
|
39
|
+
data[key.strip()] = value.strip().strip('"').strip("'")
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def write_env_value(path: Path, key: str, value: str) -> None:
|
|
44
|
+
lines: List[str] = []
|
|
45
|
+
found = False
|
|
46
|
+
if path.exists():
|
|
47
|
+
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
48
|
+
updated: List[str] = []
|
|
49
|
+
for raw_line in lines:
|
|
50
|
+
if raw_line.strip().startswith(f"{key}="):
|
|
51
|
+
updated.append(f"{key}={value}")
|
|
52
|
+
found = True
|
|
53
|
+
else:
|
|
54
|
+
updated.append(raw_line)
|
|
55
|
+
if not found:
|
|
56
|
+
if updated and updated[-1].strip():
|
|
57
|
+
updated.append("")
|
|
58
|
+
updated.append(f"{key}={value}")
|
|
59
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
path.write_text("\n".join(updated).rstrip() + "\n", encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def instance_root(instance_name: str) -> Path:
|
|
64
|
+
normalized = (instance_name or "").strip()
|
|
65
|
+
if normalized in {"", "base", "conny", "default"}:
|
|
66
|
+
return CONNY_DIR
|
|
67
|
+
return INSTANCES_DIR / normalized
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def instance_runtime_info(instance_name: str) -> Dict[str, Any]:
|
|
71
|
+
root = instance_root(instance_name)
|
|
72
|
+
env_path = root / ".env"
|
|
73
|
+
env = load_env_file(env_path)
|
|
74
|
+
meta_path = root / "instance.json"
|
|
75
|
+
meta: Dict[str, Any] = {}
|
|
76
|
+
if meta_path.exists():
|
|
77
|
+
try:
|
|
78
|
+
meta = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
79
|
+
except Exception:
|
|
80
|
+
meta = {}
|
|
81
|
+
port_raw = env.get("PORT") or meta.get("port") or ("8001" if root == CONNY_DIR else "8002")
|
|
82
|
+
try:
|
|
83
|
+
port = int(str(port_raw).strip())
|
|
84
|
+
except Exception:
|
|
85
|
+
port = 8001 if root == CONNY_DIR else 8002
|
|
86
|
+
return {
|
|
87
|
+
"name": root.name if root != CONNY_DIR else "base",
|
|
88
|
+
"root": root,
|
|
89
|
+
"env_path": env_path,
|
|
90
|
+
"env": env,
|
|
91
|
+
"meta": meta,
|
|
92
|
+
"port": port,
|
|
93
|
+
"base_url": str(env.get("BASE_URL", "")).strip(),
|
|
94
|
+
"webhook_secret": str(env.get("WEBHOOK_SECRET", "")).strip(),
|
|
95
|
+
"telegram_token": str(env.get("TELEGRAM_TOKEN", "")).strip(),
|
|
96
|
+
"pm2_name": "conny" if root == CONNY_DIR else f"conny-{root.name}",
|
|
97
|
+
"platform": str(env.get("PLATFORM", meta.get("platform", "telegram"))).strip() or "telegram",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def port_is_open(port: int, host: str = "127.0.0.1", timeout: float = 0.5) -> bool:
|
|
102
|
+
try:
|
|
103
|
+
with socket.create_connection((host, int(port)), timeout=timeout):
|
|
104
|
+
return True
|
|
105
|
+
except Exception:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def python_candidates(instance_name: str = "base") -> List[Dict[str, str]]:
|
|
110
|
+
info = instance_runtime_info(instance_name)
|
|
111
|
+
env = info["env"]
|
|
112
|
+
explicit = [
|
|
113
|
+
("env:CONNY_PYTHON_BIN", env.get("CONNY_PYTHON_BIN", "")),
|
|
114
|
+
("env:PYTHON_BIN", env.get("PYTHON_BIN", "")),
|
|
115
|
+
("process:CONNY_PYTHON_BIN", os.getenv("CONNY_PYTHON_BIN", "")),
|
|
116
|
+
("process:PYTHON_BIN", os.getenv("PYTHON_BIN", "")),
|
|
117
|
+
]
|
|
118
|
+
path_candidates = [
|
|
119
|
+
("instance:.venv", str(info["root"] / ".venv" / "bin" / "python")),
|
|
120
|
+
("instance:.venv3", str(info["root"] / ".venv" / "bin" / "python3")),
|
|
121
|
+
("base:.venv", str(CONNY_DIR / ".venv" / "bin" / "python")),
|
|
122
|
+
("base:.venv3", str(CONNY_DIR / ".venv" / "bin" / "python3")),
|
|
123
|
+
("home:runtime", str(CONNY_HOME / "runtime" / "bin" / "python")),
|
|
124
|
+
("home:runtime3", str(CONNY_HOME / "runtime" / "bin" / "python3")),
|
|
125
|
+
("sys.executable", sys.executable),
|
|
126
|
+
("PATH:python3", shutil.which("python3") or ""),
|
|
127
|
+
("PATH:python", shutil.which("python") or ""),
|
|
128
|
+
]
|
|
129
|
+
seen: set[str] = set()
|
|
130
|
+
resolved: List[Dict[str, str]] = []
|
|
131
|
+
for source, candidate in [*explicit, *path_candidates]:
|
|
132
|
+
candidate = str(candidate or "").strip()
|
|
133
|
+
if not candidate or candidate in seen:
|
|
134
|
+
continue
|
|
135
|
+
seen.add(candidate)
|
|
136
|
+
exists = Path(candidate).exists()
|
|
137
|
+
resolved.append({"source": source, "path": candidate, "exists": exists})
|
|
138
|
+
return resolved
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def resolve_python(instance_name: str = "base") -> Optional[Dict[str, str]]:
|
|
142
|
+
for candidate in python_candidates(instance_name):
|
|
143
|
+
if candidate["exists"]:
|
|
144
|
+
return candidate
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def pm2_processes() -> List[Dict[str, Any]]:
|
|
149
|
+
try:
|
|
150
|
+
result = subprocess.run(
|
|
151
|
+
["pm2", "jlist"],
|
|
152
|
+
capture_output=True,
|
|
153
|
+
text=True,
|
|
154
|
+
timeout=8,
|
|
155
|
+
check=False,
|
|
156
|
+
)
|
|
157
|
+
if result.returncode != 0 or not result.stdout.strip():
|
|
158
|
+
return []
|
|
159
|
+
return json.loads(result.stdout)
|
|
160
|
+
except Exception:
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def find_pm2_processes(instance_name: str) -> List[Dict[str, Any]]:
|
|
165
|
+
expected = instance_runtime_info(instance_name)["pm2_name"]
|
|
166
|
+
return [proc for proc in pm2_processes() if proc.get("name") == expected]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def extract_tunnel_target_ports(command_line: str) -> List[int]:
|
|
170
|
+
ports: List[int] = []
|
|
171
|
+
for pattern in _TUNNEL_PORT_PATTERNS:
|
|
172
|
+
for match in pattern.findall(command_line or ""):
|
|
173
|
+
try:
|
|
174
|
+
ports.append(int(match))
|
|
175
|
+
except Exception:
|
|
176
|
+
continue
|
|
177
|
+
deduped: List[int] = []
|
|
178
|
+
seen: set[int] = set()
|
|
179
|
+
for port in ports:
|
|
180
|
+
if port not in seen:
|
|
181
|
+
seen.add(port)
|
|
182
|
+
deduped.append(port)
|
|
183
|
+
return deduped
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def rewrite_tunnel_command_port(command_line: str, new_port: int) -> str:
|
|
187
|
+
updated = str(command_line or "")
|
|
188
|
+
replacements = [
|
|
189
|
+
(re.compile(r"(localhost:)(\d+)"), rf"\g<1>{int(new_port)}"),
|
|
190
|
+
(re.compile(r"(127\.0\.0\.1:)(\d+)"), rf"\g<1>{int(new_port)}"),
|
|
191
|
+
(re.compile(r"(0\.0\.0\.0:)(\d+)"), rf"\g<1>{int(new_port)}"),
|
|
192
|
+
(re.compile(r"(\bngrok\s+http\s+)(?:https?://)?(?:localhost:)?(\d+)", re.I), rf"\g<1>{int(new_port)}"),
|
|
193
|
+
(re.compile(r"(\bcloudflared\s+tunnel.*--url\s+)(?:https?://)?(?:localhost:)?(\d+)", re.I), rf"\g<1>http://localhost:{int(new_port)}"),
|
|
194
|
+
(re.compile(r"(\blt\s+--port\s+)(\d+)", re.I), rf"\g<1>{int(new_port)}"),
|
|
195
|
+
]
|
|
196
|
+
for pattern, replacement in replacements:
|
|
197
|
+
candidate = pattern.sub(replacement, updated)
|
|
198
|
+
if candidate != updated:
|
|
199
|
+
updated = candidate
|
|
200
|
+
return updated
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def detect_tunnel_processes() -> List[Dict[str, Any]]:
|
|
204
|
+
try:
|
|
205
|
+
result = subprocess.run(
|
|
206
|
+
["ps", "-eo", "pid,args"],
|
|
207
|
+
capture_output=True,
|
|
208
|
+
text=True,
|
|
209
|
+
timeout=8,
|
|
210
|
+
check=False,
|
|
211
|
+
)
|
|
212
|
+
except Exception:
|
|
213
|
+
return []
|
|
214
|
+
found: List[Dict[str, Any]] = []
|
|
215
|
+
for raw_line in result.stdout.splitlines():
|
|
216
|
+
line = raw_line.strip()
|
|
217
|
+
if not line:
|
|
218
|
+
continue
|
|
219
|
+
lower = line.lower()
|
|
220
|
+
if not any(token in lower for token in ("ngrok", "cloudflared", "localhost.run", "serveo", "localtunnel", "ssh -r", "ssh -l")):
|
|
221
|
+
continue
|
|
222
|
+
parts = line.split(maxsplit=1)
|
|
223
|
+
if len(parts) != 2:
|
|
224
|
+
continue
|
|
225
|
+
try:
|
|
226
|
+
pid = int(parts[0])
|
|
227
|
+
except Exception:
|
|
228
|
+
continue
|
|
229
|
+
command = parts[1]
|
|
230
|
+
found.append(
|
|
231
|
+
{
|
|
232
|
+
"pid": pid,
|
|
233
|
+
"command": command,
|
|
234
|
+
"ports": extract_tunnel_target_ports(command),
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
return found
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def health_payload(port: int) -> Optional[Dict[str, Any]]:
|
|
241
|
+
try:
|
|
242
|
+
import httpx
|
|
243
|
+
except Exception:
|
|
244
|
+
return None
|
|
245
|
+
try:
|
|
246
|
+
with httpx.Client(timeout=5.0) as client:
|
|
247
|
+
response = client.get(f"http://127.0.0.1:{int(port)}/health")
|
|
248
|
+
if response.status_code == 200:
|
|
249
|
+
return response.json()
|
|
250
|
+
except Exception:
|
|
251
|
+
return None
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def telegram_webhook_info(token: str) -> Dict[str, Any]:
|
|
256
|
+
token = str(token or "").strip()
|
|
257
|
+
if not token:
|
|
258
|
+
return {}
|
|
259
|
+
try:
|
|
260
|
+
import httpx
|
|
261
|
+
except Exception:
|
|
262
|
+
return {}
|
|
263
|
+
try:
|
|
264
|
+
with httpx.Client(timeout=8.0) as client:
|
|
265
|
+
response = client.get(f"https://api.telegram.org/bot{token}/getWebhookInfo")
|
|
266
|
+
payload = response.json()
|
|
267
|
+
if response.status_code == 200 and payload.get("ok"):
|
|
268
|
+
return payload.get("result", {}) or {}
|
|
269
|
+
except Exception:
|
|
270
|
+
return {}
|
|
271
|
+
return {}
|