@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.
Files changed (175) hide show
  1. package/.env.example +68 -0
  2. package/CHANGELOG.md +54 -0
  3. package/LICENSE +21 -0
  4. package/README.md +369 -0
  5. package/brand-assets/A_dark_luxury_web_background_202605210700.jpeg +0 -0
  6. package/brand-assets/Conny.web.logo.png +0 -0
  7. package/brand-assets/Logo_Conny_Petalo_Claro.png +0 -0
  8. package/brand-assets/cl-nica-de-las-am-ricas/manifest.json +22 -0
  9. package/brand-assets/cl-nica-de-las-am-ricas/processed/business-identity.txt +11 -0
  10. package/brand-assets/cl-nica-de-las-am-ricas/raw/business-identity.txt +11 -0
  11. package/brand-assets/cl-nica-las-am-ricas/manifest.json +22 -0
  12. package/brand-assets/cl-nica-las-am-ricas/processed/business-identity.txt +11 -0
  13. package/brand-assets/cl-nica-las-am-ricas/raw/business-identity.txt +11 -0
  14. package/brand-assets/conny-demo/manifest.json +22 -0
  15. package/brand-assets/conny-demo/processed/business-identity.txt +7 -0
  16. package/brand-assets/conny-demo/raw/business-identity.txt +7 -0
  17. package/brand-assets/conny-logo.png +0 -0
  18. package/brand-assets/web.background.png +0 -0
  19. package/brand_assets.py +323 -0
  20. package/conny +28 -0
  21. package/conny-chat.py +579 -0
  22. package/conny-omni.py +3843 -0
  23. package/conny.py +113 -0
  24. package/conny_agents/__init__.py +1 -0
  25. package/conny_agents/agenda.py +1 -0
  26. package/conny_agents/captacion.py +1 -0
  27. package/conny_agents/conocimiento.py +1 -0
  28. package/conny_agents/escalacion.py +1 -0
  29. package/conny_agents/objeciones.py +1 -0
  30. package/conny_agents/seguimiento.py +1 -0
  31. package/conny_app.py +287 -0
  32. package/conny_audio.py +350 -0
  33. package/conny_audio_learn.py +84 -0
  34. package/conny_brain_v10.py +804 -0
  35. package/conny_bridge.py +656 -0
  36. package/conny_calendar.py +169 -0
  37. package/conny_cli.py +11784 -0
  38. package/conny_cli_bb.py +437 -0
  39. package/conny_commands.py +243 -0
  40. package/conny_config.py +215 -0
  41. package/conny_core/__init__.py +3 -0
  42. package/conny_core/conversation_engine.py +446 -0
  43. package/conny_core/first_turn_ops.py +287 -0
  44. package/conny_core/persona_registry.py +157 -0
  45. package/conny_core/prompt_ops.py +561 -0
  46. package/conny_cron.py +72 -0
  47. package/conny_demo_v2.py +209 -0
  48. package/conny_demo_voice.py +134 -0
  49. package/conny_design.py +43 -0
  50. package/conny_doctor.py +319 -0
  51. package/conny_domino.py +696 -0
  52. package/conny_generator.py +447 -0
  53. package/conny_google_auth.py +159 -0
  54. package/conny_i18n.py +619 -0
  55. package/conny_init.py +509 -0
  56. package/conny_integrations/__init__.py +4 -0
  57. package/conny_integrations/llm.py +1 -0
  58. package/conny_integrations/vault.py +77 -0
  59. package/conny_integrations/whatsapp.py +1 -0
  60. package/conny_intelligence.py +65 -0
  61. package/conny_learning.py +154 -0
  62. package/conny_memory.py +243 -0
  63. package/conny_memory_engine.py +292 -0
  64. package/conny_nova_proxy.py +170 -0
  65. package/conny_nuke_robot_phrases.py +493 -0
  66. package/conny_pairing.py +253 -0
  67. package/conny_patch.py +291 -0
  68. package/conny_persona_cli.py +150 -0
  69. package/conny_router.py +308 -0
  70. package/conny_runtime_ops.py +271 -0
  71. package/conny_session.py +516 -0
  72. package/conny_skills/__init__.py +1 -0
  73. package/conny_skills/demo_mode.py +35 -0
  74. package/conny_skills/text_processing.py +1 -0
  75. package/conny_skills/tone_detection.py +1 -0
  76. package/conny_smart_features.py +333 -0
  77. package/conny_studio.py +161 -0
  78. package/conny_sync_fix.py +306 -0
  79. package/conny_tui.py +512 -0
  80. package/conny_tui_select.py +202 -0
  81. package/conny_ultra_config.py +411 -0
  82. package/conny_uncertainty.py +174 -0
  83. package/conny_utils.py +87 -0
  84. package/conny_voice.py +156 -0
  85. package/conny_voice_engine.py +124 -0
  86. package/conny_web_search.py +66 -0
  87. package/conny_weekly_report.py +85 -0
  88. package/conny_worm.py +88 -0
  89. package/core/__init__.py +25 -0
  90. package/ecosystem.config.js +24 -0
  91. package/fix_init.py +27 -0
  92. package/install.sh +78 -0
  93. package/knowledge_base.py +330 -0
  94. package/nova/rules/default.yaml +37 -0
  95. package/nova_bridge.py +509 -0
  96. package/npm/conny.js +471 -0
  97. package/package.json +102 -0
  98. package/personas/conny/base/default.yaml +35 -0
  99. package/personas/conny/base/estetica_whatsapp.yaml +36 -0
  100. package/requirements.txt +14 -0
  101. package/run.sh +47 -0
  102. package/search.py +465 -0
  103. package/smart_handoff.py +1150 -0
  104. package/src/__init__.py +0 -0
  105. package/src/conny/__init__.py +0 -0
  106. package/src/conny/admin/__init__.py +0 -0
  107. package/src/conny/admin/api.py +234 -0
  108. package/src/conny/admin/dashboard.py +772 -0
  109. package/src/conny/api/__init__.py +0 -0
  110. package/src/conny/api/routes.py +8851 -0
  111. package/src/conny/brain/__init__.py +15 -0
  112. package/src/conny/brain/engine.py +804 -0
  113. package/src/conny/brain/learning.py +154 -0
  114. package/src/conny/brain/memory.py +324 -0
  115. package/src/conny/brain/smart_features.py +333 -0
  116. package/src/conny/brain/uncertainty.py +167 -0
  117. package/src/conny/channels/__init__.py +0 -0
  118. package/src/conny/channels/audio.py +316 -0
  119. package/src/conny/channels/cli.py +11795 -0
  120. package/src/conny/channels/logo_art.py +11 -0
  121. package/src/conny/channels/voice.py +156 -0
  122. package/src/conny/core/__init__.py +0 -0
  123. package/src/conny/core/config.py +215 -0
  124. package/src/conny/core/cron.py +72 -0
  125. package/src/conny/core/messenger.py +563 -0
  126. package/src/conny/core/router.py +297 -0
  127. package/src/conny/core/session.py +312 -0
  128. package/src/conny/demo/__init__.py +0 -0
  129. package/src/conny/demo/handler.py +3110 -0
  130. package/src/conny/integrations/__init__.py +19 -0
  131. package/src/conny/integrations/calendar.py +169 -0
  132. package/src/conny/integrations/knowledge.py +312 -0
  133. package/src/conny/integrations/search.py +66 -0
  134. package/src/conny/personas/__init__.py +0 -0
  135. package/src/conny/personas/generator.py +447 -0
  136. package/src/conny/production/__init__.py +0 -0
  137. package/src/conny/production/domino.py +696 -0
  138. package/src/conny/production/guard.py +550 -0
  139. package/src/conny/production/handoff.py +1150 -0
  140. package/src/conny/production/monitor.py +353 -0
  141. package/src/conny/utils/__init__.py +2 -0
  142. package/src/conny/utils/helpers.py +75 -0
  143. package/src/conny/utils/i18n.py +619 -0
  144. package/src/core/admin_engines.py +772 -0
  145. package/src/core/globals.py +11845 -0
  146. package/src/core/orchestrator.py +273 -0
  147. package/src/core/production_monitor.py +353 -0
  148. package/src/core/runtime.py +5487 -0
  149. package/src/domain/onboarding_flow.py +230 -0
  150. package/src/domain/prompts/__init__.py +1 -0
  151. package/src/domain/prompts/prospect_pitch.py +282 -0
  152. package/src/domain/send_guard.py +636 -0
  153. package/src/domain/swarm/queen.py +96 -0
  154. package/src/infrastructure/llm_providers/engine.py +487 -0
  155. package/src/interfaces/mcp_server.py +73 -0
  156. package/src/interfaces/nova_bridge.py +58 -0
  157. package/src/interfaces/web/admin_api.py +1379 -0
  158. package/src/interfaces/web/app.py +9408 -0
  159. package/src/interfaces/web/demo_handler.py +3450 -0
  160. package/src/interfaces/web/static/generate_avatars.py +46 -0
  161. package/v7/__init__.py +46 -0
  162. package/v7/agents/__init__.py +46 -0
  163. package/v7/agents/agenda.py +77 -0
  164. package/v7/agents/base.py +216 -0
  165. package/v7/agents/captacion.py +60 -0
  166. package/v7/agents/conocimiento.py +69 -0
  167. package/v7/agents/escalacion.py +83 -0
  168. package/v7/agents/objeciones.py +109 -0
  169. package/v7/agents/seguimiento.py +71 -0
  170. package/v7/memory/__init__.py +46 -0
  171. package/v7/memory/patient_profile.py +200 -0
  172. package/v7/orchestrator.py +275 -0
  173. package/v7/postprocess.py +127 -0
  174. package/v7/router.py +239 -0
  175. 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 {}