daita-agents 0.2.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 (69) hide show
  1. daita/__init__.py +216 -0
  2. daita/agents/__init__.py +33 -0
  3. daita/agents/base.py +743 -0
  4. daita/agents/substrate.py +1141 -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 +481 -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 +779 -0
  32. daita/core/reliability.py +381 -0
  33. daita/core/scaling.py +459 -0
  34. daita/core/tools.py +554 -0
  35. daita/core/tracing.py +770 -0
  36. daita/core/workflow.py +1144 -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 +291 -0
  45. daita/llm/base.py +530 -0
  46. daita/llm/factory.py +101 -0
  47. daita/llm/gemini.py +355 -0
  48. daita/llm/grok.py +219 -0
  49. daita/llm/mock.py +172 -0
  50. daita/llm/openai.py +220 -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 +849 -0
  55. daita/plugins/mcp.py +481 -0
  56. daita/plugins/mongodb.py +520 -0
  57. daita/plugins/mysql.py +362 -0
  58. daita/plugins/postgresql.py +342 -0
  59. daita/plugins/redis_messaging.py +500 -0
  60. daita/plugins/rest.py +537 -0
  61. daita/plugins/s3.py +770 -0
  62. daita/plugins/slack.py +729 -0
  63. daita/utils/__init__.py +18 -0
  64. daita_agents-0.2.0.dist-info/METADATA +409 -0
  65. daita_agents-0.2.0.dist-info/RECORD +69 -0
  66. daita_agents-0.2.0.dist-info/WHEEL +5 -0
  67. daita_agents-0.2.0.dist-info/entry_points.txt +2 -0
  68. daita_agents-0.2.0.dist-info/licenses/LICENSE +56 -0
  69. daita_agents-0.2.0.dist-info/top_level.txt +1 -0
