empathy-framework 4.6.6__py3-none-any.whl → 4.7.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.
Files changed (273) hide show
  1. empathy_framework-4.7.1.dist-info/METADATA +690 -0
  2. empathy_framework-4.7.1.dist-info/RECORD +379 -0
  3. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/top_level.txt +1 -2
  4. empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
  5. empathy_llm_toolkit/agent_factory/__init__.py +6 -6
  6. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
  7. empathy_llm_toolkit/agents_md/__init__.py +22 -0
  8. empathy_llm_toolkit/agents_md/loader.py +218 -0
  9. empathy_llm_toolkit/agents_md/parser.py +271 -0
  10. empathy_llm_toolkit/agents_md/registry.py +307 -0
  11. empathy_llm_toolkit/commands/__init__.py +51 -0
  12. empathy_llm_toolkit/commands/context.py +375 -0
  13. empathy_llm_toolkit/commands/loader.py +301 -0
  14. empathy_llm_toolkit/commands/models.py +231 -0
  15. empathy_llm_toolkit/commands/parser.py +371 -0
  16. empathy_llm_toolkit/commands/registry.py +429 -0
  17. empathy_llm_toolkit/config/__init__.py +8 -8
  18. empathy_llm_toolkit/config/unified.py +3 -7
  19. empathy_llm_toolkit/context/__init__.py +22 -0
  20. empathy_llm_toolkit/context/compaction.py +455 -0
  21. empathy_llm_toolkit/context/manager.py +434 -0
  22. empathy_llm_toolkit/hooks/__init__.py +24 -0
  23. empathy_llm_toolkit/hooks/config.py +306 -0
  24. empathy_llm_toolkit/hooks/executor.py +289 -0
  25. empathy_llm_toolkit/hooks/registry.py +302 -0
  26. empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
  27. empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
  28. empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
  29. empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
  30. empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
  31. empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
  32. empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
  33. empathy_llm_toolkit/learning/__init__.py +30 -0
  34. empathy_llm_toolkit/learning/evaluator.py +438 -0
  35. empathy_llm_toolkit/learning/extractor.py +514 -0
  36. empathy_llm_toolkit/learning/storage.py +560 -0
  37. empathy_llm_toolkit/providers.py +4 -11
  38. empathy_llm_toolkit/security/__init__.py +17 -17
  39. empathy_llm_toolkit/utils/tokens.py +2 -5
  40. empathy_os/__init__.py +202 -70
  41. empathy_os/cache_monitor.py +5 -3
  42. empathy_os/cli/__init__.py +11 -55
  43. empathy_os/cli/__main__.py +29 -15
  44. empathy_os/cli/commands/inspection.py +21 -12
  45. empathy_os/cli/commands/memory.py +4 -12
  46. empathy_os/cli/commands/profiling.py +198 -0
  47. empathy_os/cli/commands/utilities.py +27 -7
  48. empathy_os/cli.py +28 -57
  49. empathy_os/cli_unified.py +525 -1164
  50. empathy_os/cost_tracker.py +9 -3
  51. empathy_os/dashboard/server.py +200 -2
  52. empathy_os/hot_reload/__init__.py +7 -7
  53. empathy_os/hot_reload/config.py +6 -7
  54. empathy_os/hot_reload/integration.py +35 -35
  55. empathy_os/hot_reload/reloader.py +57 -57
  56. empathy_os/hot_reload/watcher.py +28 -28
  57. empathy_os/hot_reload/websocket.py +2 -2
  58. empathy_os/memory/__init__.py +11 -4
  59. empathy_os/memory/claude_memory.py +1 -1
  60. empathy_os/memory/cross_session.py +8 -12
  61. empathy_os/memory/edges.py +6 -6
  62. empathy_os/memory/file_session.py +770 -0
  63. empathy_os/memory/graph.py +30 -30
  64. empathy_os/memory/nodes.py +6 -6
  65. empathy_os/memory/short_term.py +15 -9
  66. empathy_os/memory/unified.py +606 -140
  67. empathy_os/meta_workflows/agent_creator.py +3 -9
  68. empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
  69. empathy_os/meta_workflows/form_engine.py +6 -18
  70. empathy_os/meta_workflows/intent_detector.py +64 -24
  71. empathy_os/meta_workflows/models.py +3 -1
  72. empathy_os/meta_workflows/pattern_learner.py +13 -31
  73. empathy_os/meta_workflows/plan_generator.py +55 -47
  74. empathy_os/meta_workflows/session_context.py +2 -3
  75. empathy_os/meta_workflows/workflow.py +20 -51
  76. empathy_os/models/cli.py +2 -2
  77. empathy_os/models/tasks.py +1 -2
  78. empathy_os/models/telemetry.py +4 -1
  79. empathy_os/models/token_estimator.py +3 -1
  80. empathy_os/monitoring/alerts.py +938 -9
  81. empathy_os/monitoring/alerts_cli.py +346 -183
  82. empathy_os/orchestration/execution_strategies.py +12 -29
  83. empathy_os/orchestration/pattern_learner.py +20 -26
  84. empathy_os/orchestration/real_tools.py +6 -15
  85. empathy_os/platform_utils.py +2 -1
  86. empathy_os/plugins/__init__.py +2 -2
  87. empathy_os/plugins/base.py +64 -64
  88. empathy_os/plugins/registry.py +32 -32
  89. empathy_os/project_index/index.py +49 -15
  90. empathy_os/project_index/models.py +1 -2
  91. empathy_os/project_index/reports.py +1 -1
  92. empathy_os/project_index/scanner.py +1 -0
  93. empathy_os/redis_memory.py +10 -7
  94. empathy_os/resilience/__init__.py +1 -1
  95. empathy_os/resilience/health.py +10 -10
  96. empathy_os/routing/__init__.py +7 -7
  97. empathy_os/routing/chain_executor.py +37 -37
  98. empathy_os/routing/classifier.py +36 -36
  99. empathy_os/routing/smart_router.py +40 -40
  100. empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
  101. empathy_os/scaffolding/__init__.py +8 -8
  102. empathy_os/scaffolding/__main__.py +1 -1
  103. empathy_os/scaffolding/cli.py +28 -28
  104. empathy_os/socratic/__init__.py +3 -19
  105. empathy_os/socratic/ab_testing.py +25 -36
  106. empathy_os/socratic/blueprint.py +38 -38
  107. empathy_os/socratic/cli.py +34 -20
  108. empathy_os/socratic/collaboration.py +30 -28
  109. empathy_os/socratic/domain_templates.py +9 -1
  110. empathy_os/socratic/embeddings.py +17 -13
  111. empathy_os/socratic/engine.py +135 -70
  112. empathy_os/socratic/explainer.py +70 -60
  113. empathy_os/socratic/feedback.py +24 -19
  114. empathy_os/socratic/forms.py +15 -10
  115. empathy_os/socratic/generator.py +51 -35
  116. empathy_os/socratic/llm_analyzer.py +25 -23
  117. empathy_os/socratic/mcp_server.py +99 -159
  118. empathy_os/socratic/session.py +19 -13
  119. empathy_os/socratic/storage.py +98 -67
  120. empathy_os/socratic/success.py +38 -27
  121. empathy_os/socratic/visual_editor.py +51 -39
  122. empathy_os/socratic/web_ui.py +99 -66
  123. empathy_os/telemetry/cli.py +3 -1
  124. empathy_os/telemetry/usage_tracker.py +1 -3
  125. empathy_os/test_generator/__init__.py +3 -3
  126. empathy_os/test_generator/cli.py +28 -28
  127. empathy_os/test_generator/generator.py +64 -66
  128. empathy_os/test_generator/risk_analyzer.py +11 -11
  129. empathy_os/vscode_bridge 2.py +173 -0
  130. empathy_os/vscode_bridge.py +173 -0
  131. empathy_os/workflows/__init__.py +212 -120
  132. empathy_os/workflows/batch_processing.py +8 -24
  133. empathy_os/workflows/bug_predict.py +1 -1
  134. empathy_os/workflows/code_review.py +20 -5
  135. empathy_os/workflows/code_review_pipeline.py +13 -8
  136. empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
  137. empathy_os/workflows/manage_documentation.py +1 -0
  138. empathy_os/workflows/orchestrated_health_check.py +6 -11
  139. empathy_os/workflows/orchestrated_release_prep.py +3 -3
  140. empathy_os/workflows/pr_review.py +18 -10
  141. empathy_os/workflows/progressive/README 2.md +454 -0
  142. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  143. empathy_os/workflows/progressive/__init__.py +2 -12
  144. empathy_os/workflows/progressive/cli 2.py +242 -0
  145. empathy_os/workflows/progressive/cli.py +14 -37
  146. empathy_os/workflows/progressive/core 2.py +488 -0
  147. empathy_os/workflows/progressive/core.py +12 -12
  148. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  149. empathy_os/workflows/progressive/orchestrator.py +166 -144
  150. empathy_os/workflows/progressive/reports 2.py +528 -0
  151. empathy_os/workflows/progressive/reports.py +22 -31
  152. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  153. empathy_os/workflows/progressive/telemetry.py +8 -14
  154. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  155. empathy_os/workflows/progressive/test_gen.py +29 -48
  156. empathy_os/workflows/progressive/workflow 2.py +628 -0
  157. empathy_os/workflows/progressive/workflow.py +31 -70
  158. empathy_os/workflows/release_prep.py +21 -6
  159. empathy_os/workflows/release_prep_crew.py +1 -0
  160. empathy_os/workflows/secure_release.py +13 -6
  161. empathy_os/workflows/security_audit.py +8 -3
  162. empathy_os/workflows/test_coverage_boost_crew.py +3 -2
  163. empathy_os/workflows/test_maintenance_crew.py +1 -0
  164. empathy_os/workflows/test_runner.py +16 -12
  165. empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
  166. empathy_software_plugin/cli.py +0 -122
  167. patterns/README.md +119 -0
  168. patterns/__init__.py +95 -0
  169. patterns/behavior.py +298 -0
  170. patterns/code_review_memory.json +441 -0
  171. patterns/core.py +97 -0
  172. patterns/debugging.json +3763 -0
  173. patterns/empathy.py +268 -0
  174. patterns/health_check_memory.json +505 -0
  175. patterns/input.py +161 -0
  176. patterns/memory_graph.json +8 -0
  177. patterns/refactoring_memory.json +1113 -0
  178. patterns/registry.py +663 -0
  179. patterns/security_memory.json +8 -0
  180. patterns/structural.py +415 -0
  181. patterns/validation.py +194 -0
  182. coach_wizards/__init__.py +0 -45
  183. coach_wizards/accessibility_wizard.py +0 -91
  184. coach_wizards/api_wizard.py +0 -91
  185. coach_wizards/base_wizard.py +0 -209
  186. coach_wizards/cicd_wizard.py +0 -91
  187. coach_wizards/code_reviewer_README.md +0 -60
  188. coach_wizards/code_reviewer_wizard.py +0 -180
  189. coach_wizards/compliance_wizard.py +0 -91
  190. coach_wizards/database_wizard.py +0 -91
  191. coach_wizards/debugging_wizard.py +0 -91
  192. coach_wizards/documentation_wizard.py +0 -91
  193. coach_wizards/generate_wizards.py +0 -347
  194. coach_wizards/localization_wizard.py +0 -173
  195. coach_wizards/migration_wizard.py +0 -91
  196. coach_wizards/monitoring_wizard.py +0 -91
  197. coach_wizards/observability_wizard.py +0 -91
  198. coach_wizards/performance_wizard.py +0 -91
  199. coach_wizards/prompt_engineering_wizard.py +0 -661
  200. coach_wizards/refactoring_wizard.py +0 -91
  201. coach_wizards/scaling_wizard.py +0 -90
  202. coach_wizards/security_wizard.py +0 -92
  203. coach_wizards/testing_wizard.py +0 -91
  204. empathy_framework-4.6.6.dist-info/METADATA +0 -1597
  205. empathy_framework-4.6.6.dist-info/RECORD +0 -410
  206. empathy_llm_toolkit/wizards/__init__.py +0 -43
  207. empathy_llm_toolkit/wizards/base_wizard.py +0 -364
  208. empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
  209. empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
  210. empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
  211. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
  212. empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
  213. empathy_os/wizard_factory_cli.py +0 -170
  214. empathy_software_plugin/wizards/__init__.py +0 -42
  215. empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
  216. empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
  217. empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
  218. empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
  219. empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
  220. empathy_software_plugin/wizards/base_wizard.py +0 -288
  221. empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
  222. empathy_software_plugin/wizards/code_review_wizard.py +0 -604
  223. empathy_software_plugin/wizards/debugging/__init__.py +0 -50
  224. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
  225. empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
  226. empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
  227. empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
  228. empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
  229. empathy_software_plugin/wizards/debugging/verification.py +0 -369
  230. empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
  231. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
  232. empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
  233. empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
  234. empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
  235. empathy_software_plugin/wizards/performance/__init__.py +0 -9
  236. empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
  237. empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
  238. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
  239. empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
  240. empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
  241. empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
  242. empathy_software_plugin/wizards/security/__init__.py +0 -32
  243. empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
  244. empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
  245. empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
  246. empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
  247. empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
  248. empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
  249. empathy_software_plugin/wizards/testing/__init__.py +0 -27
  250. empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
  251. empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
  252. empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
  253. empathy_software_plugin/wizards/testing_wizard.py +0 -274
  254. wizards/__init__.py +0 -82
  255. wizards/admission_assessment_wizard.py +0 -644
  256. wizards/care_plan.py +0 -321
  257. wizards/clinical_assessment.py +0 -769
  258. wizards/discharge_planning.py +0 -77
  259. wizards/discharge_summary_wizard.py +0 -468
  260. wizards/dosage_calculation.py +0 -497
  261. wizards/incident_report_wizard.py +0 -454
  262. wizards/medication_reconciliation.py +0 -85
  263. wizards/nursing_assessment.py +0 -171
  264. wizards/patient_education.py +0 -654
  265. wizards/quality_improvement.py +0 -705
  266. wizards/sbar_report.py +0 -324
  267. wizards/sbar_wizard.py +0 -608
  268. wizards/shift_handoff_wizard.py +0 -535
  269. wizards/soap_note_wizard.py +0 -679
  270. wizards/treatment_plan.py +0 -15
  271. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/WHEEL +0 -0
  272. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/entry_points.txt +0 -0
  273. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,267 +1,430 @@
