@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,169 @@
1
+ """conny_calendar.py — Google Calendar: check availability + book appointments."""
2
+ from __future__ import annotations
3
+ import json, logging, os
4
+ from datetime import datetime, timedelta
5
+ from pathlib import Path
6
+ from typing import Dict, List, Optional
7
+ import httpx
8
+
9
+ log = logging.getLogger("conny.calendar")
10
+
11
+ VAULT_DIR = Path("integrations/vault")
12
+
13
+
14
+ class ConnyCalendar:
15
+ """Google Calendar integration for booking appointments."""
16
+
17
+ def __init__(self, instance_id: str = "default"):
18
+ self.instance_id = instance_id
19
+ self._tokens_file = VAULT_DIR / instance_id / "google_tokens.json"
20
+ self._creds_file = VAULT_DIR / instance_id / "google_credentials.json"
21
+
22
+ @property
23
+ def is_connected(self) -> bool:
24
+ return self._tokens_file.exists()
25
+
26
+ async def _get_access_token(self) -> Optional[str]:
27
+ """Get valid access token (refresh if needed)."""
28
+ if not self._tokens_file.exists():
29
+ return None
30
+ tokens = json.loads(self._tokens_file.read_text())
31
+ refresh_token = tokens.get("refresh_token")
32
+ if not refresh_token:
33
+ return tokens.get("access_token")
34
+
35
+ # Load client creds
36
+ if not self._creds_file.exists():
37
+ return tokens.get("access_token")
38
+ creds_data = json.loads(self._creds_file.read_text())
39
+ inner = creds_data.get("installed") or creds_data.get("web") or creds_data
40
+ client_id = inner.get("client_id", "")
41
+ client_secret = inner.get("client_secret", "")
42
+
43
+ if not client_id:
44
+ return tokens.get("access_token")
45
+
46
+ # Refresh
47
+ try:
48
+ async with httpx.AsyncClient(timeout=10.0) as client:
49
+ r = await client.post("https://oauth2.googleapis.com/token", data={
50
+ "client_id": client_id,
51
+ "client_secret": client_secret,
52
+ "refresh_token": refresh_token,
53
+ "grant_type": "refresh_token",
54
+ })
55
+ if r.status_code == 200:
56
+ new = r.json()
57
+ tokens["access_token"] = new["access_token"]
58
+ self._tokens_file.write_text(json.dumps(tokens, indent=2))
59
+ return new["access_token"]
60
+ except Exception as e:
61
+ log.warning(f"[calendar] refresh failed: {e}")
62
+
63
+ return tokens.get("access_token")
64
+
65
+ async def get_availability(self, date: str = None, days_ahead: int = 3) -> List[Dict]:
66
+ """Get available slots for the next N days."""
67
+ token = await self._get_access_token()
68
+ if not token:
69
+ return []
70
+
71
+ if not date:
72
+ start = datetime.now()
73
+ else:
74
+ try:
75
+ start = datetime.strptime(date, "%Y-%m-%d")
76
+ except:
77
+ start = datetime.now()
78
+
79
+ end = start + timedelta(days=days_ahead)
80
+ time_min = start.isoformat() + "Z"
81
+ time_max = end.isoformat() + "Z"
82
+
83
+ try:
84
+ async with httpx.AsyncClient(timeout=10.0) as client:
85
+ r = await client.get(
86
+ "https://www.googleapis.com/calendar/v3/calendars/primary/events",
87
+ headers={"Authorization": f"Bearer {token}"},
88
+ params={"timeMin": time_min, "timeMax": time_max,
89
+ "singleEvents": "true", "orderBy": "startTime"},
90
+ )
91
+ if r.status_code == 200:
92
+ events = r.json().get("items", [])
93
+ return [{"summary": e.get("summary", "Ocupado"),
94
+ "start": e.get("start", {}).get("dateTime", ""),
95
+ "end": e.get("end", {}).get("dateTime", "")}
96
+ for e in events]
97
+ else:
98
+ log.warning(f"[calendar] events fetch failed: {r.status_code}")
99
+ return []
100
+ except Exception as e:
101
+ log.error(f"[calendar] error: {e}")
102
+ return []
103
+
104
+ async def create_appointment(self, patient_name: str, phone: str,
105
+ service: str, date_time: str,
106
+ duration_minutes: int = 30) -> Optional[str]:
107
+ """Create a calendar event for an appointment."""
108
+ token = await self._get_access_token()
109
+ if not token:
110
+ return None
111
+
112
+ try:
113
+ start_dt = datetime.fromisoformat(date_time)
114
+ except:
115
+ return None
116
+
117
+ end_dt = start_dt + timedelta(minutes=duration_minutes)
118
+
119
+ event = {
120
+ "summary": f"Cita: {patient_name} — {service}",
121
+ "description": f"Paciente: {patient_name}\nTeléfono: {phone}\nServicio: {service}\nAgendado por: Conny AI",
122
+ "start": {"dateTime": start_dt.isoformat(), "timeZone": "America/Bogota"},
123
+ "end": {"dateTime": end_dt.isoformat(), "timeZone": "America/Bogota"},
124
+ "reminders": {
125
+ "useDefault": False,
126
+ "overrides": [
127
+ {"method": "popup", "minutes": 60},
128
+ {"method": "popup", "minutes": 1440},
129
+ ],
130
+ },
131
+ }
132
+
133
+ try:
134
+ async with httpx.AsyncClient(timeout=10.0) as client:
135
+ r = await client.post(
136
+ "https://www.googleapis.com/calendar/v3/calendars/primary/events",
137
+ headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
138
+ json=event,
139
+ )
140
+ if r.status_code in (200, 201):
141
+ created = r.json()
142
+ log.info(f"[calendar] appointment created: {created.get('id')}")
143
+ return created.get("id")
144
+ else:
145
+ log.error(f"[calendar] create failed: {r.status_code} {r.text[:200]}")
146
+ return None
147
+ except Exception as e:
148
+ log.error(f"[calendar] create error: {e}")
149
+ return None
150
+
151
+ async def get_availability_summary(self, days_ahead: int = 3) -> str:
152
+ """Get human-readable availability summary for Conny to use."""
153
+ events = await self.get_availability(days_ahead=days_ahead)
154
+ if not events:
155
+ return "sin citas programadas los próximos días — disponibilidad abierta"
156
+
157
+ busy_times = []
158
+ for e in events[:10]:
159
+ start = e.get("start", "")
160
+ if start:
161
+ try:
162
+ dt = datetime.fromisoformat(start.replace("Z", ""))
163
+ busy_times.append(f"{dt.strftime('%A %d')} a las {dt.strftime('%I:%M%p')}: {e['summary']}")
164
+ except:
165
+ pass
166
+
167
+ if busy_times:
168
+ return "Citas existentes:\n" + "\n".join(busy_times)
169
+ return "calendario disponible"