codeframe-ai 0.9.0__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 (197) hide show
  1. codeframe/__init__.py +11 -0
  2. codeframe/__main__.py +20 -0
  3. codeframe/adapters/__init__.py +5 -0
  4. codeframe/adapters/e2b/__init__.py +13 -0
  5. codeframe/adapters/e2b/adapter.py +342 -0
  6. codeframe/adapters/e2b/budget.py +71 -0
  7. codeframe/adapters/e2b/credential_scanner.py +134 -0
  8. codeframe/adapters/llm/__init__.py +92 -0
  9. codeframe/adapters/llm/anthropic.py +414 -0
  10. codeframe/adapters/llm/base.py +444 -0
  11. codeframe/adapters/llm/mock.py +281 -0
  12. codeframe/adapters/llm/openai.py +483 -0
  13. codeframe/agents/__init__.py +8 -0
  14. codeframe/agents/dependency_resolver.py +714 -0
  15. codeframe/auth/__init__.py +16 -0
  16. codeframe/auth/api_key_router.py +238 -0
  17. codeframe/auth/api_keys.py +156 -0
  18. codeframe/auth/dependencies.py +358 -0
  19. codeframe/auth/manager.py +178 -0
  20. codeframe/auth/models.py +30 -0
  21. codeframe/auth/router.py +93 -0
  22. codeframe/auth/schemas.py +15 -0
  23. codeframe/auth/scopes.py +53 -0
  24. codeframe/cli/__init__.py +12 -0
  25. codeframe/cli/__main__.py +20 -0
  26. codeframe/cli/api_client.py +275 -0
  27. codeframe/cli/app.py +5688 -0
  28. codeframe/cli/auth.py +122 -0
  29. codeframe/cli/auth_commands.py +958 -0
  30. codeframe/cli/commands/__init__.py +5 -0
  31. codeframe/cli/config_commands.py +79 -0
  32. codeframe/cli/dashboard_commands.py +67 -0
  33. codeframe/cli/engines_commands.py +205 -0
  34. codeframe/cli/env_commands.py +409 -0
  35. codeframe/cli/helpers.py +56 -0
  36. codeframe/cli/hooks_commands.py +208 -0
  37. codeframe/cli/import_commands.py +129 -0
  38. codeframe/cli/pr_commands.py +549 -0
  39. codeframe/cli/proof_commands.py +415 -0
  40. codeframe/cli/stats_commands.py +311 -0
  41. codeframe/cli/telemetry_runtime.py +153 -0
  42. codeframe/cli/validators.py +123 -0
  43. codeframe/config/rate_limits.py +165 -0
  44. codeframe/core/__init__.py +15 -0
  45. codeframe/core/adapters/__init__.py +43 -0
  46. codeframe/core/adapters/agent_adapter.py +114 -0
  47. codeframe/core/adapters/builtin.py +326 -0
  48. codeframe/core/adapters/claude_code.py +62 -0
  49. codeframe/core/adapters/codex.py +393 -0
  50. codeframe/core/adapters/git_utils.py +40 -0
  51. codeframe/core/adapters/kilocode.py +126 -0
  52. codeframe/core/adapters/opencode.py +48 -0
  53. codeframe/core/adapters/streaming_chat.py +483 -0
  54. codeframe/core/adapters/subprocess_adapter.py +213 -0
  55. codeframe/core/adapters/verification_wrapper.py +269 -0
  56. codeframe/core/agent.py +2183 -0
  57. codeframe/core/agents_config.py +569 -0
  58. codeframe/core/api_key_service.py +211 -0
  59. codeframe/core/artifacts.py +428 -0
  60. codeframe/core/blocker_detection.py +218 -0
  61. codeframe/core/blockers.py +433 -0
  62. codeframe/core/checkpoints.py +481 -0
  63. codeframe/core/conductor.py +2255 -0
  64. codeframe/core/config.py +827 -0
  65. codeframe/core/config_watcher.py +268 -0
  66. codeframe/core/context.py +542 -0
  67. codeframe/core/context_packager.py +234 -0
  68. codeframe/core/credentials.py +735 -0
  69. codeframe/core/dependency_analyzer.py +229 -0
  70. codeframe/core/dependency_graph.py +290 -0
  71. codeframe/core/diagnostic_agent.py +712 -0
  72. codeframe/core/diagnostics.py +616 -0
  73. codeframe/core/editor.py +556 -0
  74. codeframe/core/engine_registry.py +256 -0
  75. codeframe/core/engine_stats.py +231 -0
  76. codeframe/core/environment.py +697 -0
  77. codeframe/core/events.py +375 -0
  78. codeframe/core/executor.py +1005 -0
  79. codeframe/core/fix_tracker.py +480 -0
  80. codeframe/core/gates.py +1322 -0
  81. codeframe/core/git.py +477 -0
  82. codeframe/core/github_connect_service.py +178 -0
  83. codeframe/core/github_integration_config.py +118 -0
  84. codeframe/core/github_issues_service.py +449 -0
  85. codeframe/core/hooks.py +184 -0
  86. codeframe/core/importers/__init__.py +1 -0
  87. codeframe/core/importers/ralph.py +540 -0
  88. codeframe/core/installer.py +650 -0
  89. codeframe/core/models.py +1026 -0
  90. codeframe/core/notifications_config.py +183 -0
  91. codeframe/core/planner.py +437 -0
  92. codeframe/core/prd.py +670 -0
  93. codeframe/core/prd_discovery.py +1118 -0
  94. codeframe/core/prd_stress_test.py +499 -0
  95. codeframe/core/progress.py +126 -0
  96. codeframe/core/proof/__init__.py +34 -0
  97. codeframe/core/proof/capture.py +79 -0
  98. codeframe/core/proof/evidence.py +56 -0
  99. codeframe/core/proof/ledger.py +574 -0
  100. codeframe/core/proof/models.py +162 -0
  101. codeframe/core/proof/obligations.py +103 -0
  102. codeframe/core/proof/runner.py +233 -0
  103. codeframe/core/proof/scope.py +81 -0
  104. codeframe/core/proof/stubs.py +156 -0
  105. codeframe/core/quick_fixes.py +558 -0
  106. codeframe/core/react_agent.py +1650 -0
  107. codeframe/core/reconciliation.py +183 -0
  108. codeframe/core/replay.py +788 -0
  109. codeframe/core/review.py +285 -0
  110. codeframe/core/runtime.py +1134 -0
  111. codeframe/core/sandbox/__init__.py +27 -0
  112. codeframe/core/sandbox/context.py +98 -0
  113. codeframe/core/sandbox/worktree.py +20 -0
  114. codeframe/core/schedule.py +396 -0
  115. codeframe/core/stall_detector.py +71 -0
  116. codeframe/core/stall_monitor.py +134 -0
  117. codeframe/core/state_machine.py +121 -0
  118. codeframe/core/streaming.py +502 -0
  119. codeframe/core/task_tree.py +400 -0
  120. codeframe/core/tasks.py +1022 -0
  121. codeframe/core/telemetry.py +232 -0
  122. codeframe/core/templates.py +221 -0
  123. codeframe/core/tools.py +942 -0
  124. codeframe/core/workspace.py +887 -0
  125. codeframe/core/worktrees.py +276 -0
  126. codeframe/git/__init__.py +5 -0
  127. codeframe/git/github_integration.py +505 -0
  128. codeframe/lib/__init__.py +0 -0
  129. codeframe/lib/audit_logger.py +248 -0
  130. codeframe/lib/metrics_tracker.py +800 -0
  131. codeframe/lib/quality/__init__.py +7 -0
  132. codeframe/lib/quality/complexity_analyzer.py +316 -0
  133. codeframe/lib/quality/owasp_patterns.py +284 -0
  134. codeframe/lib/quality/security_scanner.py +250 -0
  135. codeframe/lib/rate_limiter.py +312 -0
  136. codeframe/notifications/__init__.py +0 -0
  137. codeframe/notifications/webhook.py +380 -0
  138. codeframe/planning/__init__.py +30 -0
  139. codeframe/planning/issue_generator.py +219 -0
  140. codeframe/planning/prd_template_functions.py +137 -0
  141. codeframe/planning/prd_templates.py +975 -0
  142. codeframe/planning/task_scheduler.py +511 -0
  143. codeframe/planning/task_templates.py +533 -0
  144. codeframe/platform_store/__init__.py +5 -0
  145. codeframe/platform_store/database.py +277 -0
  146. codeframe/platform_store/repositories/__init__.py +24 -0
  147. codeframe/platform_store/repositories/api_key_repository.py +245 -0
  148. codeframe/platform_store/repositories/audit_repository.py +67 -0
  149. codeframe/platform_store/repositories/base.py +295 -0
  150. codeframe/platform_store/repositories/interactive_sessions.py +165 -0
  151. codeframe/platform_store/repositories/token_repository.py +598 -0
  152. codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
  153. codeframe/platform_store/schema_manager.py +321 -0
  154. codeframe/templates/AGENTS.md.default +94 -0
  155. codeframe/tui/__init__.py +5 -0
  156. codeframe/tui/app.py +256 -0
  157. codeframe/tui/data_service.py +103 -0
  158. codeframe/ui/__init__.py +0 -0
  159. codeframe/ui/dependencies.py +103 -0
  160. codeframe/ui/models.py +999 -0
  161. codeframe/ui/response_models.py +201 -0
  162. codeframe/ui/routers/__init__.py +5 -0
  163. codeframe/ui/routers/_helpers.py +29 -0
  164. codeframe/ui/routers/batches_v2.py +315 -0
  165. codeframe/ui/routers/blockers_v2.py +320 -0
  166. codeframe/ui/routers/checkpoints_v2.py +310 -0
  167. codeframe/ui/routers/costs_v2.py +322 -0
  168. codeframe/ui/routers/diagnose_v2.py +225 -0
  169. codeframe/ui/routers/discovery_v2.py +417 -0
  170. codeframe/ui/routers/environment_v2.py +284 -0
  171. codeframe/ui/routers/events_v2.py +75 -0
  172. codeframe/ui/routers/gates_v2.py +166 -0
  173. codeframe/ui/routers/git_v2.py +284 -0
  174. codeframe/ui/routers/github_integrations_v2.py +532 -0
  175. codeframe/ui/routers/interactive_sessions_v2.py +238 -0
  176. codeframe/ui/routers/pr_v2.py +709 -0
  177. codeframe/ui/routers/prd_v2.py +695 -0
  178. codeframe/ui/routers/proof_v2.py +755 -0
  179. codeframe/ui/routers/review_v2.py +360 -0
  180. codeframe/ui/routers/schedule_v2.py +214 -0
  181. codeframe/ui/routers/session_chat_ws.py +354 -0
  182. codeframe/ui/routers/settings_v2.py +562 -0
  183. codeframe/ui/routers/streaming_v2.py +155 -0
  184. codeframe/ui/routers/tasks_v2.py +1098 -0
  185. codeframe/ui/routers/templates_v2.py +232 -0
  186. codeframe/ui/routers/terminal_ws.py +267 -0
  187. codeframe/ui/routers/workspace_v2.py +527 -0
  188. codeframe/ui/server.py +568 -0
  189. codeframe/ui/shared.py +241 -0
  190. codeframe/workspace/__init__.py +5 -0
  191. codeframe/workspace/manager.py +249 -0
  192. codeframe_ai-0.9.0.dist-info/METADATA +517 -0
  193. codeframe_ai-0.9.0.dist-info/RECORD +197 -0
  194. codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
  195. codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
  196. codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
  197. codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,375 @@