daita/cli/main.py ADDED
@@ -0,0 +1,588 @@
1
+ """
2
+ Daita CLI - Command Line Interface for Daita Agents.
3
+
4
+ Simple, git-like CLI for building and deploying AI agents.
5
+
6
+ Usage:
7
+ daita init [project-name] # Initialize new project
8
+ daita create agent <name> # Create agent
9
+ daita create workflow <name> # Create workflow
10
+ daita test [target] # Test agents/workflows
11
+ daita push # Deploy to production
12
+ daita status # Show project status
13
+ daita logs # View deployment logs
14
+ """
15
+ import click
16
+ import asyncio
17
+ import logging
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ # Load environment variables from .env file
22
+ try:
23
+ from dotenv import load_dotenv
24
+ load_dotenv()
25
+ except ImportError:
26
+ # dotenv is optional, ignore if not installed
27
+ pass
28
+
29
+ # Check for required CLI dependencies
30
+ def check_cli_dependencies():
31
+ """Check if CLI dependencies are installed."""
32
+ missing = []
33
+
34
+ try:
35
+ import yaml
36
+ except ImportError:
37
+ missing.append("PyYAML")
38
+
39
+ try:
40
+ import watchdog
41
+ except ImportError:
42
+ missing.append("watchdog")
43
+
44
+ if missing:
45
+ click.echo(" Missing CLI dependencies. Install with:", err=True)
46
+ click.echo(f" pip install {' '.join(missing)}", err=True)
47
+ click.echo(" Or install all CLI dependencies with:", err=True)
48
+ click.echo(" pip install daita-agents[cli]", err=True)
49
+ sys.exit(1)
50
+
51
+ # Import core functions only after dependency check
52
+ def _import_core_functions():
53
+ """Import core CLI functions after dependency check."""
54
+ try:
55
+ from .core.init import initialize_project
56
+ from .core.create import create_from_template
57
+ from .core.test import run_tests
58
+ from .core.deploy import deploy_to_environment
59
+ from .core.status import show_project_status
60
+ from .core.logs import show_deployment_logs
61
+ from .core.deployments import (
62
+ list_deployments, download_deployment, show_deployment_details,
63
+ rollback_deployment, delete_deployment
64
+ )
65
+ from .core.run import (
66
+ run_remote_execution, list_remote_executions, get_execution_logs
67
+ )
68
+ return (
69
+ initialize_project,
70
+ create_from_template,
71
+ run_tests,
72
+ deploy_to_environment,
73
+ show_project_status,
74
+ show_deployment_logs,
75
+ list_deployments,
76
+ download_deployment,
77
+ show_deployment_details,
78
+ rollback_deployment,
79
+ delete_deployment,
80
+ run_remote_execution,
81
+ list_remote_executions,
82
+ get_execution_logs
83
+ )
84
+ except ImportError as e:
85
+ click.echo(f" Error importing CLI modules: {str(e)}", err=True)
86
+ sys.exit(1)
87
+
88
+ # CLI version
89
+ __version__ = "0.1.1"
90
+
91
+ def _check_first_time_usage():
92
+ """Check if this is the user's first time using the CLI and show welcome banner."""
93
+ import os
94
+ from pathlib import Path
95
+
96
+ # Create a marker file in user's home directory
97
+ marker_file = Path.home() / '.daita_cli_first_run'
98
+
99
+ # If marker doesn't exist, this is first run
100
+ if not marker_file.exists():
101
+ try:
102
+ # Show welcome banner
103
+ from .ascii_art import display_welcome_banner
104
+ display_welcome_banner()
105
+ click.echo(" Welcome to Daita! ")
106
+ click.echo(" Get started with: daita init my_project")
107
+ click.echo(" For help: daita --help")
108
+ click.echo("")
109
+
110
+ # Create marker file to remember this isn't first run anymore
111
+ marker_file.touch()
112
+ except Exception:
113
+ # If anything goes wrong, silently continue
114
+ pass
115
+
116
+ @click.group()
117
+ @click.version_option(version=__version__, prog_name="daita")
118
+ @click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
119
+ @click.option('--quiet', '-q', is_flag=True, help='Suppress non-error output')
120
+ @click.pass_context
121
+ def cli(ctx, verbose, quiet):
122
+ """
123
+ Daita CLI - AI Agent Framework Command Line Interface.
124
+
125
+ Build, test, and deploy AI agents with ease.
126
+ """
127
+ # Check CLI dependencies
128
+ check_cli_dependencies()
129
+
130
+ # Import core functions after dependency check
131
+ ctx.ensure_object(dict)
132
+ ctx.obj['core_functions'] = _import_core_functions()
133
+
134
+ # Setup logging
135
+ if verbose:
136
+ log_level = logging.DEBUG
137
+ elif quiet:
138
+ log_level = logging.ERROR
139
+ else:
140
+ log_level = logging.INFO
141
+
142
+ logging.basicConfig(
143
+ level=log_level,
144
+ format="%(levelname)s: %(message)s"
145
+ )
146
+
147
+ # Store options in context
148
+ ctx.obj['verbose'] = verbose
149
+ ctx.obj['quiet'] = quiet
150
+
151
+ # ======= Core Commands =======
152
+
153
+ @cli.command()
154
+ @click.argument('project_name', required=False)
155
+ @click.option('--type', 'project_type', default='basic',
156
+ type=click.Choice(['basic', 'analysis', 'pipeline']),
157
+ help='Type of project to create')
158
+ @click.option('--force', is_flag=True, help='Overwrite existing project')
159
+ @click.pass_context
160
+ def init(ctx, project_name, project_type, force):
161
+ """Initialize a new Daita project."""
162
+ try:
163
+ initialize_project = ctx.obj['core_functions'][0]
164
+ asyncio.run(initialize_project(
165
+ project_name=project_name,
166
+ project_type=project_type,
167
+ force=force,
168
+ verbose=ctx.obj.get('verbose', False)
169
+ ))
170
+ except KeyboardInterrupt:
171
+ click.echo("\n Operation cancelled.", err=True)
172
+ sys.exit(1)
173
+ except Exception as e:
174
+ if ctx.obj.get('verbose'):
175
+ logging.exception("Init command failed")
176
+ click.echo(f" Error: {str(e)}", err=True)
177
+ sys.exit(1)
178
+
179
+ @cli.group()
180
+ def create():
181
+ """Create agents, workflows, and other components."""
182
+ pass
183
+
184
+ @create.command()
185
+ @click.argument('name')
186
+ @click.pass_context
187
+ def agent(ctx, name):
188
+ """Create a new agent."""
189
+ try:
190
+ create_from_template = ctx.obj['core_functions'][1]
191
+ create_from_template(
192
+ template='agent',
193
+ name=name,
194
+ verbose=ctx.obj.get('verbose', False)
195
+ )
196
+ except Exception as e:
197
+ click.echo(f" Error: {str(e)}", err=True)
198
+ sys.exit(1)
199
+
200
+ @create.command()
201
+ @click.argument('name')
202
+ @click.pass_context
203
+ def workflow(ctx, name):
204
+ """Create a new workflow."""
205
+ try:
206
+ create_from_template = ctx.obj['core_functions'][1]
207
+ create_from_template(
208
+ template='workflow',
209
+ name=name,
210
+ verbose=ctx.obj.get('verbose', False)
211
+ )
212
+ except Exception as e:
213
+ click.echo(f" Error: {str(e)}", err=True)
214
+ sys.exit(1)
215
+
216
+ @cli.command()
217
+ @click.argument('target', required=False)
218
+ @click.option('--data', help='Test data file to use')
219
+ @click.option('--watch', is_flag=True, help='Watch for changes and re-run tests')
220
+ @click.pass_context
221
+ def test(ctx, target, data, watch):
222
+ """Test agents and workflows."""
223
+ try:
224
+ run_tests = ctx.obj['core_functions'][2]
225
+ asyncio.run(run_tests(
226
+ target=target,
227
+ data_file=data,
228
+ watch=watch,
229
+ verbose=ctx.obj.get('verbose', False)
230
+ ))
231
+ except KeyboardInterrupt:
232
+ click.echo("\n Tests cancelled.", err=True)
233
+ sys.exit(1)
234
+ except Exception as e:
235
+ if ctx.obj.get('verbose'):
236
+ logging.exception("Test command failed")
237
+ click.echo(f" Error: {str(e)}", err=True)
238
+ sys.exit(1)
239
+
240
+ @cli.command()
241
+ @click.option('--force', is_flag=True, help='Force deployment without confirmation')
242
+ @click.option('--dry-run', is_flag=True, help='Show what would be deployed')
243
+ @click.pass_context
244
+ def push(ctx, force, dry_run):
245
+ """Deploy to production (like git push)."""
246
+ environment = 'production'
247
+ try:
248
+ deploy_to_environment = ctx.obj['core_functions'][3]
249
+ asyncio.run(deploy_to_environment(
250
+ environment=environment,
251
+ force=force,
252
+ dry_run=dry_run,
253
+ verbose=ctx.obj.get('verbose', False)
254
+ ))
255
+ except KeyboardInterrupt:
256
+ click.echo("\n Deployment cancelled.", err=True)
257
+ sys.exit(1)
258
+ except Exception as e:
259
+ if ctx.obj.get('verbose'):
260
+ logging.exception("Deploy command failed")
261
+ click.echo(f" Error: {str(e)}", err=True)
262
+ sys.exit(1)
263
+
264
+ @cli.command()
265
+ @click.pass_context
266
+ def status(ctx):
267
+ """Show project and deployment status (like git status)."""
268
+ try:
269
+ show_project_status = ctx.obj['core_functions'][4]
270
+ asyncio.run(show_project_status(
271
+ environment='production', # Only production is supported
272
+ verbose=ctx.obj.get('verbose', False)
273
+ ))
274
+ except Exception as e:
275
+ if ctx.obj.get('verbose'):
276
+ logging.exception("Status command failed")
277
+ click.echo(f" Error: {str(e)}", err=True)
278
+ sys.exit(1)
279
+
280
+ @cli.command()
281
+ @click.option('--follow', '-f', is_flag=True, help='Follow log output')
282
+ @click.option('--lines', '-n', default=10, help='Number of lines to show')
283
+ @click.pass_context
284
+ def logs(ctx, follow, lines):
285
+ """View deployment logs (like git log)."""
286
+ try:
287
+ show_deployment_logs = ctx.obj['core_functions'][5]
288
+ asyncio.run(show_deployment_logs(
289
+ environment='production', # Only production is supported
290
+ limit=lines,
291
+ follow=follow,
292
+ verbose=ctx.obj.get('verbose', False)
293
+ ))
294
+ except KeyboardInterrupt:
295
+ if follow:
296
+ click.echo("\n Stopped following logs.", err=True)
297
+ sys.exit(1)
298
+ except Exception as e:
299
+ if ctx.obj.get('verbose'):
300
+ logging.exception("Logs command failed")
301
+ click.echo(f" Error: {str(e)}", err=True)
302
+ sys.exit(1)
303
+
304
+ # ======= Deployment Management Commands =======
305
+
306
+ @cli.group()
307
+ def deployments():
308
+ """Manage deployments."""
309
+ pass
310
+
311
+ @deployments.command('list')
312
+ @click.argument('project_name', required=False)
313
+ @click.option('--limit', default=10, help='Number of deployments to show')
314
+ @click.pass_context
315
+ def list_cmd(ctx, project_name, limit):
316
+ """List deployment history."""
317
+ try:
318
+ list_deployments_func = ctx.obj['core_functions'][6]
319
+ asyncio.run(list_deployments_func(project_name, 'production', limit))
320
+ except Exception as e:
321
+ if ctx.obj.get('verbose'):
322
+ logging.exception("List deployments failed")
323
+ click.echo(f" Error: {str(e)}", err=True)
324
+ sys.exit(1)
325
+
326
+ @deployments.command()
327
+ @click.argument('deployment_id')
328
+ @click.option('--output', '-o', help='Output file path')
329
+ @click.pass_context
330
+ def download(ctx, deployment_id, output):
331
+ """Download a deployment package."""
332
+ try:
333
+ download_deployment_func = ctx.obj['core_functions'][7]
334
+ asyncio.run(download_deployment_func(deployment_id, output))
335
+ except Exception as e:
336
+ if ctx.obj.get('verbose'):
337
+ logging.exception("Download deployment failed")
338
+ click.echo(f" Error: {str(e)}", err=True)
339
+ sys.exit(1)
340
+
341
+ @deployments.command()
342
+ @click.argument('deployment_id')
343
+ @click.pass_context
344
+ def show(ctx, deployment_id):
345
+ """Show detailed deployment information."""
346
+ try:
347
+ show_deployment_details_func = ctx.obj['core_functions'][8]
348
+ asyncio.run(show_deployment_details_func(deployment_id))
349
+ except Exception as e:
350
+ if ctx.obj.get('verbose'):
351
+ logging.exception("Show deployment failed")
352
+ click.echo(f" Error: {str(e)}", err=True)
353
+ sys.exit(1)
354
+
355
+ @deployments.command()
356
+ @click.argument('deployment_id')
357
+ @click.pass_context
358
+ def rollback(ctx, deployment_id):
359
+ """Rollback to a previous deployment."""
360
+ try:
361
+ rollback_deployment_func = ctx.obj['core_functions'][9]
362
+ asyncio.run(rollback_deployment_func(deployment_id, 'production'))
363
+ except Exception as e:
364
+ if ctx.obj.get('verbose'):
365
+ logging.exception("Rollback deployment failed")
366
+ click.echo(f" Error: {str(e)}", err=True)
367
+ sys.exit(1)
368
+
369
+ @deployments.command()
370
+ @click.argument('deployment_id')
371
+ @click.option('--force', is_flag=True, help='Skip confirmation')
372
+ @click.pass_context
373
+ def delete(ctx, deployment_id, force):
374
+ """Delete a deployment and its Lambda functions."""
375
+ try:
376
+ delete_deployment_func = ctx.obj['core_functions'][10]
377
+ asyncio.run(delete_deployment_func(deployment_id, force))
378
+ except Exception as e:
379
+ if ctx.obj.get('verbose'):
380
+ logging.exception("Delete deployment failed")
381
+ click.echo(f" Error: {str(e)}", err=True)
382
+ sys.exit(1)
383
+
384
+ # ======= Utility Commands =======
385
+
386
+ @cli.command()
387
+ def version():
388
+ """Show version information."""
389
+ click.echo(f"Daita CLI v{__version__}")
390
+ click.echo("AI Agent Framework")
391
+
392
+ @cli.command()
393
+ def docs():
394
+ """Open documentation in browser."""
395
+ import webbrowser
396
+ webbrowser.open("https://docs.daita-tech.io")
397
+ click.echo(" Opening documentation in browser...")
398
+
399
+ # ======= Main Entry Point =======
400
+
401
+ def main():
402
+ """Main CLI entry point."""
403
+ try:
404
+ # Import freemium utilities
405
+ from .utils import is_cloud_command, require_api_key_for_cloud, show_local_vs_cloud_help
406
+
407
+ # Check current command (skip global flags like --verbose, --quiet, -v, -q)
408
+ current_cmd = None
409
+ for arg in sys.argv[1:]:
410
+ if not arg.startswith('-'):
411
+ current_cmd = arg
412
+ break
413
+
414
+ # Handle help command with freemium info
415
+ if current_cmd in ['--help', '-h', 'help']:
416
+ # Show ASCII art before help
417
+ from .ascii_art import display_compact_banner
418
+ display_compact_banner()
419
+ # Let click handle help, but add freemium info
420
+ try:
421
+ cli()
422
+ except SystemExit:
423
+ show_local_vs_cloud_help()
424
+ raise
425
+ else:
426
+ # Check for first-time usage only if not showing help
427
+ _check_first_time_usage()
428
+
429
+ # Enforce API key for cloud commands (but not for help)
430
+ if current_cmd and is_cloud_command(current_cmd) and '--help' not in sys.argv:
431
+ require_api_key_for_cloud()
432
+
433
+ # Check if we're in a project for commands that need it
434
+ project_commands = ['create', 'test', 'push', 'status', 'logs', 'deployments', 'run', 'executions', 'execution-logs']
435
+
436
+ if current_cmd in project_commands:
437
+ # Check if we're in a Daita project
438
+ current = Path.cwd()
439
+ in_project = False
440
+ for path in [current] + list(current.parents):
441
+ if (path / '.daita').exists():
442
+ in_project = True
443
+ break
444
+
445
+ if not in_project:
446
+ click.echo(" Not in a Daita project directory.", err=True)
447
+ click.echo(" Run 'daita init' to create a new project.")
448
+ sys.exit(1)
449
+
450
+ # Run the CLI
451
+ cli()
452
+
453
+ except Exception as e:
454
+ click.echo(f" Unexpected error: {str(e)}", err=True)
455
+ sys.exit(1)
456
+
457
+ # ===== REMOTE EXECUTION COMMANDS =====
458
+
459
+ @cli.command()
460
+ @click.argument('target_name')
461
+ @click.option('--type', 'target_type', default='agent',
462
+ type=click.Choice(['agent', 'workflow']),
463
+ help='Type of target to execute')
464
+ @click.option('--data', 'data_file',
465
+ help='JSON file containing input data')
466
+ @click.option('--data-json',
467
+ help='JSON string containing input data')
468
+ @click.option('--task', default='process',
469
+ help='Task to execute (for agents only)')
470
+ @click.option('--follow', '-f', is_flag=True,
471
+ help='Follow execution progress in real-time')
472
+ @click.option('--timeout', default=300, type=int,
473
+ help='Execution timeout in seconds')
474
+ @click.pass_context
475
+ def run(ctx, target_name, target_type, data_file, data_json, task, follow, timeout):
476
+ """Execute an agent or workflow remotely in the cloud."""
477
+ environment = 'production' # Only production is supported
478
+ try:
479
+ run_remote_execution_func = ctx.obj['core_functions'][11]
480
+ success = asyncio.run(run_remote_execution_func(
481
+ target_name=target_name,
482
+ target_type=target_type,
483
+ environment=environment,
484
+ data_file=data_file,
485
+ data_json=data_json,
486
+ task=task,
487
+ follow=follow,
488
+ timeout=timeout,
489
+ verbose=ctx.obj.get('verbose', False)
490
+ ))
491
+ if not success:
492
+ sys.exit(1)
493
+ except KeyboardInterrupt:
494
+ click.echo("\n Operation cancelled.", err=True)
495
+ sys.exit(1)
496
+ except Exception as e:
497
+ if ctx.obj.get('verbose'):
498
+ logging.exception("Run command failed")
499
+ click.echo(f" Error: {str(e)}", err=True)
500
+ sys.exit(1)
501
+
502
+ @cli.command('executions')
503
+ @click.option('--limit', default=10, type=int,
504
+ help='Number of executions to show')
505
+ @click.option('--status',
506
+ type=click.Choice(['queued', 'running', 'completed', 'failed', 'cancelled']),
507
+ help='Filter by execution status')
508
+ @click.option('--type', 'target_type',
509
+ type=click.Choice(['agent', 'workflow']),
510
+ help='Filter by target type')
511
+ @click.pass_context
512
+ def list_executions(ctx, limit, status, target_type):
513
+ """List recent remote executions."""
514
+ try:
515
+ list_remote_executions_func = ctx.obj['core_functions'][12]
516
+ success = asyncio.run(list_remote_executions_func(
517
+ limit=limit,
518
+ status=status,
519
+ target_type=target_type,
520
+ environment='production', # Only production is supported
521
+ verbose=ctx.obj.get('verbose', False)
522
+ ))
523
+ if not success:
524
+ sys.exit(1)
525
+ except Exception as e:
526
+ if ctx.obj.get('verbose'):
527
+ logging.exception("List executions command failed")
528
+ click.echo(f" Error: {str(e)}", err=True)
529
+ sys.exit(1)
530
+
531
+ @cli.command('execution-logs')
532
+ @click.argument('execution_id')
533
+ @click.option('--follow', '-f', is_flag=True,
534
+ help='Follow execution progress')
535
+ @click.pass_context
536
+ def execution_logs(ctx, execution_id, follow):
537
+ """Get logs and status for a specific execution."""
538
+ try:
539
+ get_execution_logs_func = ctx.obj['core_functions'][13]
540
+ success = asyncio.run(get_execution_logs_func(
541
+ execution_id=execution_id,
542
+ follow=follow,
543
+ verbose=ctx.obj.get('verbose', False)
544
+ ))
545
+ if not success:
546
+ sys.exit(1)
547
+ except KeyboardInterrupt:
548
+ if follow:
549
+ click.echo("\n Stopped following execution.", err=True)
550
+ sys.exit(1)
551
+ except Exception as e:
552
+ if ctx.obj.get('verbose'):
553
+ logging.exception("Execution logs command failed")
554
+ click.echo(f" Error: {str(e)}", err=True)
555
+ sys.exit(1)
556
+
557
+ # ======= Webhook Commands =======
558
+
559
+ @cli.group()
560
+ @click.pass_context
561
+ def webhook(ctx):
562
+ """Webhook management commands."""
563
+ pass
564
+
565
+ @webhook.command('list')
566
+ @click.option('--api-key-only', is_flag=True,
567
+ help='Show only webhooks created with current API key')
568
+ @click.pass_context
569
+ def webhook_list(ctx, api_key_only):
570
+ """List all webhook URLs for your organization."""
571
+ try:
572
+ # Import the webhook listing function
573
+ from .core.webhooks import list_webhooks
574
+
575
+ success = asyncio.run(list_webhooks(
576
+ api_key_only=api_key_only,
577
+ verbose=ctx.obj.get('verbose', False)
578
+ ))
579
+ if not success:
580
+ sys.exit(1)
581
+ except Exception as e:
582
+ if ctx.obj.get('verbose'):
583
+ logging.exception("Webhook list command failed")
584
+ click.echo(f" Error: {str(e)}", err=True)
585
+ sys.exit(1)
586
+
587
+ if __name__ == "__main__":
588
+ main()