daita-agents 0.2.0__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 +2 -3
- daita/cli/core/deployments.py +43 -62
- daita/cli/core/logs.py +2 -2
- daita/cli/core/managed_deploy.py +1 -12
- daita/cli/core/run.py +133 -68
- daita/cli/core/status.py +54 -10
- daita/cli/main.py +16 -38
- daita/cli/utils.py +1 -1
- daita/plugins/__init__.py +14 -2
- daita/plugins/snowflake.py +1159 -0
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/METADATA +5 -1
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/RECORD +16 -15
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/WHEEL +0 -0
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/entry_points.txt +0 -0
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {daita_agents-0.2.0.dist-info → daita_agents-0.2.3.dist-info}/top_level.txt +0 -0
daita/cli/__init__.py
CHANGED
|
@@ -16,8 +16,7 @@ Example usage:
|
|
|
16
16
|
daita test --watch
|
|
17
17
|
|
|
18
18
|
# Deploy
|
|
19
|
-
daita push
|
|
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.
|
|
27
|
+
__version__ = "0.1.2"
|
|
29
28
|
|
|
30
29
|
# Import main CLI components
|
|
31
30
|
from .main import cli, main
|
daita/cli/core/deployments.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Deployment management commands for Daita CLI.
|
|
3
3
|
|
|
4
|
-
Provides commands to list
|
|
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
|
|
224
|
+
"""Delete a deployment via API."""
|
|
225
|
+
import aiohttp
|
|
226
|
+
|
|
260
227
|
try:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
239
|
+
confirm = input(" Delete deployment? Type 'yes' to confirm: ")
|
|
272
240
|
if confirm != 'yes':
|
|
273
241
|
print(" Deletion cancelled")
|
|
274
242
|
return
|
|
275
|
-
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
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
|
|
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/managed_deploy.py
CHANGED
|
@@ -96,18 +96,7 @@ async def deploy_to_managed_environment(environment='production', force=False, d
|
|
|
96
96
|
|
|
97
97
|
print(f" Deployed to Daita-managed {environment}")
|
|
98
98
|
print(f" Deployment ID: {deployment_id}")
|
|
99
|
-
|
|
100
|
-
# Show deployed functions
|
|
101
|
-
if deploy_result.get('functions'):
|
|
102
|
-
print(f" Lambda Functions:")
|
|
103
|
-
for func in deploy_result['functions']:
|
|
104
|
-
# Get name from either agent_name or workflow_name
|
|
105
|
-
name = func.get('agent_name') or func.get('workflow_name', 'Unknown')
|
|
106
|
-
if func.get('status') == 'deployed':
|
|
107
|
-
print(f" {name}: {func['function_name']}")
|
|
108
|
-
else:
|
|
109
|
-
print(f" {name}: {func.get('error', 'Unknown error')}")
|
|
110
|
-
|
|
99
|
+
|
|
111
100
|
|
|
112
101
|
except aiohttp.ClientConnectorError:
|
|
113
102
|
print(" Cannot connect to deployment host")
|
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("
|
|
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("
|
|
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("
|
|
270
|
+
click.echo("\n" + "=" * 60)
|
|
271
|
+
click.echo(" EXECUTION FAILED")
|
|
272
|
+
click.echo("=" * 60)
|
|
269
273
|
if result.get('error'):
|
|
270
|
-
click.echo(f"
|
|
274
|
+
click.echo(f"\nError: {result['error']}")
|
|
271
275
|
return False
|
|
272
276
|
elif status == 'cancelled':
|
|
273
|
-
click.echo("
|
|
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"
|
|
285
|
+
click.echo(f" Error polling execution: {e}", err=True)
|
|
282
286
|
return False
|
|
283
|
-
|
|
287
|
+
|
|
284
288
|
# Timeout reached
|
|
285
|
-
click.echo("
|
|
286
|
-
click.echo(f" Check status with: daita
|
|
287
|
-
click.echo(f" Follow progress with: daita
|
|
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
|
|
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:
|
|
304
|
+
click.echo(f" Duration: {duration:.0f}ms")
|
|
298
305
|
elif duration < 60000:
|
|
299
|
-
click.echo(f" Duration:
|
|
306
|
+
click.echo(f" Duration: {duration/1000:.1f}s")
|
|
300
307
|
else:
|
|
301
|
-
click.echo(f" Duration:
|
|
302
|
-
|
|
308
|
+
click.echo(f" Duration: {duration/60000:.1f}m")
|
|
309
|
+
|
|
303
310
|
if result.get('memory_used_mb'):
|
|
304
|
-
click.echo(f"
|
|
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
|
|
315
|
+
click.echo("\n" + "-" * 60)
|
|
316
|
+
click.echo("Result:")
|
|
317
|
+
click.echo("-" * 60)
|
|
318
|
+
|
|
309
319
|
if verbose:
|
|
310
|
-
# Pretty print full result
|
|
311
|
-
|
|
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"
|
|
331
|
+
click.echo(f" Status: {result_data['status']}")
|
|
318
332
|
if 'message' in result_data:
|
|
319
|
-
|
|
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
|
-
|
|
343
|
+
data_types.append('insights')
|
|
322
344
|
if 'recommendations' in result_data:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
click.echo(f"
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
|
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
|
-
#
|
|
390
|
-
|
|
391
|
-
'completed': '',
|
|
392
|
-
'
|
|
393
|
-
'
|
|
394
|
-
'
|
|
395
|
-
'
|
|
396
|
-
|
|
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
|
-
|
|
417
|
-
|
|
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"
|
|
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"
|
|
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
|
|
206
|
+
print(" Run 'daita push' to deploy")
|
|
207
207
|
else:
|
|
208
208
|
print(" Cloud Deployments: None")
|
|
209
|
-
print(" Run 'daita push
|
|
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
|
-
|
|
356
|
-
|
|
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
|
|
379
|
-
print(" daita 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
|
|