daita-agents 0.1.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.

Potentially problematic release.


This version of daita-agents might be problematic. Click here for more details.

Files changed (69) hide show
  1. daita/__init__.py +208 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +722 -0
  4. daita/agents/substrate.py +895 -0
  5. daita/cli/__init__.py +145 -0
  6. daita/cli/__main__.py +7 -0
  7. daita/cli/ascii_art.py +44 -0
  8. daita/cli/core/__init__.py +0 -0
  9. daita/cli/core/create.py +254 -0
  10. daita/cli/core/deploy.py +473 -0
  11. daita/cli/core/deployments.py +309 -0
  12. daita/cli/core/import_detector.py +219 -0
  13. daita/cli/core/init.py +382 -0
  14. daita/cli/core/logs.py +239 -0
  15. daita/cli/core/managed_deploy.py +709 -0
  16. daita/cli/core/run.py +648 -0
  17. daita/cli/core/status.py +421 -0
  18. daita/cli/core/test.py +239 -0
  19. daita/cli/core/webhooks.py +172 -0
  20. daita/cli/main.py +588 -0
  21. daita/cli/utils.py +541 -0
  22. daita/config/__init__.py +62 -0
  23. daita/config/base.py +159 -0
  24. daita/config/settings.py +184 -0
  25. daita/core/__init__.py +262 -0
  26. daita/core/decision_tracing.py +701 -0
  27. daita/core/exceptions.py +480 -0
  28. daita/core/focus.py +251 -0
  29. daita/core/interfaces.py +76 -0
  30. daita/core/plugin_tracing.py +550 -0
  31. daita/core/relay.py +695 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +444 -0
  34. daita/core/tools.py +402 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1084 -0
  37. daita/display/__init__.py +1 -0
  38. daita/display/console.py +160 -0
  39. daita/execution/__init__.py +58 -0
  40. daita/execution/client.py +856 -0
  41. daita/execution/exceptions.py +92 -0
  42. daita/execution/models.py +317 -0
  43. daita/llm/__init__.py +60 -0
  44. daita/llm/anthropic.py +166 -0
  45. daita/llm/base.py +373 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +152 -0
  48. daita/llm/grok.py +114 -0
  49. daita/llm/mock.py +135 -0
  50. daita/llm/openai.py +109 -0
  51. daita/plugins/__init__.py +141 -0
  52. daita/plugins/base.py +37 -0
  53. daita/plugins/base_db.py +167 -0
  54. daita/plugins/elasticsearch.py +844 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +510 -0
  57. daita/plugins/mysql.py +351 -0
  58. daita/plugins/postgresql.py +331 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +529 -0
  61. daita/plugins/s3.py +761 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.1.0.dist-info/METADATA +350 -0
  65. daita_agents-0.1.0.dist-info/RECORD +69 -0
  66. daita_agents-0.1.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.1.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.1.0.dist-info/licenses/LICENSE +56 -0
  69. daita_agents-0.1.0.dist-info/top_level.txt +1 -0