1
+ """Event logging for CodeFRAME v2.
2
+
3
+ Provides an append-only event log for workspace activity. Events are:
4
+ - Stored durably in SQLite
5
+ - Printed to stdout for CLI visibility
6
+ - Available for tailing/streaming
7
+
8
+ This module is headless - no FastAPI or HTTP dependencies.
9
+ """
10
+
11
+ import json
12
+ from dataclasses import dataclass
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Any, Iterator, Optional
16
+
17
+ from rich.console import Console
18
+
19
+ from codeframe.core.workspace import get_workspace, get_db_connection, Workspace
20
+
21
+
22
+ def _utc_now() -> datetime:
23
+ """Get current UTC time as timezone-aware datetime."""
24
+ return datetime.now(timezone.utc)
25
+
26
+ console = Console()
27
+
28
+
29
+ # Event type constants (for consistency)
30
+ class EventType:
31
+ """Standard event types emitted by core modules."""
32
+
33
+ # Workspace events
34
+ WORKSPACE_INIT = "WORKSPACE_INIT"
35
+
36
+ # PRD events
37
+ PRD_ADDED = "PRD_ADDED"
38
+ PRD_UPDATED = "PRD_UPDATED"
39
+ PRD_DELETED = "PRD_DELETED"
40
+
41
+ # Task events
42
+ TASKS_GENERATED = "TASKS_GENERATED"
43
+ TASK_STATUS_CHANGED = "TASK_STATUS_CHANGED"
44
+ TASK_CREATED = "TASK_CREATED"
45
+ TASK_UPDATED = "TASK_UPDATED"
46
+
47
+ # Run/execution events
48
+ RUN_STARTED = "RUN_STARTED"
49
+ RUN_COMPLETED = "RUN_COMPLETED"
50
+ RUN_FAILED = "RUN_FAILED"
51
+
52
+ # Agent events
53
+ AGENT_STEP_STARTED = "AGENT_STEP_STARTED"
54
+ AGENT_STEP_COMPLETED = "AGENT_STEP_COMPLETED"
55
+
56
+ # ReactAgent lifecycle events
57
+ AGENT_STARTED = "AGENT_STARTED"
58
+ AGENT_COMPLETED = "AGENT_COMPLETED"
59
+ AGENT_FAILED = "AGENT_FAILED"
60
+ AGENT_ITERATION_STARTED = "AGENT_ITERATION_STARTED"
61
+ AGENT_ITERATION_COMPLETED = "AGENT_ITERATION_COMPLETED"
62
+ AGENT_TOOL_DISPATCHED = "AGENT_TOOL_DISPATCHED"
63
+ AGENT_TOOL_RESULT = "AGENT_TOOL_RESULT"
64
+ AGENT_COMPACTION = "AGENT_COMPACTION"
65
+
66
+ # ReactAgent budget and optimization events
67
+ AGENT_AUTOFIX_APPLIED = "AGENT_AUTOFIX_APPLIED"
68
+ AGENT_EARLY_TERMINATION = "AGENT_EARLY_TERMINATION"
69
+ AGENT_BUDGET_CALCULATED = "AGENT_BUDGET_CALCULATED"
70
+ AGENT_STALL_DETECTED = "AGENT_STALL_DETECTED"
71
+
72
+ # Blocker events
73
+ BLOCKER_CREATED = "BLOCKER_CREATED"
74
+ BLOCKER_ANSWERED = "BLOCKER_ANSWERED"
75
+ BLOCKER_RESOLVED = "BLOCKER_RESOLVED"
76
+
77
+ # Gate events
78
+ GATES_STARTED = "GATES_STARTED"
79
+ GATES_COMPLETED = "GATES_COMPLETED"
80
+
81
+ # Artifact events
82
+ PATCH_EXPORTED = "PATCH_EXPORTED"
83
+ COMMIT_CREATED = "COMMIT_CREATED"
84
+ FILES_MODIFIED = "FILES_MODIFIED"
85
+
86
+ # Checkpoint events
87
+ CHECKPOINT_CREATED = "CHECKPOINT_CREATED"
88
+ CHECKPOINT_RESTORED = "CHECKPOINT_RESTORED"
89
+
90
+ # Status events
91
+ STATUS_VIEWED = "STATUS_VIEWED"
92
+ SUMMARY_VIEWED = "SUMMARY_VIEWED"
93
+
94
+ # Batch execution events
95
+ BATCH_STARTED = "BATCH_STARTED"
96
+ BATCH_TASK_QUEUED = "BATCH_TASK_QUEUED"
97
+ BATCH_TASK_STARTED = "BATCH_TASK_STARTED"
98
+ BATCH_TASK_COMPLETED = "BATCH_TASK_COMPLETED"
99
+ BATCH_TASK_FAILED = "BATCH_TASK_FAILED"
100
+ BATCH_TASK_BLOCKED = "BATCH_TASK_BLOCKED"
101
+ BATCH_COMPLETED = "BATCH_COMPLETED"
102
+ BATCH_PARTIAL = "BATCH_PARTIAL"
103
+ BATCH_FAILED = "BATCH_FAILED"
104
+ BATCH_CANCELLED = "BATCH_CANCELLED"
105
+ BATCH_VALIDATION_FAILED = "BATCH_VALIDATION_FAILED"
106
+
107
+ # Hook events
108
+ HOOK_EXECUTED = "HOOK_EXECUTED"
109
+ HOOK_FAILED = "HOOK_FAILED"
110
+
111
+ # Reconciliation events
112
+ RECONCILIATION_STARTED = "RECONCILIATION_STARTED"
113
+ RECONCILIATION_TASK_SKIPPED = "RECONCILIATION_TASK_SKIPPED"
114
+ RECONCILIATION_TASK_REQUEUED = "RECONCILIATION_TASK_REQUEUED"
115
+ RECONCILIATION_ERROR = "RECONCILIATION_ERROR"
116
+
117
+ # Config reload events
118
+ CONFIG_RELOADED = "CONFIG_RELOADED"
119
+ CONFIG_RELOAD_FAILED = "CONFIG_RELOAD_FAILED"
120
+
121
+
122
+ @dataclass
123
+ class Event:
124
+ """Represents a recorded event.
125
+
126
+ Attributes:
127
+ id: Auto-incremented event ID
128
+ workspace_id: Workspace this event belongs to
129
+ event_type: Type of event (see EventType)
130
+ payload: JSON-serializable event data
131
+ created_at: When the event occurred
132
+ """
133
+
134
+ id: int
135
+ workspace_id: str
136
+ event_type: str
137
+ payload: dict[str, Any]
138
+ created_at: datetime
139
+
140
+
141
+ def emit(
142
+ workspace_id: str,
143
+ event_type: str,
144
+ payload: Optional[dict[str, Any]] = None,
145
+ *,
146
+ repo_path: Optional[Path] = None,
147
+ print_event: bool = True,
148
+ ) -> Event:
149
+ """Emit an event to the event log.
150
+
151
+ Events are stored durably and optionally printed to console.
152
+
153
+ Args:
154
+ workspace_id: Workspace ID to emit event for
155
+ event_type: Type of event (use EventType constants)
156
+ payload: Optional event data (must be JSON-serializable)
157
+ repo_path: Optional repo path (used to find workspace if not cached)
158
+ print_event: Whether to print event to console (default True)
159
+
160
+ Returns:
161
+ The created Event object
162
+ """
163
+ payload = payload or {}
164
+ now = _utc_now().isoformat()
165
+ payload_json = json.dumps(payload)
166
+
167
+ # Find workspace database
168
+ # For now, we need the repo_path to find the DB. In future, we could cache this.
169
+ if repo_path is None:
170
+ # Try current directory
171
+ repo_path = Path.cwd()
172
+
173
+ workspace = get_workspace(repo_path)
174
+ conn = get_db_connection(workspace)
175
+ try:
176
+ cursor = conn.cursor()
177
+ cursor.execute(
178
+ "INSERT INTO events (workspace_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
179
+ (workspace_id, event_type, payload_json, now),
180
+ )
181
+ event_id = cursor.lastrowid
182
+ conn.commit()
183
+ finally:
184
+ conn.close()
185
+
186
+ event = Event(
187
+ id=event_id,
188
+ workspace_id=workspace_id,
189
+ event_type=event_type,
190
+ payload=payload,
191
+ created_at=datetime.fromisoformat(now),
192
+ )
193
+
194
+ if print_event:
195
+ _print_event(event)
196
+
197
+ return event
198
+
199
+
200
+ def emit_for_workspace(
201
+ workspace: Workspace,
202
+ event_type: str,
203
+ payload: Optional[dict[str, Any]] = None,
204
+ *,
205
+ print_event: bool = True,
206
+ ) -> Event:
207
+ """Emit an event using a Workspace object directly.
208
+
209
+ Preferred when you already have a Workspace reference.
210
+
211
+ Args:
212
+ workspace: Workspace object
213
+ event_type: Type of event
214
+ payload: Optional event data
215
+ print_event: Whether to print event to console
216
+
217
+ Returns:
218
+ The created Event object
219
+ """
220
+ payload = payload or {}
221
+ now = _utc_now().isoformat()
222
+ payload_json = json.dumps(payload)
223
+
224
+ conn = get_db_connection(workspace)
225
+ try:
226
+ cursor = conn.cursor()
227
+ cursor.execute(
228
+ "INSERT INTO events (workspace_id, event_type, payload, created_at) VALUES (?, ?, ?, ?)",
229
+ (workspace.id, event_type, payload_json, now),
230
+ )
231
+ event_id = cursor.lastrowid
232
+ conn.commit()
233
+ finally:
234
+ conn.close()
235
+
236
+ event = Event(
237
+ id=event_id,
238
+ workspace_id=workspace.id,
239
+ event_type=event_type,
240
+ payload=payload,
241
+ created_at=datetime.fromisoformat(now),
242
+ )
243
+
244
+ if print_event:
245
+ _print_event(event)
246
+
247
+ return event
248
+
249
+
250
+ def list_recent(
251
+ workspace: Workspace,
252
+ limit: int = 20,
253
+ since_id: Optional[int] = None,
254
+ ) -> list[Event]:
255
+ """List recent events for a workspace.
256
+
257
+ Args:
258
+ workspace: Workspace to query
259
+ limit: Maximum number of events to return
260
+ since_id: Only return events after this ID (for pagination)
261
+
262
+ Returns:
263
+ List of Event objects, newest first
264
+ """
265
+ conn = get_db_connection(workspace)
266
+ try:
267
+ cursor = conn.cursor()
268
+
269
+ if since_id:
270
+ cursor.execute(
271
+ """
272
+ SELECT id, workspace_id, event_type, payload, created_at
273
+ FROM events
274
+ WHERE workspace_id = ? AND id > ?
275
+ ORDER BY id DESC
276
+ LIMIT ?
277
+ """,
278
+ (workspace.id, since_id, limit),
279
+ )
280
+ else:
281
+ cursor.execute(
282
+ """
283
+ SELECT id, workspace_id, event_type, payload, created_at
284
+ FROM events
285
+ WHERE workspace_id = ?
286
+ ORDER BY id DESC
287
+ LIMIT ?
288
+ """,
289
+ (workspace.id, limit),
290
+ )
291
+
292
+ rows = cursor.fetchall()
293
+ finally:
294
+ conn.close()
295
+
296
+ return [
297
+ Event(
298
+ id=row[0],
299
+ workspace_id=row[1],
300
+ event_type=row[2],
301
+ payload=json.loads(row[3]) if row[3] else {},
302
+ created_at=datetime.fromisoformat(row[4]),
303
+ )
304
+ for row in rows
305
+ ]
306
+
307
+
308
+ def tail(
309
+ workspace: Workspace,
310
+ since_id: int = 0,
311
+ ) -> Iterator[Event]:
312
+ """Tail the event log, yielding new events.
313
+
314
+ This is a generator that yields events as they appear.
315
+ Note: This is a simple polling implementation. For real-time,
316
+ consider using a file watcher or database triggers.
317
+
318
+ Args:
319
+ workspace: Workspace to tail
320
+ since_id: Start after this event ID
321
+
322
+ Yields:
323
+ Event objects as they are recorded
324
+ """
325
+ import time
326
+
327
+ last_id = since_id
328
+
329
+ while True:
330
+ events = list_recent(workspace, limit=50, since_id=last_id)
331
+
332
+ # Events are returned newest-first, so reverse for chronological order
333
+ for event in reversed(events):
334
+ if event.id > last_id:
335
+ last_id = event.id
336
+ yield event
337
+
338
+ time.sleep(0.5) # Poll interval
339
+
340
+
341
+ def _print_event(event: Event) -> None:
342
+ """Print an event to the console in a readable format."""
343
+ timestamp = event.created_at.strftime("%H:%M:%S")
344
+ type_color = _get_event_color(event.event_type)
345
+
346
+ console.print(
347
+ f"[dim]{timestamp}[/dim] [{type_color}]{event.event_type}[/{type_color}]",
348
+ end="",
349
+ )
350
+
351
+ # Print key payload items if present
352
+ if event.payload:
353
+ items = []
354
+ for key in ["path", "task_id", "status", "name", "title"]:
355
+ if key in event.payload:
356
+ items.append(f"{key}={event.payload[key]}")
357
+ if items:
358
+ console.print(f" [dim]{' '.join(items)}[/dim]")
359
+ else:
360
+ console.print()
361
+ else:
362
+ console.print()
363
+
364
+
365
+ def _get_event_color(event_type: str) -> str:
366
+ """Get the Rich color for an event type."""
367
+ if "ERROR" in event_type or "FAILED" in event_type:
368
+ return "red"
369
+ if "COMPLETED" in event_type or "CREATED" in event_type:
370
+ return "green"
371
+ if "STARTED" in event_type or "INIT" in event_type:
372
+ return "blue"
373
+ if "BLOCKED" in event_type or "BLOCKER" in event_type:
374
+ return "yellow"
375
+ return "cyan"