1
- """Alert CLI Wizard
1
+ """Alert CLI Workflow
2
2
 
3
- Interactive wizard for setting up LLM telemetry alerts.
3
+ Interactive workflow for setting up LLM telemetry alerts.
4
4
 
5
5
  **Usage:**
6
- empathy alerts init
7
- empathy alerts list
8
- empathy alerts delete <id>
9
- empathy alerts watch [--daemon]
10
-
11
- **Implementation:** Sprint 3 (Week 3)
12
-
13
- Copyright 2025 Smart-AI-Memory
6
+ empathy alerts init # Interactive setup workflow
7
+ empathy alerts list # List configured alerts
8
+ empathy alerts delete # Delete an alert
9
+ empathy alerts watch # Start monitoring
10
+ empathy alerts history # View alert history
11
+ empathy alerts metrics # View current metrics
12
+
13
+ Copyright 2025-2026 Smart-AI-Memory
14
14
  Licensed under Fair Source License 0.9
15
15
  """
16
16
 
17
- import sqlite3
18
- from pathlib import Path
19
- from typing import Any
20
-
21
- import click
22
-
23
-
24
- class AlertEngine:
25
- """Alert engine with SQLite storage"""
26
-
27
- def __init__(self, db_path: str = ".empathy/alerts.db"):
28
- self.db_path = Path(db_path)
29
- self.db_path.parent.mkdir(parents=True, exist_ok=True)
30
- self._init_db()
31
-
32
- def _init_db(self) -> None:
33
- """Initialize SQLite database"""
34
- conn = sqlite3.connect(self.db_path)
35
- cursor = conn.cursor()
36
-
37
- cursor.execute(
38
- """
39
- CREATE TABLE IF NOT EXISTS alerts (
40
- id TEXT PRIMARY KEY,
41
- name TEXT NOT NULL,
42
- metric TEXT NOT NULL,
43
- threshold REAL NOT NULL,
44
- channel TEXT NOT NULL,
45
- webhook_url TEXT,
46
- email TEXT,
47
- enabled INTEGER DEFAULT 1,
48
- cooldown INTEGER DEFAULT 3600,
49
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
50
- )
51
- """
52
- )
53
-
54
- conn.commit()
55
- conn.close()
56
-
57
- def add_alert(
58
- self,
59
- alert_id: str,
60
- name: str,
61
- metric: str,
62
- threshold: float,
63
- channel: str,
64
- webhook_url: str | None = None,
65
- email: str | None = None,
66
- ) -> None:
67
- """Add a new alert"""
68
- conn = sqlite3.connect(self.db_path)
69
- cursor = conn.cursor()
70
-
71
- cursor.execute(
72
- """
73
- INSERT INTO alerts (id, name, metric, threshold, channel, webhook_url, email)
74
- VALUES (?, ?, ?, ?, ?, ?, ?)
75
- """,
76
- (alert_id, name, metric, threshold, channel, webhook_url, email),
77
- )
78
-
79
- conn.commit()
80
- conn.close()
81
-
82
- def list_alerts(self) -> list[dict[str, Any]]:
83
- """List all alerts"""
84
- conn = sqlite3.connect(self.db_path)
85
- cursor = conn.cursor()
86
-
87
- cursor.execute("SELECT * FROM alerts")
88
- rows = cursor.fetchall()
89
-
90
- conn.close()
91
-
92
- alerts = []
93
- for row in rows:
94
- alerts.append(
95
- {
96
- "id": row[0],
97
- "name": row[1],
98
- "metric": row[2],
99
- "threshold": row[3],
100
- "channel": row[4],
101
- "webhook_url": row[5],
102
- "email": row[6],
103
- "enabled": bool(row[7]),
104
- "cooldown": row[8],
105
- "created_at": row[9],
106
- }
107
- )
108
-
109
- return alerts
110
-
111
- def delete_alert(self, alert_id: str) -> bool:
112
- """Delete an alert by ID"""
113
- conn = sqlite3.connect(self.db_path)
114
- cursor = conn.cursor()
17
+ from __future__ import annotations
115
18
 
116
- cursor.execute("DELETE FROM alerts WHERE id = ?", (alert_id,))
117
- deleted = cursor.rowcount > 0
19
+ import signal
20
+ import sys
21
+ import time
118
22
 
119
- conn.commit()
120
- conn.close()
23
+ import click
121
24
 
122
- return deleted
25
+ from .alerts import (
26
+ get_alert_engine,
27
+ )
123
28
 
124
29
 
125
30
  @click.group()
126
31
  def alerts():
127
- """Alert management commands"""
32
+ """Alert management commands for LLM telemetry monitoring."""
128
33
  pass
129
34
 
130
35
 
131
36
  @alerts.command()
132
- def init():
133
- """Initialize alert with interactive wizard"""
134
- click.echo("🔔 Alert Setup Wizard\n")
37
+ @click.option("--non-interactive", is_flag=True, help="Skip interactive prompts")
38
+ @click.option("--metric", type=click.Choice(["daily_cost", "error_rate", "avg_latency", "token_usage"]))
39
+ @click.option("--threshold", type=float)
40
+ @click.option("--channel", type=click.Choice(["webhook", "email", "stdout"]))
41
+ @click.option("--webhook-url", help="Webhook URL (for webhook channel)")
42
+ @click.option("--email", help="Email address (for email channel)")
43
+ def init(
44
+ non_interactive: bool,
45
+ metric: str | None,
46
+ threshold: float | None,
47
+ channel: str | None,
48
+ webhook_url: str | None,
49
+ email: str | None,
50
+ ):
51
+ """Initialize an alert with interactive workflow or CLI flags."""
52
+ if non_interactive:
53
+ # Non-interactive mode - require all parameters
54
+ if not all([metric, threshold, channel]):
55
+ click.echo("Error: --metric, --threshold, and --channel required in non-interactive mode")
56
+ sys.exit(1)
57
+
58
+ if channel == "webhook" and not webhook_url:
59
+ click.echo("Error: --webhook-url required for webhook channel")
60
+ sys.exit(1)
61
+
62
+ if channel == "email" and not email:
63
+ click.echo("Error: --email required for email channel")
64
+ sys.exit(1)
65
+
66
+ _create_alert(metric, threshold, channel, webhook_url, email)
67
+ return
68
+
69
+ # Interactive workflow
70
+ click.echo("🔔 Alert Setup Workflow\n")
135
71
 
136
72
  # Question 1: What metric?
137
73
  click.echo("1. What metric do you want to monitor?")
138
- click.echo(" a) Daily cost")
139
- click.echo(" b) Error rate")
74
+ click.echo(" a) Daily cost (total USD spent)")
75
+ click.echo(" b) Error rate (% of failed calls)")
140
76
  click.echo(" c) Latency (avg response time)")
141
- click.echo(" d) Token usage")
77
+ click.echo(" d) Token usage (total tokens)")
142
78
 
143
79
  metric_choice = click.prompt("Choose (a/b/c/d)", type=click.Choice(["a", "b", "c", "d"]))
144
80
 
145
81
  metric_map = {
146
- "a": ("daily_cost", "Daily Cost"),
147
- "b": ("error_rate", "Error Rate"),
148
- "c": ("avg_latency", "Average Latency"),
149
- "d": ("token_usage", "Token Usage"),
82
+ "a": ("daily_cost", "Daily Cost", "USD"),
83
+ "b": ("error_rate", "Error Rate", "%"),
84
+ "c": ("avg_latency", "Average Latency", "ms"),
85
+ "d": ("token_usage", "Token Usage", "tokens"),
150
86
  }
151
87
 
152
- metric, metric_name = metric_map[metric_choice]
88
+ metric, metric_name, unit = metric_map[metric_choice]
153
89
 
154
90
  # Question 2: What threshold?
155
91
  click.echo(f"\n2. What threshold for {metric_name}?")
156
- if metric == "daily_cost":
157
- threshold = click.prompt("Daily cost threshold (USD)", type=float, default=10.0)
158
- elif metric == "error_rate":
159
- threshold = click.prompt("Error rate threshold (%)", type=float, default=10.0)
160
- elif metric == "avg_latency":
161
- threshold = click.prompt("Latency threshold (ms)", type=int, default=3000)
162
- else: # token_usage
163
- threshold = click.prompt("Token usage threshold", type=int, default=100000)
92
+ defaults = {
93
+ "daily_cost": 10.0,
94
+ "error_rate": 10.0,
95
+ "avg_latency": 3000,
96
+ "token_usage": 100000,
97
+ }
98
+ threshold = click.prompt(f"Threshold ({unit})", type=float, default=defaults[metric])
164
99
 
165
100
  # Question 3: Where to send?
166
101
  click.echo("\n3. Where should alerts be sent?")
167
- click.echo(" a) Webhook (Slack, Discord, etc.)")
102
+ click.echo(" a) Webhook (Slack, Discord, Teams)")
168
103
  click.echo(" b) Email")
169
- click.echo(" c) VSCode output (console)")
104
+ click.echo(" c) Console output")
170
105
 
171
106
  channel_choice = click.prompt("Choose (a/b/c)", type=click.Choice(["a", "b", "c"]))
172
107
 
173
108
  channel_map = {
174
109
  "a": "webhook",
175
110
  "b": "email",
176
- "c": "vscode_output",
111
+ "c": "stdout",
177
112
  }
178
113
 
179
114
  channel = channel_map[channel_choice]
180
-
181
115
  webhook_url = None
182
- email = None
116
+ email_addr = None
183
117
 
184
118
  if channel == "webhook":
185
119
  webhook_url = click.prompt("Webhook URL")
186
120
  elif channel == "email":
187
- email = click.prompt("Email address")
121
+ email_addr = click.prompt("Email address")
122
+
123
+ _create_alert(metric, threshold, channel, webhook_url, email_addr)
124
+
125
+
126
+ def _create_alert(
127
+ metric: str,
128
+ threshold: float,
129
+ channel: str,
130
+ webhook_url: str | None,
131
+ email: str | None,
132
+ ) -> None:
133
+ """Create an alert with the given configuration."""
134
+ engine = get_alert_engine()
135
+ alert_id = f"alert_{metric}_{int(time.time())}"
136
+
137
+ metric_names = {
138
+ "daily_cost": "Daily Cost",
139
+ "error_rate": "Error Rate",
140
+ "avg_latency": "Average Latency",
141
+ "token_usage": "Token Usage",
142
+ }
188
143
 
189
- # Create alert
190
- engine = AlertEngine()
191
- alert_id = f"alert_{metric}_{int(__import__('time').time())}"
144
+ try:
145
+ engine.add_alert(
146
+ alert_id=alert_id,
147
+ name=f"{metric_names.get(metric, metric)} Alert",
148
+ metric=metric,
149
+ threshold=threshold,
150
+ channel=channel,
151
+ webhook_url=webhook_url,
152
+ email=email,
153
+ )
192
154
 
193
- engine.add_alert(
194
- alert_id=alert_id,
195
- name=f"{metric_name} Alert",
196
- metric=metric,
197
- threshold=threshold,
198
- channel=channel,
199
- webhook_url=webhook_url,
200
- email=email,
201
- )
155
+ click.echo("\n✅ Alert created successfully!")
156
+ click.echo(f" ID: {alert_id}")
157
+ click.echo(f" Metric: {metric_names.get(metric, metric)}")
158
+ click.echo(f" Threshold: {threshold}")
159
+ click.echo(f" Channel: {channel}")
202
160
 
203
- click.echo("\n Alert created successfully!")
204
- click.echo(f" ID: {alert_id}")
205
- click.echo(f" Metric: {metric_name}")
206
- click.echo(f" Threshold: {threshold}")
207
- click.echo(f" Channel: {channel}")
161
+ click.echo("\n💡 Tip: Run 'empathy alerts watch' to start monitoring")
208
162
 
209
- click.echo("\n💡 Tip: Run 'empathy alerts watch' to start monitoring")
163
+ except ValueError as e:
164
+ click.echo(f"\n❌ Error: {e}")
165
+ sys.exit(1)
210
166
 
211
167
 
212
168
  @alerts.command(name="list")
213
- def list_cmd():
214
- """List all configured alerts"""
215
- engine = AlertEngine()
169
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
170
+ def list_cmd(as_json: bool):
171
+ """List all configured alerts."""
172
+ engine = get_alert_engine()
216
173
  alerts_list = engine.list_alerts()
217
174
 
218
175
  if not alerts_list:
219
- click.echo("No alerts configured. Run 'empathy alerts init' to create one.")
176
+ if as_json:
177
+ click.echo("[]")
178
+ else:
179
+ click.echo("No alerts configured. Run 'empathy alerts init' to create one.")
180
+ return
181
+
182
+ if as_json:
183
+ import json
184
+ click.echo(json.dumps([a.to_dict() for a in alerts_list], indent=2))
220
185
  return
221
186
 
222
187
  click.echo("📋 Configured Alerts:\n")
223
188
 
224
189
  for alert in alerts_list:
225
- status = "✓ Enabled" if alert["enabled"] else "✗ Disabled"
226
- click.echo(f" [{status}] {alert['name']}")
227
- click.echo(f" ID: {alert['id']}")
228
- click.echo(f" Metric: {alert['metric']} > {alert['threshold']}")
229
- click.echo(f" Channel: {alert['channel']}")
190
+ status = "✓ Enabled" if alert.enabled else "✗ Disabled"
191
+ click.echo(f" [{status}] {alert.name}")
192
+ click.echo(f" ID: {alert.alert_id}")
193
+ click.echo(f" Metric: {alert.metric.value} >= {alert.threshold}")
194
+ click.echo(f" Channel: {alert.channel.value}")
195
+ click.echo(f" Severity: {alert.severity.value}")
196
+ click.echo(f" Cooldown: {alert.cooldown_seconds}s")
230
197
  click.echo()
231
198
 
232
199
 
233
200
  @alerts.command()
234
201
  @click.argument("alert_id")
235
202
  def delete(alert_id: str):
236
- """Delete an alert by ID"""
237
- engine = AlertEngine()
203
+ """Delete an alert by ID."""
204
+ engine = get_alert_engine()
238
205
  deleted = engine.delete_alert(alert_id)
239
206
 
240
207
  if deleted:
241
208
  click.echo(f"✅ Alert '{alert_id}' deleted successfully")
242
209
  else:
243
210
  click.echo(f"❌ Alert '{alert_id}' not found")
211
+ sys.exit(1)
212
+
213
+
214
+ @alerts.command()
215
+ @click.argument("alert_id")
216
+ def enable(alert_id: str):
217
+ """Enable an alert by ID."""
218
+ engine = get_alert_engine()
219
+ if engine.enable_alert(alert_id):
220
+ click.echo(f"✅ Alert '{alert_id}' enabled")
221
+ else:
222
+ click.echo(f"❌ Alert '{alert_id}' not found")
223
+ sys.exit(1)
244
224
 
245
225
 
246
226
  @alerts.command()
247
- @click.option("--daemon", is_flag=True, help="Run as background daemon (enterprise)")
248
- def watch(daemon: bool):
249
- """Watch telemetry and trigger alerts"""
227
+ @click.argument("alert_id")
228
+ def disable(alert_id: str):
229
+ """Disable an alert by ID."""
230
+ engine = get_alert_engine()
231
+ if engine.disable_alert(alert_id):
232
+ click.echo(f"✅ Alert '{alert_id}' disabled")
233
+ else:
234
+ click.echo(f"❌ Alert '{alert_id}' not found")
235
+ sys.exit(1)
236
+
237
+
238
+ @alerts.command()
239
+ @click.option("--interval", default=60, help="Check interval in seconds (default: 60)")
240
+ @click.option("--daemon", is_flag=True, help="Run as background daemon")
241
+ @click.option("--once", is_flag=True, help="Check once and exit")
242
+ def watch(interval: int, daemon: bool, once: bool):
243
+ """Watch telemetry and trigger alerts when thresholds are exceeded."""
244
+ engine = get_alert_engine()
245
+
246
+ alerts_list = engine.list_alerts()
247
+ if not alerts_list:
248
+ click.echo("No alerts configured. Run 'empathy alerts init' first.")
249
+ sys.exit(1)
250
+
251
+ enabled_count = sum(1 for a in alerts_list if a.enabled)
252
+ click.echo(f"🔔 Monitoring {enabled_count} enabled alert(s)")
253
+
254
+ if once:
255
+ # Single check mode
256
+ events = engine.check_and_trigger()
257
+ if events:
258
+ click.echo(f"\n⚠️ {len(events)} alert(s) triggered!")
259
+ for event in events:
260
+ click.echo(f" - {event.alert_name}: {event.current_value:.2f} >= {event.threshold:.2f}")
261
+ else:
262
+ click.echo("✅ All metrics within thresholds")
263
+ return
264
+
250
265
  if daemon:
251
266
  click.echo("🔄 Starting alert watcher as daemon...")
252
- click.echo("⚠️ Note: Daemon mode is an enterprise feature for 24/7 monitoring")
253
- click.echo(" For development, use VSCode extension polling instead.")
254
- # TODO: Implement daemon mode
267
+ click.echo("⚠️ Daemon mode runs in background. Use 'ps aux | grep empathy' to check status.")
268
+ # Daemonize
269
+ _daemonize()
270
+
271
+ click.echo(f"🔄 Starting alert watcher (checking every {interval}s)...")
272
+ click.echo(" Press Ctrl+C to stop\n")
273
+
274
+ # Set up signal handler for graceful shutdown
275
+ running = True
276
+
277
+ def signal_handler(sig, frame):
278
+ nonlocal running
279
+ running = False
280
+ click.echo("\n✓ Alert watcher stopped")
281
+
282
+ signal.signal(signal.SIGINT, signal_handler)
283
+ signal.signal(signal.SIGTERM, signal_handler)
284
+
285
+ check_count = 0
286
+ triggered_count = 0
287
+
288
+ try:
289
+ while running:
290
+ check_count += 1
291
+ events = engine.check_and_trigger()
292
+
293
+ if events:
294
+ triggered_count += len(events)
295
+ for event in events:
296
+ click.echo(f"⚠️ ALERT: {event.alert_name}")
297
+ click.echo(f" {event.metric.value}: {event.current_value:.2f} >= {event.threshold:.2f}")
298
+
299
+ # Status update every 5 checks
300
+ if check_count % 5 == 0:
301
+ click.echo(f" [Check #{check_count}] Monitoring... ({triggered_count} alerts triggered)")
302
+
303
+ time.sleep(interval)
304
+ except KeyboardInterrupt:
305
+ pass
306
+
307
+ click.echo(f"\n📊 Summary: {check_count} checks, {triggered_count} alerts triggered")
308
+
309
+
310
+ def _daemonize():
311
+ """Daemonize the current process (Unix only)."""
312
+ import os
313
+
314
+ # Double fork to detach from terminal
315
+ try:
316
+ pid = os.fork()
317
+ if pid > 0:
318
+ # Parent exits
319
+ sys.exit(0)
320
+ except OSError as e:
321
+ click.echo(f"Fork #1 failed: {e}")
322
+ sys.exit(1)
323
+
324
+ # Decouple from parent environment
325
+ os.chdir("/")
326
+ os.setsid()
327
+ os.umask(0)
328
+
329
+ # Second fork
330
+ try:
331
+ pid = os.fork()
332
+ if pid > 0:
333
+ click.echo(f"Daemon started with PID: {pid}")
334
+ sys.exit(0)
335
+ except OSError as e:
336
+ click.echo(f"Fork #2 failed: {e}")
337
+ sys.exit(1)
338
+
339
+ # Redirect standard file descriptors
340
+ sys.stdout.flush()
341
+ sys.stderr.flush()
342
+
343
+ # Close file descriptors
344
+ with open("/dev/null", "rb", 0) as f:
345
+ os.dup2(f.fileno(), sys.stdin.fileno())
346
+ with open("/dev/null", "ab", 0) as f:
347
+ os.dup2(f.fileno(), sys.stdout.fileno())
348
+ with open("/dev/null", "ab", 0) as f:
349
+ os.dup2(f.fileno(), sys.stderr.fileno())
350
+
351
+
352
+ @alerts.command()
353
+ @click.option("--alert-id", help="Filter by alert ID")
354
+ @click.option("--limit", default=20, help="Maximum records to show")
355
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
356
+ def history(alert_id: str | None, limit: int, as_json: bool):
357
+ """View alert trigger history."""
358
+ engine = get_alert_engine()
359
+ records = engine.get_alert_history(alert_id=alert_id, limit=limit)
360
+
361
+ if not records:
362
+ if as_json:
363
+ click.echo("[]")
364
+ else:
365
+ click.echo("No alert history found.")
366
+ return
367
+
368
+ if as_json:
369
+ import json
370
+ click.echo(json.dumps(records, indent=2))
371
+ return
372
+
373
+ click.echo("📜 Alert History:\n")
374
+
375
+ for record in records:
376
+ delivered = "✓" if record["delivered"] else "✗"
377
+ click.echo(f" [{delivered}] {record['alert_id']}")
378
+ click.echo(f" Metric: {record['metric']} = {record['current_value']:.2f} (threshold: {record['threshold']:.2f})")
379
+ click.echo(f" Severity: {record['severity']}")
380
+ click.echo(f" Triggered: {record['triggered_at']}")
381
+ if record.get("delivery_error"):
382
+ click.echo(f" Error: {record['delivery_error']}")
383
+ click.echo()
384
+
385
+
386
+ @alerts.command()
387
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
388
+ def metrics(as_json: bool):
389
+ """View current telemetry metrics."""
390
+ engine = get_alert_engine()
391
+ current_metrics = engine.get_metrics()
392
+
393
+ if as_json:
394
+ import json
395
+ click.echo(json.dumps(current_metrics, indent=2))
396
+ return
397
+
398
+ click.echo("📊 Current Metrics (last 24 hours):\n")
399
+
400
+ metric_info = {
401
+ "daily_cost": ("Daily Cost", "USD"),
402
+ "error_rate": ("Error Rate", "%"),
403
+ "avg_latency": ("Avg Latency", "ms"),
404
+ "token_usage": ("Token Usage", "tokens"),
405
+ }
406
+
407
+ for key, value in current_metrics.items():
408
+ name, unit = metric_info.get(key, (key, ""))
409
+ click.echo(f" {name}: {value:.2f} {unit}")
410
+
411
+ click.echo()
412
+
413
+ # Show alerts that would trigger
414
+ alerts_list = engine.list_alerts()
415
+ triggered = []
416
+ for alert in alerts_list:
417
+ if alert.enabled:
418
+ current = current_metrics.get(alert.metric.value, 0)
419
+ if current >= alert.threshold:
420
+ triggered.append((alert.name, current, alert.threshold))
421
+
422
+ if triggered:
423
+ click.echo("⚠️ Alerts that would trigger:")
424
+ for name, current, threshold in triggered:
425
+ click.echo(f" {name}: {current:.2f} >= {threshold:.2f}")
255
426
  else:
256
- click.echo("🔄 Starting alert watcher (Ctrl+C to stop)...")
257
- click.echo("💡 Tip: Use VSCode extension for automatic monitoring")
258
-
259
- try:
260
- while True:
261
- # TODO: Check telemetry and trigger alerts
262
- __import__("time").sleep(60)
263
- except KeyboardInterrupt:
264
- click.echo("\n✓ Alert watcher stopped")
427
+ click.echo(" All metrics within thresholds")
265
428
 
266
429
 
267
430
  if __name__ == "__main__":