daita/cli/core/run.py ADDED
@@ -0,0 +1,648 @@
1
+ """
2
+ Remote execution command for running agents and workflows in the cloud.
3
+
4
+ This allows users to execute their deployed agents/workflows remotely
5
+ from the command line, with real-time status updates and result streaming.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import os
11
+ import sys
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Dict, Any, Optional
15
+ import aiohttp
16
+ import click
17
+ import yaml
18
+ from ..utils import find_project_root
19
+
20
+ async def run_remote_execution(
21
+ target_name: str,
22
+ target_type: str = "agent",
23
+ environment: str = "production",
24
+ data_file: Optional[str] = None,
25
+ data_json: Optional[str] = None,
26
+ task: str = "process",
27
+ follow: bool = False,
28
+ timeout: int = 300,
29
+ verbose: bool = False
30
+ ):
31
+ """
32
+ Execute an agent or workflow remotely in the cloud.
33
+
34
+ Args:
35
+ target_name: Name of the agent or workflow to execute
36
+ target_type: "agent" or "workflow"
37
+ environment: Environment to execute in (production, staging)
38
+ data_file: Path to JSON file containing input data
39
+ data_json: JSON string containing input data
40
+ task: Task to execute (for agents only)
41
+ follow: Whether to follow execution progress
42
+ timeout: Execution timeout in seconds
43
+ verbose: Enable verbose output
44
+ """
45
+
46
+ # Load environment variables from .env file if present
47
+ try:
48
+ from dotenv import load_dotenv
49
+ load_dotenv()
50
+ except ImportError:
51
+ pass # dotenv is optional
52
+
53
+ # Get API credentials
54
+ api_key = os.getenv('DAITA_API_KEY')
55
+ api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
56
+
57
+ if not api_key:
58
+ click.echo(" DAITA_API_KEY not found", err=True)
59
+ click.echo(" Get your API key from daita-tech.io", err=True)
60
+ click.echo(" Set it with: export DAITA_API_KEY='your-key-here'", err=True)
61
+ click.echo(" Or add it to your project's .env file", err=True)
62
+ return False
63
+
64
+ # Prepare input data
65
+ input_data = {}
66
+ if data_file:
67
+ try:
68
+ with open(data_file, 'r') as f:
69
+ input_data = json.load(f)
70
+ if verbose:
71
+ click.echo(f" Loaded data from {data_file}")
72
+ except Exception as e:
73
+ click.echo(f" Failed to load data file: {e}", err=True)
74
+ return False
75
+ elif data_json:
76
+ try:
77
+ input_data = json.loads(data_json)
78
+ except Exception as e:
79
+ click.echo(f" Invalid JSON data: {e}", err=True)
80
+ return False
81
+
82
+ # Prepare execution request
83
+ request_data = {
84
+ "data": input_data,
85
+ "environment": environment,
86
+ "timeout_seconds": timeout,
87
+ "execution_source": "cli",
88
+ "source_metadata": {
89
+ "cli_version": "1.0.0",
90
+ "command": f"daita run {target_name}",
91
+ "working_directory": str(Path.cwd())
92
+ }
93
+ }
94
+
95
+ # Validate agent exists and get file name for execution
96
+ if verbose:
97
+ click.echo(f" Looking up agent '{target_name}'...")
98
+
99
+ agent_info = validate_agent_exists(target_name, target_type)
100
+ if not agent_info:
101
+ return False
102
+
103
+ file_name = agent_info['file_name']
104
+ display_name = agent_info.get('display_name', file_name)
105
+
106
+ if verbose:
107
+ click.echo(f" Found agent: {file_name} → '{display_name}'")
108
+ click.echo(f" Executing with file name: '{file_name}'")
109
+
110
+ # Add target-specific fields using file name (API/Lambda expects this)
111
+ if target_type == "agent":
112
+ request_data["agent_name"] = file_name
113
+ request_data["task"] = task
114
+ else:
115
+ request_data["workflow_name"] = file_name
116
+
117
+ if verbose:
118
+ click.echo(f" Executing {target_type} '{target_name}' in {environment}")
119
+ if input_data:
120
+ click.echo(f" Input data: {len(str(input_data))} characters")
121
+
122
+ # Execute the request
123
+ headers = {
124
+ 'Authorization': f'Bearer {api_key}',
125
+ 'Content-Type': 'application/json',
126
+ 'User-Agent': 'Daita-CLI/1.0.0'
127
+ }
128
+
129
+ try:
130
+ async with aiohttp.ClientSession() as session:
131
+ # Submit execution request
132
+ async with session.post(
133
+ f"{api_base}/api/v1/executions/execute",
134
+ headers=headers,
135
+ json=request_data
136
+ ) as response:
137
+
138
+ if response.status != 200:
139
+ error_data = await response.json()
140
+ error_detail = error_data.get('detail', 'Unknown error')
141
+ click.echo(f" Execution failed: {error_detail}", err=True)
142
+
143
+ # Provide helpful guidance based on error type
144
+ if response.status == 404 and "No deployment found" in error_detail:
145
+ click.echo(f" Possible causes:", err=True)
146
+ click.echo(f" • Agent not deployed: daita push {environment}", err=True)
147
+ click.echo(f" • Wrong agent name (using: '{file_name}')", err=True)
148
+ click.echo(f" • Check deployed agents: daita status", err=True)
149
+
150
+ return False
151
+
152
+ result = await response.json()
153
+ execution_id = result['execution_id']
154
+
155
+ if verbose:
156
+ click.echo(f" Execution ID: {execution_id}")
157
+
158
+ # All executions are now asynchronous
159
+ if follow:
160
+ # Show progress while following
161
+ return await _follow_execution(session, api_base, headers, execution_id, verbose)
162
+ else:
163
+ # Poll once for immediate result, then return
164
+ return await _poll_for_result(session, api_base, headers, execution_id, verbose)
165
+
166
+ except aiohttp.ClientError as e:
167
+ click.echo(f" Network error: {e}", err=True)
168
+ return False
169
+ except Exception as e:
170
+ click.echo(f" Unexpected error: {e}", err=True)
171
+ return False
172
+
173
+ async def _follow_execution(
174
+ session: aiohttp.ClientSession,
175
+ api_base: str,
176
+ headers: Dict[str, str],
177
+ execution_id: str,
178
+ verbose: bool
179
+ ) -> bool:
180
+ """Follow an asynchronous execution until completion."""
181
+
182
+ click.echo(" Following execution progress...")
183
+
184
+ last_status = None
185
+ start_time = time.time()
186
+
187
+ try:
188
+ while True:
189
+ # Check execution status
190
+ async with session.get(
191
+ f"{api_base}/api/v1/executions/{execution_id}",
192
+ headers=headers
193
+ ) as response:
194
+
195
+ if response.status != 200:
196
+ click.echo(" Failed to get execution status", err=True)
197
+ return False
198
+
199
+ result = await response.json()
200
+ status = result['status']
201
+
202
+ # Display status changes
203
+ if status != last_status:
204
+ elapsed = int(time.time() - start_time)
205
+
206
+ if status == 'running':
207
+ click.echo(f" Execution started (after {elapsed}s)")
208
+ elif status in ['completed', 'success']: # Handle both status values
209
+ click.echo(f" Execution completed (after {elapsed}s)")
210
+ _display_result(result, verbose)
211
+ return True
212
+ elif status in ['failed', 'error']: # Handle both status values
213
+ click.echo(f" Execution failed (after {elapsed}s)")
214
+ if result.get('error'):
215
+ click.echo(f" Error: {result['error']}")
216
+ return False
217
+ elif status == 'cancelled':
218
+ click.echo(f" Execution cancelled (after {elapsed}s)")
219
+ return False
220
+
221
+ last_status = status
222
+
223
+ # Break if execution is complete
224
+ if status in ['completed', 'success', 'failed', 'error', 'cancelled']:
225
+ break
226
+
227
+ # Wait before next check
228
+ await asyncio.sleep(2)
229
+
230
+ except KeyboardInterrupt:
231
+ click.echo("\n Stopped following execution (execution continues in background)")
232
+ click.echo(f" Check status with: daita logs --execution-id {execution_id}")
233
+ return False
234
+
235
+ async def _poll_for_result(
236
+ session: aiohttp.ClientSession,
237
+ api_base: str,
238
+ headers: Dict[str, str],
239
+ execution_id: str,
240
+ verbose: bool
241
+ ) -> bool:
242
+ """Poll for execution result without showing progress (for non-follow mode)."""
243
+
244
+ max_polls = 360 # 3 minutes max wait (0.5s intervals)
245
+ polls = 0
246
+
247
+ while polls < max_polls:
248
+ try:
249
+ # Check execution status
250
+ async with session.get(
251
+ f"{api_base}/api/v1/executions/{execution_id}",
252
+ headers=headers
253
+ ) as response:
254
+
255
+ if response.status != 200:
256
+ click.echo("⚠️ Failed to get execution status", err=True)
257
+ return False
258
+
259
+ result = await response.json()
260
+ status = result['status']
261
+
262
+ # Return result if complete
263
+ if status in ['completed', 'success']: # Handle both status values
264
+ click.echo("✅ Execution completed")
265
+ _display_result(result, verbose)
266
+ return True
267
+ elif status in ['failed', 'error']: # Handle both status values
268
+ click.echo("❌ Execution failed")
269
+ if result.get('error'):
270
+ click.echo(f" Error: {result['error']}")
271
+ return False
272
+ elif status == 'cancelled':
273
+ click.echo("🚫 Execution cancelled")
274
+ return False
275
+
276
+ # Continue polling
277
+ await asyncio.sleep(0.5)
278
+ polls += 1
279
+
280
+ except Exception as e:
281
+ click.echo(f"⚠️ Error polling execution: {e}", err=True)
282
+ return False
283
+
284
+ # Timeout reached
285
+ click.echo("⏰ Execution still running (timeout reached)")
286
+ click.echo(f" Check status with: daita logs --execution-id {execution_id}")
287
+ click.echo(f" Follow progress with: daita run {execution_id} --follow")
288
+ return False
289
+
290
+ def _display_result(result: Dict[str, Any], verbose: bool):
291
+ """Display execution results in a formatted way."""
292
+
293
+ # Display basic info
294
+ if result.get('duration_ms'):
295
+ duration = float(result['duration_ms']) # Convert string to number
296
+ if duration < 1000:
297
+ click.echo(f" Duration: {duration:.0f}ms")
298
+ elif duration < 60000:
299
+ click.echo(f" Duration: {duration/1000:.1f}s")
300
+ else:
301
+ click.echo(f" Duration: {duration/60000:.1f}m")
302
+
303
+ if result.get('memory_used_mb'):
304
+ click.echo(f" Memory: {result['memory_used_mb']:.1f}MB")
305
+
306
+ # Display result data
307
+ if result.get('result'):
308
+ click.echo("\n Result:")
309
+ if verbose:
310
+ # Pretty print full result
311
+ click.echo(json.dumps(result['result'], indent=2))
312
+ else:
313
+ # Display summary
314
+ result_data = result['result']
315
+ if isinstance(result_data, dict):
316
+ if 'status' in result_data:
317
+ click.echo(f" Status: {result_data['status']}")
318
+ if 'message' in result_data:
319
+ click.echo(f" Message: {result_data['message']}")
320
+ if 'insights' in result_data:
321
+ click.echo(" Contains: insights data")
322
+ if 'recommendations' in result_data:
323
+ click.echo(" Contains: recommendations")
324
+
325
+ # Show keys for complex objects
326
+ if len(result_data) > 3:
327
+ keys = list(result_data.keys())[:5]
328
+ if len(result_data) > 5:
329
+ keys.append("...")
330
+ click.echo(f" Keys: {', '.join(keys)}")
331
+ else:
332
+ click.echo(f" {result_data}")
333
+
334
+
335
+ async def list_remote_executions(
336
+ limit: int = 10,
337
+ status: Optional[str] = None,
338
+ target_type: Optional[str] = None,
339
+ environment: Optional[str] = None,
340
+ verbose: bool = False
341
+ ):
342
+ """List recent executions with filtering."""
343
+
344
+ api_key = os.getenv('DAITA_API_KEY')
345
+ api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
346
+
347
+ if not api_key:
348
+ click.echo(" DAITA_API_KEY not found", err=True)
349
+ return False
350
+
351
+ # Build query parameters
352
+ params = {'limit': limit}
353
+ if status:
354
+ params['status'] = status
355
+ if target_type:
356
+ params['target_type'] = target_type
357
+ if environment:
358
+ params['environment'] = environment
359
+
360
+ headers = {
361
+ 'Authorization': f'Bearer {api_key}',
362
+ 'User-Agent': 'Daita-CLI/1.0.0'
363
+ }
364
+
365
+ try:
366
+ async with aiohttp.ClientSession() as session:
367
+ async with session.get(
368
+ f"{api_base}/api/v1/executions",
369
+ headers=headers,
370
+ params=params
371
+ ) as response:
372
+
373
+ if response.status != 200:
374
+ error_data = await response.json()
375
+ click.echo(f" Failed to list executions: {error_data.get('detail', 'Unknown error')}", err=True)
376
+ return False
377
+
378
+ executions = await response.json()
379
+
380
+ if not executions:
381
+ click.echo(" No executions found")
382
+ return True
383
+
384
+ # Display executions
385
+ click.echo(f" Recent executions ({len(executions)}):")
386
+ click.echo()
387
+
388
+ for execution in executions:
389
+ # Status icon
390
+ status_icon = {
391
+ 'completed': '',
392
+ 'failed': '',
393
+ 'running': '',
394
+ 'queued': '',
395
+ 'cancelled': ''
396
+ }.get(execution['status'], '❓')
397
+
398
+ # Format time
399
+ created_at = execution['created_at']
400
+ if 'T' in created_at:
401
+ time_str = created_at.split('T')[1].split('.')[0]
402
+ else:
403
+ time_str = created_at
404
+
405
+ # Format duration
406
+ duration_str = "N/A"
407
+ if execution.get('duration_ms'):
408
+ ms = execution['duration_ms']
409
+ if ms < 1000:
410
+ duration_str = f"{ms}ms"
411
+ elif ms < 60000:
412
+ duration_str = f"{ms/1000:.1f}s"
413
+ else:
414
+ duration_str = f"{ms/60000:.1f}m"
415
+
416
+ click.echo(f"{status_icon} {execution['target_name']} ({execution['target_type']})")
417
+ click.echo(f" {execution['execution_id'][:8]}... | {time_str} | {duration_str} | {execution['environment']}")
418
+
419
+ if verbose and execution.get('error'):
420
+ click.echo(f" Error: {execution['error']}")
421
+
422
+ click.echo()
423
+
424
+ return True
425
+
426
+ except Exception as e:
427
+ click.echo(f" Error: {e}", err=True)
428
+ return False
429
+
430
+ async def get_execution_logs(
431
+ execution_id: str,
432
+ follow: bool = False,
433
+ verbose: bool = False
434
+ ):
435
+ """Get logs for a specific execution."""
436
+
437
+ api_key = os.getenv('DAITA_API_KEY')
438
+ api_base = os.getenv('DAITA_API_BASE') or os.getenv('DAITA_API_ENDPOINT') or 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com'
439
+
440
+ if not api_key:
441
+ click.echo(" DAITA_API_KEY not found", err=True)
442
+ return False
443
+
444
+ headers = {
445
+ 'Authorization': f'Bearer {api_key}',
446
+ 'User-Agent': 'Daita-CLI/1.0.0'
447
+ }
448
+
449
+ try:
450
+ async with aiohttp.ClientSession() as session:
451
+ if follow:
452
+ # Follow mode - continuously check status
453
+ await _follow_execution(session, api_base, headers, execution_id, verbose)
454
+ else:
455
+ # One-time status check
456
+ async with session.get(
457
+ f"{api_base}/api/v1/executions/{execution_id}",
458
+ headers=headers
459
+ ) as response:
460
+
461
+ if response.status == 404:
462
+ click.echo(" Execution not found", err=True)
463
+ return False
464
+ elif response.status != 200:
465
+ error_data = await response.json()
466
+ click.echo(f" Failed to get execution: {error_data.get('detail', 'Unknown error')}", err=True)
467
+ return False
468
+
469
+ result = await response.json()
470
+
471
+ # Display execution info
472
+ click.echo(f" Execution: {result['execution_id']}")
473
+ click.echo(f" Target: {result['target_name']} ({result['target_type']})")
474
+ click.echo(f" Environment: {result['environment']}")
475
+ click.echo(f" Status: {result['status']}")
476
+
477
+ if result.get('created_at'):
478
+ click.echo(f" Created: {result['created_at']}")
479
+
480
+ _display_result(result, verbose)
481
+
482
+ return True
483
+
484
+ except Exception as e:
485
+ click.echo(f" Error: {e}", err=True)
486
+ return False
487
+
488
+
489
+ async def _resolve_agent_id(agent_name: str, environment: str, api_key: str, api_base: str) -> Optional[str]:
490
+ """
491
+ Resolve agent name to deployed agent ID.
492
+
493
+ This function queries the deployments API to find the actual agent ID
494
+ that corresponds to the simple agent name provided by the user.
495
+
496
+ Args:
497
+ agent_name: Simple agent name (e.g., "sentiment_analyzer")
498
+ environment: Target environment (e.g., "staging", "production")
499
+ api_key: DAITA API key for authentication
500
+ api_base: API base URL
501
+
502
+ Returns:
503
+ Agent ID if found, None otherwise
504
+ """
505
+ try:
506
+ # Get current project name to filter deployments
507
+ project_root = find_project_root()
508
+ project_name = None
509
+ if project_root:
510
+ try:
511
+ import yaml
512
+ config_file = project_root / 'daita-project.yaml'
513
+ if config_file.exists():
514
+ with open(config_file, 'r') as f:
515
+ config = yaml.safe_load(f)
516
+ project_name = config.get('name')
517
+ except:
518
+ pass
519
+
520
+ headers = {
521
+ 'Authorization': f'Bearer {api_key}',
522
+ 'Content-Type': 'application/json',
523
+ 'User-Agent': 'Daita-CLI/1.0.0'
524
+ }
525
+
526
+ async with aiohttp.ClientSession() as session:
527
+ # Query deployments API to find matching agent
528
+ url = f"{api_base}/api/v1/deployments"
529
+ params = {
530
+ 'environment': environment,
531
+ 'status': 'active'
532
+ }
533
+
534
+ # Add project filter if available
535
+ if project_name:
536
+ params['project_name'] = project_name
537
+
538
+ async with session.get(url, headers=headers, params=params) as response:
539
+ if response.status == 200:
540
+ deployments = await response.json()
541
+
542
+ # Debug logging
543
+ click.echo(f" DEBUG: Found {len(deployments)} deployments for project '{project_name}' in {environment}", err=True)
544
+
545
+ # Search through deployments for matching agent
546
+ for deployment in deployments:
547
+ agents_config = deployment.get('agents_config', [])
548
+ for agent_config in agents_config:
549
+ # Extract agent file name from agent_id
550
+ # Format: {org_id}_{project_name}_{agent_file_name}
551
+ agent_id = agent_config.get('agent_id', '')
552
+ agent_file_name = ''
553
+
554
+ if agent_id and project_name:
555
+ # Try to extract agent name by removing org_id and project_name prefix
556
+ # Expected format: "1_{project_name}_{agent_name}" where project_name may contain hyphens
557
+ # Strategy: look for pattern that starts with "1_{project_name}_"
558
+ prefix = f"1_{project_name}_"
559
+ if agent_id.startswith(prefix):
560
+ # Extract everything after the prefix
561
+ agent_file_name = agent_id[len(prefix):]
562
+
563
+ # Match by multiple criteria:
564
+ display_name = agent_config.get('agent_name', '')
565
+
566
+ if (agent_name == agent_file_name or
567
+ agent_name == display_name or
568
+ agent_name.replace('_', ' ').title() == display_name or
569
+ agent_name.replace('_', '').lower() == display_name.replace(' ', '').lower()):
570
+ return agent_config.get('agent_id')
571
+
572
+ return None
573
+ else:
574
+ # If API call fails, return None to let execution try with original name
575
+ click.echo(f" DEBUG: Deployments API returned status {response.status}", err=True)
576
+ return None
577
+
578
+ except Exception:
579
+ # If resolution fails, return None to let execution try with original name
580
+ return None
581
+
582
+
583
+ def validate_agent_exists(target_name: str, target_type: str = "agent") -> Optional[dict]:
584
+ """
585
+ Validate agent exists and return file name and display name.
586
+
587
+ Args:
588
+ target_name: Name of the agent/workflow (file name or display name)
589
+ target_type: "agent" or "workflow"
590
+
591
+ Returns:
592
+ Dict with 'file_name' and 'display_name' if found, None otherwise
593
+ """
594
+ project_root = find_project_root()
595
+ if not project_root:
596
+ click.echo(" No daita-project.yaml found")
597
+ click.echo(" Run 'daita init' to create a project")
598
+ return None
599
+
600
+ config_file = project_root / 'daita-project.yaml'
601
+ if not config_file.exists():
602
+ click.echo(" No daita-project.yaml found")
603
+ click.echo(" Run 'daita init' to create a project")
604
+ return None
605
+
606
+ try:
607
+ with open(config_file, 'r') as f:
608
+ config = yaml.safe_load(f)
609
+ except Exception as e:
610
+ click.echo(f" Failed to read daita-project.yaml: {e}")
611
+ return None
612
+
613
+ if not config:
614
+ click.echo(" Invalid daita-project.yaml file")
615
+ return None
616
+
617
+ # Get the appropriate component list
618
+ component_key = 'agents' if target_type == 'agent' else 'workflows'
619
+ components = config.get(component_key, [])
620
+
621
+ # Find the component by name OR display_name
622
+ component = next((c for c in components if c.get('name') == target_name), None)
623
+
624
+ # If not found by name, try finding by display_name
625
+ if not component:
626
+ component = next((c for c in components if c.get('display_name') == target_name), None)
627
+
628
+ if not component:
629
+ available_names = [c.get('name', 'unknown') for c in components]
630
+ click.echo(f" {target_type.title()} '{target_name}' not found in project")
631
+ if available_names:
632
+ click.echo(f" Available {component_key} (use file names): {', '.join(available_names)}")
633
+ else:
634
+ click.echo(f" No {component_key} found. Create one with: daita create {target_type}")
635
+ click.echo(" Use file names for execution (e.g., 'my_agent' not 'My Agent')")
636
+ return None
637
+
638
+ file_name = component.get('name')
639
+ display_name = component.get('display_name')
640
+
641
+ if not file_name:
642
+ click.echo(f" {target_type.title()} missing name in config")
643
+ return None
644
+
645
+ return {
646
+ 'file_name': file_name,
647
+ 'display_name': display_name or file_name
648
+ }