daita-agents 0.2.1__py3-none-any.whl → 0.2.3__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.
daita/cli/__init__.py CHANGED
@@ -16,8 +16,7 @@ Example usage:
16
16
  daita test --watch
17
17
 
18
18
  # Deploy
19
- daita push staging
20
- daita push production --force
19
+ daita push
21
20
 
22
21
  # Monitor
23
22
  daita status
@@ -25,7 +24,7 @@ Example usage:
25
24
  """
26
25
 
27
26
  # CLI version
28
- __version__ = "0.1.1"
27
+ __version__ = "0.1.2"
29
28
 
30
29
  # Import main CLI components
31
30
  from .main import cli, main
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Deployment management commands for Daita CLI.
3
3
 
4
- Provides commands to list, download, and manage deployments.
4
+ Provides commands to list and manage deployments.
5
5
  """
6
6
  import os
7
7
  import json
@@ -125,36 +125,6 @@ async def list_deployments(project_name: Optional[str] = None, environment: Opti
125
125
  except Exception as e:
126
126
  print(f" Failed to list deployments: {e}")
127
127
 
128
- async def download_deployment(deployment_id: str, output_path: Optional[str] = None):
129
- """Download a deployment package."""
130
- try:
131
- from ...cloud.lambda_deploy import LambdaDeployer
132
-
133
- # Get AWS region
134
- aws_region = os.getenv('AWS_REGION', 'us-east-1')
135
- deployer = LambdaDeployer(aws_region)
136
-
137
- # Determine output path
138
- if not output_path:
139
- output_path = f"{deployment_id}.zip"
140
-
141
- output_file = Path(output_path)
142
-
143
- print(f" Downloading deployment: {deployment_id}")
144
- print(f" Output: {output_file.absolute()}")
145
-
146
- # Download deployment
147
- success = await deployer.download_deployment(deployment_id, output_file)
148
-
149
- if success:
150
- file_size = output_file.stat().st_size
151
- print(f" Downloaded successfully ({file_size / 1024 / 1024:.1f}MB)")
152
- else:
153
- print(f" Download failed")
154
-
155
- except Exception as e:
156
- print(f" Failed to download deployment: {e}")
157
-
158
128
  async def show_deployment_details(deployment_id: str):
159
129
  """Show detailed information about a deployment."""
160
130
  try:
@@ -188,11 +158,6 @@ async def show_deployment_details(deployment_id: str):
188
158
  print(f"Version: {target_deployment.get('version', '1.0.0')}")
189
159
  print(f"Deployed At: {deployed_date.strftime('%Y-%m-%d %H:%M:%S UTC')}")
190
160
  print(f"Package Size: {target_deployment.get('package_size_bytes', 0) / 1024 / 1024:.1f}MB")
191
- print(f"Package Hash: {target_deployment.get('package_hash', 'N/A')}")
192
-
193
- if target_deployment.get('s3_bucket'):
194
- print(f"S3 Location: s3://{target_deployment['s3_bucket']}/{target_deployment['s3_key']}")
195
-
196
161
  print()
197
162
 
198
163
  # Show agents
@@ -256,38 +221,54 @@ async def rollback_deployment(deployment_id: str, environment: str = 'production
256
221
  print(f" Failed to rollback deployment: {e}")
257
222
 
258
223
  async def delete_deployment(deployment_id: str, force: bool = False):
259
- """Delete a deployment and its Lambda functions."""
224
+ """Delete a deployment via API."""
225
+ import aiohttp
226
+
260
227
  try:
261
- from ...cloud.lambda_deploy import LambdaDeployer
262
-
263
- # Get AWS region
264
- aws_region = os.getenv('AWS_REGION', 'us-east-1')
265
- deployer = LambdaDeployer(aws_region)
266
-
267
- print(f" Deleting deployment: {deployment_id}")
268
-
228
+ # Get API key
229
+ api_key = os.getenv('DAITA_API_KEY')
230
+ if not api_key:
231
+ print(" No DAITA_API_KEY found")
232
+ print(" Get your API key at daita-tech.io")
233
+ return
234
+
235
+ print(f" Deleting deployment: {deployment_id}")
236
+
269
237
  # Confirm deletion
270
238
  if not force:
271
- confirm = input(" Delete deployment and all Lambda functions? Type 'yes' to confirm: ")
239
+ confirm = input(" Delete deployment? Type 'yes' to confirm: ")
272
240
  if confirm != 'yes':
273
241
  print(" Deletion cancelled")
274
242
  return
275
-
276
- # Delete deployment
277
- result = await deployer.delete_deployment(deployment_id)
278
-
279
- if result['deleted_functions']:
280
- print(f" Deleted {len(result['deleted_functions'])} Lambda functions:")
281
- for func_name in result['deleted_functions']:
282
- print(f" {func_name}")
283
-
284
- if result['errors']:
285
- print(f" {len(result['errors'])} errors occurred:")
286
- for error in result['errors']:
287
- print(f" {error}")
288
-
289
- print(f" Note: S3 packages are retained for audit purposes")
290
-
243
+
244
+ # Get API endpoint
245
+ api_endpoint = os.getenv('DAITA_API_ENDPOINT', 'https://ondk4sdyv0.execute-api.us-east-1.amazonaws.com')
246
+
247
+ headers = {
248
+ "Authorization": f"Bearer {api_key}",
249
+ "User-Agent": "Daita-CLI/1.0.0"
250
+ }
251
+
252
+ async with aiohttp.ClientSession() as session:
253
+ url = f"{api_endpoint}/api/v1/deployments/{deployment_id}"
254
+
255
+ async with session.delete(url, headers=headers, timeout=30) as response:
256
+ if response.status == 200:
257
+ result = await response.json()
258
+ print(f" {result.get('message', 'Deployment deleted successfully')}")
259
+ elif response.status == 404:
260
+ print(" Deployment not found")
261
+ elif response.status == 401:
262
+ print(" Authentication failed")
263
+ print(" Check your DAITA_API_KEY")
264
+ else:
265
+ error_text = await response.text()
266
+ print(f" Deletion failed (HTTP {response.status})")
267
+ print(f" {error_text}")
268
+
269
+ except aiohttp.ClientConnectorError:
270
+ print(" Cannot connect to API")
271
+ print(" Check your internet connection")
291
272
  except Exception as e:
292
273
  print(f" Failed to delete deployment: {e}")
293
274
 
daita/cli/core/logs.py CHANGED
@@ -48,13 +48,13 @@ async def show_deployment_logs(environment=None, limit=10, follow=False, verbose
48
48
  print(f" No deployments found ({current_project}, {environment})")
49
49
  else:
50
50
  print(f" No deployments found ({current_project})")
51
- print(" Run 'daita push staging' to create your first deployment")
51
+ print(" Run 'daita push' to create your first deployment")
52
52
  else:
53
53
  print(" No deployments found")
54
54
  if environment:
55
55
  print(f" No deployments found for environment: {environment}")
56
56
  else:
57
- print(" Run 'daita push staging' to create your first deployment")
57
+ print(" Run 'daita push' to create your first deployment")
58
58
  return
59
59
 
60
60
  # Filter by environment if specified (additional client-side filtering)
daita/cli/core/run.py CHANGED
@@ -234,16 +234,16 @@ async def _follow_execution(
234
234
 
235
235
  async def _poll_for_result(
236
236
  session: aiohttp.ClientSession,
237
- api_base: str,
237
+ api_base: str,
238
238
  headers: Dict[str, str],
239
239
  execution_id: str,
240
240
  verbose: bool
241
241
  ) -> bool:
242
242
  """Poll for execution result without showing progress (for non-follow mode)."""
243
-
243
+
244
244
  max_polls = 360 # 3 minutes max wait (0.5s intervals)
245
245
  polls = 0
246
-
246
+
247
247
  while polls < max_polls:
248
248
  try:
249
249
  # Check execution status
@@ -251,86 +251,128 @@ async def _poll_for_result(
251
251
  f"{api_base}/api/v1/executions/{execution_id}",
252
252
  headers=headers
253
253
  ) as response:
254
-
254
+
255
255
  if response.status != 200:
256
- click.echo("⚠️ Failed to get execution status", err=True)
256
+ click.echo(" Failed to get execution status", err=True)
257
257
  return False
258
-
258
+
259
259
  result = await response.json()
260
260
  status = result['status']
261
-
261
+
262
262
  # Return result if complete
263
263
  if status in ['completed', 'success']: # Handle both status values
264
- click.echo(" Execution completed")
264
+ click.echo("\n" + "=" * 60)
265
+ click.echo(" EXECUTION COMPLETED")
266
+ click.echo("=" * 60)
265
267
  _display_result(result, verbose)
266
268
  return True
267
269
  elif status in ['failed', 'error']: # Handle both status values
268
- click.echo(" Execution failed")
270
+ click.echo("\n" + "=" * 60)
271
+ click.echo(" EXECUTION FAILED")
272
+ click.echo("=" * 60)
269
273
  if result.get('error'):
270
- click.echo(f" Error: {result['error']}")
274
+ click.echo(f"\nError: {result['error']}")
271
275
  return False
272
276
  elif status == 'cancelled':
273
- click.echo("🚫 Execution cancelled")
277
+ click.echo(" Execution cancelled")
274
278
  return False
275
-
279
+
276
280
  # Continue polling
277
281
  await asyncio.sleep(0.5)
278
282
  polls += 1
279
-
283
+
280
284
  except Exception as e:
281
- click.echo(f"⚠️ Error polling execution: {e}", err=True)
285
+ click.echo(f" Error polling execution: {e}", err=True)
282
286
  return False
283
-
287
+
284
288
  # 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")
289
+ click.echo(" Execution still running (timeout reached)")
290
+ click.echo(f" Check status with: daita execution-logs {execution_id}")
291
+ click.echo(f" Follow progress with: daita execution-logs {execution_id} --follow")
288
292
  return False
289
293
 
290
294
  def _display_result(result: Dict[str, Any], verbose: bool):
291
295
  """Display execution results in a formatted way."""
292
-
293
- # Display basic info
296
+
297
+ # Display execution metadata
298
+ click.echo("\nExecution Metadata:")
299
+ click.echo("-" * 60)
300
+
294
301
  if result.get('duration_ms'):
295
302
  duration = float(result['duration_ms']) # Convert string to number
296
303
  if duration < 1000:
297
- click.echo(f" Duration: {duration:.0f}ms")
304
+ click.echo(f" Duration: {duration:.0f}ms")
298
305
  elif duration < 60000:
299
- click.echo(f" Duration: {duration/1000:.1f}s")
306
+ click.echo(f" Duration: {duration/1000:.1f}s")
300
307
  else:
301
- click.echo(f" Duration: {duration/60000:.1f}m")
302
-
308
+ click.echo(f" Duration: {duration/60000:.1f}m")
309
+
303
310
  if result.get('memory_used_mb'):
304
- click.echo(f" Memory: {result['memory_used_mb']:.1f}MB")
305
-
311
+ click.echo(f" Memory: {result['memory_used_mb']:.1f}MB")
312
+
306
313
  # Display result data
307
314
  if result.get('result'):
308
- click.echo("\n Result:")
315
+ click.echo("\n" + "-" * 60)
316
+ click.echo("Result:")
317
+ click.echo("-" * 60)
318
+
309
319
  if verbose:
310
- # Pretty print full result
311
- click.echo(json.dumps(result['result'], indent=2))
320
+ # Pretty print full result with indentation
321
+ result_json = json.dumps(result['result'], indent=2)
322
+ for line in result_json.split('\n'):
323
+ click.echo(f" {line}")
312
324
  else:
313
325
  # Display summary
314
326
  result_data = result['result']
327
+
315
328
  if isinstance(result_data, dict):
329
+ # Show key fields if present
316
330
  if 'status' in result_data:
317
- click.echo(f" Status: {result_data['status']}")
331
+ click.echo(f" Status: {result_data['status']}")
318
332
  if 'message' in result_data:
319
- click.echo(f" Message: {result_data['message']}")
333
+ # Word wrap long messages
334
+ message = result_data['message']
335
+ if len(message) > 70:
336
+ click.echo(f" Message: {message[:70]}...")
337
+ else:
338
+ click.echo(f" Message: {message}")
339
+
340
+ # Show presence of complex data types
341
+ data_types = []
320
342
  if 'insights' in result_data:
321
- click.echo(" Contains: insights data")
343
+ data_types.append('insights')
322
344
  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)}")
345
+ data_types.append('recommendations')
346
+ if 'data' in result_data:
347
+ data_types.append('data')
348
+ if 'results' in result_data:
349
+ data_types.append('results')
350
+
351
+ if data_types:
352
+ click.echo(f" Contains: {', '.join(data_types)}")
353
+
354
+ # Show all keys for reference
355
+ all_keys = list(result_data.keys())
356
+ if len(all_keys) > 0:
357
+ keys_display = ', '.join(all_keys[:8])
358
+ if len(all_keys) > 8:
359
+ keys_display += f" (+{len(all_keys) - 8} more)"
360
+ click.echo(f" Keys: {keys_display}")
361
+
362
+ click.echo("\n Tip: Use --verbose or -v flag to see full result")
363
+
364
+ elif isinstance(result_data, str):
365
+ # String result - show first part
366
+ if len(result_data) > 200:
367
+ click.echo(f" {result_data[:200]}...")
368
+ click.echo(f"\n (Result truncated - use -v for full output)")
369
+ else:
370
+ click.echo(f" {result_data}")
331
371
  else:
332
- click.echo(f" {result_data}")
333
-
372
+ # Other types
373
+ click.echo(f" {result_data}")
374
+
375
+ click.echo("=" * 60 + "\n")
334
376
 
335
377
  async def list_remote_executions(
336
378
  limit: int = 10,
@@ -371,8 +413,14 @@ async def list_remote_executions(
371
413
  ) as response:
372
414
 
373
415
  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)
416
+ try:
417
+ error_data = await response.json()
418
+ error_msg = error_data.get('detail', 'Unknown error')
419
+ except:
420
+ error_msg = await response.text()
421
+ click.echo(f" Failed to list executions: {error_msg}", err=True)
422
+ if verbose:
423
+ click.echo(f" Status code: {response.status}", err=True)
376
424
  return False
377
425
 
378
426
  executions = await response.json()
@@ -380,28 +428,33 @@ async def list_remote_executions(
380
428
  if not executions:
381
429
  click.echo(" No executions found")
382
430
  return True
383
-
384
- # Display executions
385
- click.echo(f" Recent executions ({len(executions)}):")
386
- click.echo()
387
-
431
+
432
+ # Display executions header
433
+ click.echo(f"\n Recent Executions ({len(executions)})")
434
+ click.echo(" " + "=" * 70)
435
+ click.echo(f" {'Status':<10} {'Agent/Workflow':<25} {'Time & Duration'}")
436
+ click.echo(" " + "-" * 70)
437
+
388
438
  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
-
439
+ # Map status to friendly text labels
440
+ status_label = {
441
+ 'completed': 'success',
442
+ 'success': 'success',
443
+ 'failed': 'error',
444
+ 'error': 'error',
445
+ 'running': 'running',
446
+ 'queued': 'queued',
447
+ 'cancelled': 'cancelled',
448
+ 'started': 'running'
449
+ }.get(execution['status'], execution['status'])
450
+
398
451
  # Format time
399
452
  created_at = execution['created_at']
400
453
  if 'T' in created_at:
401
454
  time_str = created_at.split('T')[1].split('.')[0]
402
455
  else:
403
456
  time_str = created_at
404
-
457
+
405
458
  # Format duration
406
459
  duration_str = "N/A"
407
460
  if execution.get('duration_ms'):
@@ -412,19 +465,31 @@ async def list_remote_executions(
412
465
  duration_str = f"{ms/1000:.1f}s"
413
466
  else:
414
467
  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
-
468
+
469
+ # Format agent/workflow name with type
470
+ target_info = f"{execution['target_name']} ({execution['target_type']})"
471
+
472
+ # Format time and duration info
473
+ time_duration = f"{time_str} ({duration_str})"
474
+
475
+ # Display execution line
476
+ click.echo(f" {status_label:<10} {target_info:<25} {time_duration}")
477
+
478
+ # Show full execution ID on next line (indented)
479
+ click.echo(f" {'':>10} ID: {execution['execution_id']}")
480
+
419
481
  if verbose and execution.get('error'):
420
- click.echo(f" Error: {execution['error']}")
421
-
422
- click.echo()
482
+ click.echo(f" {'':>10} Error: {execution['error']}")
483
+
484
+ click.echo() # Blank line between executions
423
485
 
424
486
  return True
425
-
487
+
426
488
  except Exception as e:
427
- click.echo(f" Error: {e}", err=True)
489
+ click.echo(f" Failed to list executions: {e}", err=True)
490
+ if verbose:
491
+ import traceback
492
+ click.echo(traceback.format_exc(), err=True)
428
493
  return False
429
494
 
430
495
  async def get_execution_logs(
daita/cli/core/status.py CHANGED
@@ -203,10 +203,10 @@ async def _show_cloud_deployments_status(verbose):
203
203
  if not deployments:
204
204
  if current_project:
205
205
  print(f" Cloud Deployments ({current_project}): None")
206
- print(" Run 'daita push staging' to deploy")
206
+ print(" Run 'daita push' to deploy")
207
207
  else:
208
208
  print(" Cloud Deployments: None")
209
- print(" Run 'daita push staging' to deploy")
209
+ print(" Run 'daita push' to deploy")
210
210
  print("")
211
211
  return
212
212
 
@@ -351,9 +351,15 @@ def _show_issues(project_root, config):
351
351
  if not workflow_file.exists():
352
352
  issues.append(f"Missing workflow file: {workflow['name']}.py")
353
353
 
354
- # Check for LLM API key
355
- if not _has_api_key():
356
- issues.append("No LLM API key found (set OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)")
354
+ # Check for LLM API key (check both local env and project .env for deployments)
355
+ has_local_key = _has_api_key()
356
+ has_project_env_key = _has_api_key_in_project_env(project_root)
357
+
358
+ if not has_local_key and not has_project_env_key:
359
+ issues.append("No LLM API key found (set OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. in .env or environment)")
360
+ elif not has_local_key:
361
+ # Has key in project .env (for deployments) but not in local env (for local testing)
362
+ issues.append("No LLM API key in local environment (project .env has keys for deployment)")
357
363
 
358
364
  # Show issues
359
365
  if issues:
@@ -375,8 +381,8 @@ def _show_issues(project_root, config):
375
381
  print(" daita test --watch # Development mode (free)")
376
382
 
377
383
  if has_daita_key:
378
- print(" daita push staging # Deploy to cloud")
379
- print(" daita logs staging # View cloud logs")
384
+ print(" daita push # Deploy to cloud")
385
+ print(" daita logs # View cloud logs")
380
386
  else:
381
387
  print(" ")
382
388
  print(" Ready for cloud deployment?")
@@ -408,14 +414,52 @@ def _load_project_config(project_root):
408
414
  return None
409
415
 
410
416
  def _has_api_key():
411
- """Check if API key is configured."""
417
+ """Check if API key is configured in local environment."""
412
418
  import os
413
419
  return bool(
414
- os.getenv('OPENAI_API_KEY') or
420
+ os.getenv('OPENAI_API_KEY') or
415
421
  os.getenv('ANTHROPIC_API_KEY') or
416
- os.getenv('GEMINI_API_KEY')
422
+ os.getenv('GEMINI_API_KEY') or
423
+ os.getenv('GROK_API_KEY')
417
424
  )
418
425
 
426
+ def _has_api_key_in_project_env(project_root):
427
+ """Check if API key is configured in project's .env file (for deployments)."""
428
+ env_file = project_root / '.env'
429
+ if not env_file.exists():
430
+ return False
431
+
432
+ try:
433
+ with open(env_file, 'r') as f:
434
+ env_content = f.read()
435
+
436
+ # Check for LLM API key patterns in .env file
437
+ llm_key_patterns = [
438
+ 'OPENAI_API_KEY=',
439
+ 'ANTHROPIC_API_KEY=',
440
+ 'GEMINI_API_KEY=',
441
+ 'GROK_API_KEY='
442
+ ]
443
+
444
+ for pattern in llm_key_patterns:
445
+ for line in env_content.splitlines():
446
+ # Skip comments and empty lines
447
+ line = line.strip()
448
+ if line.startswith('#') or not line:
449
+ continue
450
+
451
+ # Check if line has the pattern and a non-empty value
452
+ if line.startswith(pattern):
453
+ value = line.split('=', 1)[1].strip()
454
+ # Remove quotes if present
455
+ value = value.strip('"').strip("'")
456
+ if value and value != 'your-key-here' and value != 'sk-...':
457
+ return True
458
+
459
+ return False
460
+ except Exception:
461
+ return False
462
+
419
463
  # Alias for backward compatibility
420
464
  show_status = show_project_status
421
465