tapps-agents 3.5.38__py3-none-any.whl → 3.5.39__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.
tapps_agents/cli/main.py CHANGED
@@ -1,645 +1,651 @@
1
- """
2
- Main CLI entry point
3
- """
4
- import argparse
5
- import asyncio
6
- import os
7
- import sys
8
- from collections.abc import Callable
9
-
10
- # Try to import version, with fallback to importlib.metadata
11
- try:
12
- from .. import __version__ as PACKAGE_VERSION
13
- except (ImportError, AttributeError):
14
- # Fallback: use importlib.metadata (standard library, Python 3.8+)
15
- try:
16
- from importlib.metadata import version
17
- PACKAGE_VERSION = version("tapps-agents")
18
- except Exception:
19
- # Last resort: try reading from __init__.py directly
20
- try:
21
- import importlib.util
22
- import pathlib
23
- init_path = pathlib.Path(__file__).parent.parent / "__init__.py"
24
- if init_path.exists():
25
- spec = importlib.util.spec_from_file_location("tapps_agents_init", init_path)
26
- if spec and spec.loader:
27
- module = importlib.util.module_from_spec(spec)
28
- spec.loader.exec_module(module)
29
- PACKAGE_VERSION = getattr(module, "__version__", "unknown")
30
- else:
31
- PACKAGE_VERSION = "unknown"
32
- else:
33
- PACKAGE_VERSION = "unknown"
34
- except Exception:
35
- PACKAGE_VERSION = "unknown"
36
- from .commands import (
37
- analyst,
38
- architect,
39
- debugger,
40
- designer,
41
- documenter,
42
- enhancer,
43
- evaluator,
44
- implementer,
45
- improver,
46
- learning,
47
- observability,
48
- ops,
49
- orchestrator,
50
- planner,
51
- reviewer,
52
- simple_mode,
53
- tester,
54
- top_level,
55
- )
56
- from .feedback import FeedbackManager, ProgressMode, VerbosityLevel
57
-
58
- # Import all parser registration functions
59
- from .parsers import (
60
- analyst as analyst_parsers,
61
- )
62
- from .parsers import (
63
- architect as architect_parsers,
64
- )
65
- from .parsers import (
66
- debugger as debugger_parsers,
67
- )
68
- from .parsers import (
69
- designer as designer_parsers,
70
- )
71
- from .parsers import (
72
- documenter as documenter_parsers,
73
- )
74
- from .parsers import (
75
- enhancer as enhancer_parsers,
76
- )
77
- from .parsers import (
78
- evaluator as evaluator_parsers,
79
- )
80
- from .parsers import (
81
- implementer as implementer_parsers,
82
- )
83
- from .parsers import (
84
- improver as improver_parsers,
85
- )
86
- from .parsers import (
87
- ops as ops_parsers,
88
- )
89
- from .parsers import (
90
- orchestrator as orchestrator_parsers,
91
- )
92
- from .parsers import (
93
- planner as planner_parsers,
94
- )
95
- from .parsers import (
96
- reviewer as reviewer_parsers,
97
- )
98
- from .parsers import (
99
- tester as tester_parsers,
100
- )
101
- from .parsers import (
102
- top_level as top_level_parsers,
103
- )
104
-
105
-
106
- def _reorder_global_flags(argv: list[str]) -> list[str]:
107
- """
108
- Allow global flags to appear anywhere (common modern CLI UX).
109
-
110
- Argparse only supports global flags before the subcommand. Users frequently type:
111
- tapps-agents doctor --no-progress
112
- so we hoist known global flags to the front before parsing.
113
- """
114
-
115
- hoisted: list[str] = []
116
- rest: list[str] = []
117
- i = 0
118
- while i < len(argv):
119
- a = argv[i]
120
-
121
- if a in {"--quiet", "-q", "--verbose", "-v", "--no-progress"}:
122
- hoisted.append(a)
123
- i += 1
124
- continue
125
-
126
- if a.startswith("--progress="):
127
- hoisted.append(a)
128
- i += 1
129
- continue
130
-
131
- if a == "--progress":
132
- # Keep the value if present; argparse will validate choices later.
133
- if i + 1 < len(argv):
134
- hoisted.extend([a, argv[i + 1]])
135
- i += 2
136
- else:
137
- hoisted.append(a)
138
- i += 1
139
- continue
140
-
141
- rest.append(a)
142
- i += 1
143
-
144
- return hoisted + rest
145
-
146
-
147
- def create_root_parser() -> argparse.ArgumentParser:
148
- """
149
- Create the root ArgumentParser for the TappsCodingAgents CLI.
150
-
151
- Sets up the main parser with version information and helpful epilog
152
- showing common usage examples and shortcuts.
153
-
154
- Returns:
155
- Configured ArgumentParser instance ready for subparser registration.
156
-
157
- Example:
158
- >>> parser = create_root_parser()
159
- >>> parser.parse_args(['--version'])
160
- """
161
- parser = argparse.ArgumentParser(
162
- description="""TappsCodingAgents CLI - AI Coding Agents Framework
163
-
164
- A comprehensive framework for defining, configuring, and orchestrating AI coding agents
165
- with 14 workflow agents, industry experts, and full Cursor AI integration.
166
-
167
- Key Features:
168
- 14 Workflow Agents: Analyst, Architect, Debugger, Designer, Documenter, Enhancer,
169
- Evaluator, Implementer, Improver, Ops, Orchestrator, Planner, Reviewer, Tester
170
- • Industry Experts: Domain-specific knowledge with weighted decision-making
171
- Workflow Presets: Rapid development, full SDLC, bug fixes, quality improvement
172
- Code Quality Tools: Scoring, linting, type checking, duplication detection
173
- Cursor Integration: Skills and natural language commands
174
-
175
- For detailed documentation, visit: https://github.com/your-repo/docs""",
176
- epilog="""QUICK START COMMANDS:
177
- create <description> Create a new project from description (PRIMARY USE CASE)
178
- score <file> Score a code file (shortcut for 'reviewer score')
179
- init Initialize project (Cursor Rules + workflow presets)
180
- workflow <preset> Run preset workflows (rapid, full, fix, quality, hotfix)
181
- doctor Validate local environment and tools
182
- setup-experts Interactive expert setup wizard
183
-
184
- ALSO: simple-mode status | cleanup workflow-docs | health check | health overview | health usage dashboard | beads ready | cursor verify
185
-
186
- WORKFLOW PRESETS:
187
- rapid / feature Rapid development for sprint work and features
188
- full / enterprise Full SDLC pipeline for complete lifecycle management
189
- fix / refactor Maintenance and bug fixing workflows
190
- quality / improve Quality improvement and code review cycles
191
- hotfix / urgent Quick fixes for urgent production bugs
192
-
193
- AGENT COMMANDS:
194
- Use 'tapps-agents <agent> help' to see available commands for each agent:
195
- analyst - Requirements gathering, stakeholder analysis, tech research
196
- • architect - System design, architecture diagrams, tech selection
197
- debugger - Error debugging, stack trace analysis, code tracing
198
- designer - API design, data models, UI/UX design, wireframes
199
- documenter - Generate documentation, update README, docstrings
200
- enhancer - Transform simple prompts into comprehensive specifications
201
- evaluator - Framework effectiveness evaluation and usage analysis
202
- implementer - Code generation, refactoring, file writing
203
- improver - Code refactoring, performance optimization, quality improvement
204
- ops - Security scanning, compliance checks, deployment
205
- orchestrator - Workflow management, step coordination, gate decisions
206
- planner - Create plans, user stories, task breakdowns
207
- reviewer - Code review, scoring, linting, type checking, reports
208
- tester - Generate and run tests, test coverage
209
-
210
- EXAMPLES:
211
- # Create a new project
212
- tapps-agents create "Build a task management web app with React and FastAPI"
213
-
214
- # Score code quality
215
- tapps-agents score src/main.py
216
-
217
- # Initialize project setup
218
- tapps-agents init
219
-
220
- # Run rapid development workflow
221
- tapps-agents workflow rapid --prompt "Add user authentication"
222
-
223
- # Review code with detailed analysis
224
- tapps-agents reviewer review src/app.py --format json
225
-
226
- # Generate tests
227
- tapps-agents tester test src/utils.py
228
-
229
- # Get workflow recommendation
230
- tapps-agents workflow recommend
231
-
232
- # Check environment
233
- tapps-agents doctor
234
-
235
- For more information on a specific command, use:
236
- tapps-agents <command> --help
237
- tapps-agents <agent> help""",
238
- formatter_class=argparse.RawDescriptionHelpFormatter,
239
- )
240
- parser.add_argument(
241
- "--version",
242
- action="version",
243
- version=f"%(prog)s {PACKAGE_VERSION}",
244
- help="Show version and exit",
245
- )
246
- parser.add_argument(
247
- "--quiet",
248
- "-q",
249
- action="store_const",
250
- const="quiet",
251
- dest="verbosity",
252
- help="Quiet mode: only show errors and final results",
253
- )
254
- parser.add_argument(
255
- "--verbose",
256
- "-v",
257
- action="store_const",
258
- const="verbose",
259
- dest="verbosity",
260
- help="Verbose mode: show detailed debugging information",
261
- )
262
- parser.add_argument(
263
- "--progress",
264
- choices=[m.value for m in ProgressMode],
265
- default=None,
266
- help=(
267
- "Progress UI mode: auto (default), rich (animated), plain (single-line), off (disabled). "
268
- "You can also set TAPPS_PROGRESS=auto|rich|plain|off or TAPPS_NO_PROGRESS=1."
269
- ),
270
- )
271
- parser.add_argument(
272
- "--no-progress",
273
- action="store_true",
274
- help="Disable progress UI (same as --progress off)",
275
- )
276
- return parser
277
-
278
-
279
- def register_all_parsers(parser: argparse.ArgumentParser) -> None:
280
- """
281
- Register all agent and top-level command subparsers.
282
-
283
- This function sets up the complete command structure by registering:
284
- - Agent parsers (reviewer, planner, implementer, tester, etc.)
285
- - Top-level command parsers (create, init, workflow, score, etc.)
286
-
287
- Args:
288
- parser: Root ArgumentParser to attach subparsers to.
289
-
290
- Note:
291
- This must be called after create_root_parser() and before parsing arguments.
292
- """
293
- subparsers = parser.add_subparsers(dest="agent", help="Agent or command to use")
294
-
295
- # Register agent parsers
296
- reviewer_parsers.add_reviewer_parser(subparsers)
297
- planner_parsers.add_planner_parser(subparsers)
298
- implementer_parsers.add_implementer_parser(subparsers)
299
- tester_parsers.add_tester_parser(subparsers)
300
- debugger_parsers.add_debugger_parser(subparsers)
301
- documenter_parsers.add_documenter_parser(subparsers)
302
- orchestrator_parsers.add_orchestrator_parser(subparsers)
303
- analyst_parsers.add_analyst_parser(subparsers)
304
- architect_parsers.add_architect_parser(subparsers)
305
- designer_parsers.add_designer_parser(subparsers)
306
- improver_parsers.add_improver_parser(subparsers)
307
- ops_parsers.add_ops_parser(subparsers)
308
- enhancer_parsers.add_enhancer_parser(subparsers)
309
- evaluator_parsers.add_evaluator_parser(subparsers)
310
-
311
- # Register top-level parsers
312
- top_level_parsers.add_top_level_parsers(subparsers)
313
-
314
-
315
- def _get_agent_command_handlers() -> dict[str, Callable[[argparse.Namespace], None]]:
316
- """
317
- Get dictionary mapping agent names to their command handlers.
318
-
319
- Returns:
320
- Dictionary mapping agent names to handler functions
321
- """
322
- return {
323
- "reviewer": reviewer.handle_reviewer_command,
324
- "planner": planner.handle_planner_command,
325
- "implementer": implementer.handle_implementer_command,
326
- "tester": tester.handle_tester_command,
327
- "debugger": debugger.handle_debugger_command,
328
- "documenter": documenter.handle_documenter_command,
329
- "orchestrator": orchestrator.handle_orchestrator_command,
330
- "analyst": analyst.handle_analyst_command,
331
- "architect": architect.handle_architect_command,
332
- "designer": designer.handle_designer_command,
333
- "improver": improver.handle_improver_command,
334
- "ops": ops.handle_ops_command,
335
- "enhancer": enhancer.handle_enhancer_command,
336
- "evaluator": evaluator.handle_evaluator_command,
337
- }
338
-
339
-
340
- def _get_top_level_command_handlers() -> dict[str, Callable[[argparse.Namespace], None]]:
341
- """
342
- Get dictionary mapping top-level command names to their handlers.
343
-
344
- Returns:
345
- Dictionary mapping command names to handler functions
346
- """
347
- return {
348
- "create": top_level.handle_create_command,
349
- "init": top_level.handle_init_command,
350
- "generate-rules": top_level.handle_generate_rules_command,
351
- "workflow": top_level.handle_workflow_command,
352
- "score": top_level.handle_score_command,
353
- "status": top_level.handle_status_command,
354
- "doctor": top_level.handle_doctor_command,
355
- "docs": top_level.handle_docs_command,
356
- "install-dev": top_level.handle_install_dev_command,
357
- "customize": top_level.handle_customize_command,
358
- "commands": top_level.handle_commands_command,
359
- "skill": top_level.handle_skill_command,
360
- "skill-template": top_level.handle_skill_template_command,
361
- "setup-experts": top_level.handle_setup_experts_command,
362
- "cursor": top_level.handle_cursor_command,
363
- "beads": top_level.handle_beads_command,
364
- "continuous-bug-fix": top_level.handle_continuous_bug_fix_command,
365
- "bug-fix-continuous": top_level.handle_continuous_bug_fix_command,
366
- "brownfield": top_level.handle_brownfield_command,
367
- }
368
-
369
-
370
- def _handle_cleanup_command(args: argparse.Namespace) -> None:
371
- """Handle cleanup command with sub-commands."""
372
- cleanup_type = getattr(args, "cleanup_type", None)
373
- if cleanup_type == "workflow-docs":
374
- top_level.handle_cleanup_workflow_docs_command(args)
375
- elif cleanup_type == "sessions":
376
- top_level.handle_cleanup_sessions_command(args)
377
- elif cleanup_type == "all":
378
- top_level.handle_cleanup_all_command(args)
379
- else:
380
- print(f"Unknown cleanup type: {cleanup_type}", file=sys.stderr)
381
- print("Use 'tapps-agents cleanup --help' for available cleanup operations", file=sys.stderr)
382
- sys.exit(1)
383
-
384
-
385
- def _handle_observability_command(args: argparse.Namespace) -> None:
386
- """Handle observability command with sub-commands."""
387
- from pathlib import Path
388
-
389
- project_root = Path.cwd()
390
- command = getattr(args, "observability_command", None)
391
-
392
- observability_handlers = {
393
- "dashboard": lambda: observability.handle_observability_dashboard_command(
394
- workflow_id=getattr(args, "workflow_id", None),
395
- output_format=getattr(args, "format", "text"),
396
- output_file=getattr(args, "output", None),
397
- project_root=project_root,
398
- ),
399
- "graph": lambda: observability.handle_observability_graph_command(
400
- workflow_id=args.workflow_id,
401
- output_format=getattr(args, "format", "dot"),
402
- output_file=getattr(args, "output", None),
403
- project_root=project_root,
404
- ),
405
- "otel": lambda: observability.handle_observability_otel_command(
406
- workflow_id=args.workflow_id,
407
- output_file=getattr(args, "output", None),
408
- project_root=project_root,
409
- ),
410
- }
411
-
412
- handler = observability_handlers.get(command)
413
- if handler:
414
- handler()
415
- else:
416
- print(f"Unknown observability subcommand: {command}")
417
- sys.exit(1)
418
-
419
-
420
- def _handle_health_command(args: argparse.Namespace) -> None:
421
- """Handle health command with sub-commands."""
422
- from pathlib import Path
423
-
424
- from .commands import health
425
-
426
- if not hasattr(args, "command"):
427
- return
428
-
429
- health_handlers = {
430
- "check": lambda: health.handle_health_check_command(
431
- check_name=getattr(args, "check", None),
432
- output_format=getattr(args, "format", "text"),
433
- save=getattr(args, "save", True),
434
- ),
435
- "dashboard": lambda: health.handle_health_dashboard_command(
436
- output_format=getattr(args, "format", "text"),
437
- ),
438
- "show": lambda: health.handle_health_dashboard_command(
439
- output_format=getattr(args, "format", "text"),
440
- ),
441
- "metrics": lambda: health.handle_health_metrics_command(
442
- check_name=getattr(args, "check_name", None),
443
- status=getattr(args, "status", None),
444
- days=getattr(args, "days", 30),
445
- output_format=getattr(args, "format", "text"),
446
- ),
447
- "trends": lambda: health.handle_health_trends_command(
448
- check_name=getattr(args, "check_name", None) or "",
449
- days=getattr(args, "days", 7),
450
- output_format=getattr(args, "format", "text"),
451
- ),
452
- "usage": lambda: health.handle_health_usage_command(args),
453
- "overview": lambda: health.handle_health_overview_command(
454
- output_format=getattr(args, "format", "text"),
455
- project_root=Path.cwd(),
456
- ),
457
- "summary": lambda: health.handle_health_overview_command(
458
- output_format=getattr(args, "format", "text"),
459
- project_root=Path.cwd(),
460
- ),
461
- }
462
-
463
- handler = health_handlers.get(args.command)
464
- if handler:
465
- handler()
466
-
467
-
468
- def route_command(args: argparse.Namespace) -> None:
469
- """
470
- Route parsed command arguments to the appropriate handler function.
471
-
472
- This function acts as the central dispatcher, examining the 'agent' attribute
473
- from parsed arguments and calling the corresponding handler function.
474
-
475
- Supported routes:
476
- - Agent commands: reviewer, planner, implementer, tester, debugger, etc.
477
- - Top-level commands: create, init, workflow, score, doctor, etc.
478
- - Special commands: customize, skill, etc.
479
-
480
- Args:
481
- args: Parsed command-line arguments with 'agent' attribute indicating the command.
482
-
483
- Note:
484
- If no agent is specified, prints the main help message.
485
- """
486
- # Apply prompt enhancement middleware if enabled
487
- from ..cli.utils.prompt_enhancer import enhance_prompt_if_needed
488
- from ..core.config import load_config
489
-
490
- config = load_config()
491
- if config.auto_enhancement.enabled:
492
- args = enhance_prompt_if_needed(args, config.auto_enhancement)
493
-
494
- agent = args.agent
495
-
496
- # Handle None case (show help)
497
- if agent is None:
498
- help_parser = create_root_parser()
499
- register_all_parsers(help_parser)
500
- _safe_print_help(help_parser)
501
- return
502
-
503
- # Try agent command handlers first
504
- agent_handlers = _get_agent_command_handlers()
505
- if agent in agent_handlers:
506
- agent_handlers[agent](args)
507
- return
508
-
509
- # Try top-level command handlers
510
- top_level_handlers = _get_top_level_command_handlers()
511
- if agent in top_level_handlers:
512
- top_level_handlers[agent](args)
513
- return
514
-
515
- # Handle special cases with sub-commands or aliases
516
- special_handlers = {
517
- "cleanup": _handle_cleanup_command,
518
- "health": _handle_health_command,
519
- "observability": _handle_observability_command,
520
- "simple-mode": simple_mode.handle_simple_mode_command,
521
- "learning": learning.handle_learning_command,
522
- "knowledge": top_level.handle_knowledge_command,
523
- }
524
-
525
- if agent in special_handlers:
526
- special_handlers[agent](args)
527
- return
528
-
529
- # Unknown command - show help
530
- help_parser = create_root_parser()
531
- register_all_parsers(help_parser)
532
- _safe_print_help(help_parser)
533
-
534
-
535
- def _setup_windows_encoding() -> None:
536
- """
537
- Set up UTF-8 encoding for Windows console to prevent encoding errors.
538
-
539
- This must be called before any argparse operations or help text printing.
540
- """
541
- if sys.platform == "win32":
542
- # Set environment variable for subprocess encoding
543
- os.environ.setdefault("PYTHONIOENCODING", "utf-8")
544
-
545
- # Reconfigure stdout/stderr to UTF-8 if possible (Python 3.7+)
546
- try:
547
- if hasattr(sys.stdout, 'reconfigure'):
548
- sys.stdout.reconfigure(encoding='utf-8', errors='replace')
549
- if hasattr(sys.stderr, 'reconfigure'):
550
- sys.stderr.reconfigure(encoding='utf-8', errors='replace')
551
- except (AttributeError, ValueError, OSError):
552
- # Fallback: use environment variable only
553
- # Some terminals may not support reconfiguration
554
- pass
555
-
556
-
557
- def _safe_print_help(parser: argparse.ArgumentParser) -> None:
558
- """
559
- Safely print argparse help text with Windows encoding handling.
560
-
561
- Args:
562
- parser: ArgumentParser instance to print help for
563
- """
564
- try:
565
- parser.print_help()
566
- except (UnicodeEncodeError, OSError) as e:
567
- # If encoding fails, try to print ASCII-safe version
568
- try:
569
- # Get help text and encode safely
570
- help_text = parser.format_help()
571
- # Replace any problematic Unicode characters with ASCII equivalents
572
- safe_text = help_text.encode('ascii', 'replace').decode('ascii')
573
- print(safe_text, file=sys.stdout)
574
- except Exception:
575
- # Last resort: print basic error message
576
- print("Help text unavailable due to encoding issues.", file=sys.stderr)
577
- print(f"Error: {e}", file=sys.stderr)
578
- sys.exit(1)
579
-
580
-
581
- def main() -> None:
582
- """Main CLI entry point - supports both *command and command formats"""
583
- # Set up Windows encoding FIRST, before any argparse operations
584
- _setup_windows_encoding()
585
-
586
- # Run startup routines (documentation refresh) before main
587
- from ..core.startup import startup_routines
588
-
589
- async def startup():
590
- """Run startup routines in background."""
591
- try:
592
- await startup_routines(refresh_docs=True, background_refresh=True)
593
- except Exception:
594
- # Don't fail if startup routines fail
595
- return
596
-
597
- # Start startup routines in background
598
- asyncio.run(startup())
599
-
600
- # Create parser and register all subparsers
601
- parser = create_root_parser()
602
- register_all_parsers(parser)
603
-
604
- # Parse arguments
605
- argv = _reorder_global_flags(sys.argv[1:])
606
- try:
607
- args = parser.parse_args(argv)
608
- except SystemExit as e:
609
- # argparse raises SystemExit(0) for --help, SystemExit(2) for errors
610
- # We need to handle this gracefully with encoding safety
611
- if e.code == 0:
612
- # --help was used, argparse already printed help, but we need to ensure encoding
613
- # If we get here, argparse.print_help() was called internally
614
- # The encoding setup at the start should have handled it, but exit cleanly
615
- sys.exit(0)
616
- else:
617
- # Parse error - argparse already printed error message
618
- # Ensure encoding was set up, then exit with error code
619
- sys.exit(e.code)
620
-
621
- # Set verbosity level from arguments
622
- verbosity_str = getattr(args, "verbosity", None)
623
- if verbosity_str == "quiet":
624
- FeedbackManager.set_verbosity(VerbosityLevel.QUIET)
625
- elif verbosity_str == "verbose":
626
- FeedbackManager.set_verbosity(VerbosityLevel.VERBOSE)
627
- else:
628
- FeedbackManager.set_verbosity(VerbosityLevel.NORMAL)
629
-
630
- # Set progress mode (CLI flags override env; env is handled by FeedbackManager.AUTO)
631
- if getattr(args, "no_progress", False):
632
- FeedbackManager.set_progress_mode(ProgressMode.OFF)
633
- else:
634
- progress_str = getattr(args, "progress", None)
635
- if progress_str:
636
- FeedbackManager.set_progress_mode(ProgressMode(progress_str))
637
-
638
- # Set format type if specified (for commands that support it)
639
- format_type = getattr(args, "format", None)
640
- if format_type:
641
- FeedbackManager.set_format(format_type)
642
-
643
- # Route to appropriate handler
644
- route_command(args)
645
-
1
+ """
2
+ Main CLI entry point
3
+ """
4
+ import argparse
5
+ import asyncio
6
+ import os
7
+ import sys
8
+ from collections.abc import Callable
9
+
10
+ # Try to import version, with fallback to importlib.metadata
11
+ try:
12
+ from .. import __version__ as PACKAGE_VERSION
13
+ except (ImportError, AttributeError):
14
+ # Fallback: use importlib.metadata (standard library, Python 3.8+)
15
+ try:
16
+ from importlib.metadata import version
17
+ PACKAGE_VERSION = version("tapps-agents")
18
+ except Exception:
19
+ # Last resort: try reading from __init__.py directly
20
+ try:
21
+ import importlib.util
22
+ import pathlib
23
+ init_path = pathlib.Path(__file__).parent.parent / "__init__.py"
24
+ if init_path.exists():
25
+ spec = importlib.util.spec_from_file_location("tapps_agents_init", init_path)
26
+ if spec and spec.loader:
27
+ module = importlib.util.module_from_spec(spec)
28
+ spec.loader.exec_module(module)
29
+ PACKAGE_VERSION = getattr(module, "__version__", "unknown")
30
+ else:
31
+ PACKAGE_VERSION = "unknown"
32
+ else:
33
+ PACKAGE_VERSION = "unknown"
34
+ except Exception:
35
+ PACKAGE_VERSION = "unknown"
36
+ from .commands import (
37
+ analyst,
38
+ architect,
39
+ cleanup_agent,
40
+ debugger,
41
+ designer,
42
+ documenter,
43
+ enhancer,
44
+ evaluator,
45
+ implementer,
46
+ improver,
47
+ learning,
48
+ observability,
49
+ ops,
50
+ orchestrator,
51
+ planner,
52
+ reviewer,
53
+ simple_mode,
54
+ tester,
55
+ top_level,
56
+ )
57
+ from .feedback import FeedbackManager, ProgressMode, VerbosityLevel
58
+
59
+ # Import all parser registration functions
60
+ from .parsers import (
61
+ analyst as analyst_parsers,
62
+ )
63
+ from .parsers import (
64
+ cleanup_agent as cleanup_agent_parsers,
65
+ )
66
+ from .parsers import (
67
+ architect as architect_parsers,
68
+ )
69
+ from .parsers import (
70
+ debugger as debugger_parsers,
71
+ )
72
+ from .parsers import (
73
+ designer as designer_parsers,
74
+ )
75
+ from .parsers import (
76
+ documenter as documenter_parsers,
77
+ )
78
+ from .parsers import (
79
+ enhancer as enhancer_parsers,
80
+ )
81
+ from .parsers import (
82
+ evaluator as evaluator_parsers,
83
+ )
84
+ from .parsers import (
85
+ implementer as implementer_parsers,
86
+ )
87
+ from .parsers import (
88
+ improver as improver_parsers,
89
+ )
90
+ from .parsers import (
91
+ ops as ops_parsers,
92
+ )
93
+ from .parsers import (
94
+ orchestrator as orchestrator_parsers,
95
+ )
96
+ from .parsers import (
97
+ planner as planner_parsers,
98
+ )
99
+ from .parsers import (
100
+ reviewer as reviewer_parsers,
101
+ )
102
+ from .parsers import (
103
+ tester as tester_parsers,
104
+ )
105
+ from .parsers import (
106
+ top_level as top_level_parsers,
107
+ )
108
+
109
+
110
+ def _reorder_global_flags(argv: list[str]) -> list[str]:
111
+ """
112
+ Allow global flags to appear anywhere (common modern CLI UX).
113
+
114
+ Argparse only supports global flags before the subcommand. Users frequently type:
115
+ tapps-agents doctor --no-progress
116
+ so we hoist known global flags to the front before parsing.
117
+ """
118
+
119
+ hoisted: list[str] = []
120
+ rest: list[str] = []
121
+ i = 0
122
+ while i < len(argv):
123
+ a = argv[i]
124
+
125
+ if a in {"--quiet", "-q", "--verbose", "-v", "--no-progress"}:
126
+ hoisted.append(a)
127
+ i += 1
128
+ continue
129
+
130
+ if a.startswith("--progress="):
131
+ hoisted.append(a)
132
+ i += 1
133
+ continue
134
+
135
+ if a == "--progress":
136
+ # Keep the value if present; argparse will validate choices later.
137
+ if i + 1 < len(argv):
138
+ hoisted.extend([a, argv[i + 1]])
139
+ i += 2
140
+ else:
141
+ hoisted.append(a)
142
+ i += 1
143
+ continue
144
+
145
+ rest.append(a)
146
+ i += 1
147
+
148
+ return hoisted + rest
149
+
150
+
151
+ def create_root_parser() -> argparse.ArgumentParser:
152
+ """
153
+ Create the root ArgumentParser for the TappsCodingAgents CLI.
154
+
155
+ Sets up the main parser with version information and helpful epilog
156
+ showing common usage examples and shortcuts.
157
+
158
+ Returns:
159
+ Configured ArgumentParser instance ready for subparser registration.
160
+
161
+ Example:
162
+ >>> parser = create_root_parser()
163
+ >>> parser.parse_args(['--version'])
164
+ """
165
+ parser = argparse.ArgumentParser(
166
+ description="""TappsCodingAgents CLI - AI Coding Agents Framework
167
+
168
+ A comprehensive framework for defining, configuring, and orchestrating AI coding agents
169
+ with 14 workflow agents, industry experts, and full Cursor AI integration.
170
+
171
+ Key Features:
172
+ 14 Workflow Agents: Analyst, Architect, Debugger, Designer, Documenter, Enhancer,
173
+ Evaluator, Implementer, Improver, Ops, Orchestrator, Planner, Reviewer, Tester
174
+ • Industry Experts: Domain-specific knowledge with weighted decision-making
175
+ Workflow Presets: Rapid development, full SDLC, bug fixes, quality improvement
176
+ Code Quality Tools: Scoring, linting, type checking, duplication detection
177
+ Cursor Integration: Skills and natural language commands
178
+
179
+ For detailed documentation, visit: https://github.com/your-repo/docs""",
180
+ epilog="""QUICK START COMMANDS:
181
+ create <description> Create a new project from description (PRIMARY USE CASE)
182
+ score <file> Score a code file (shortcut for 'reviewer score')
183
+ init Initialize project (Cursor Rules + workflow presets)
184
+ workflow <preset> Run preset workflows (rapid, full, fix, quality, hotfix)
185
+ doctor Validate local environment and tools
186
+ setup-experts Interactive expert setup wizard
187
+
188
+ ALSO: simple-mode status | cleanup workflow-docs | health check | health overview | health usage dashboard | beads ready | cursor verify
189
+
190
+ WORKFLOW PRESETS:
191
+ rapid / feature Rapid development for sprint work and features
192
+ full / enterprise Full SDLC pipeline for complete lifecycle management
193
+ fix / refactor Maintenance and bug fixing workflows
194
+ quality / improve Quality improvement and code review cycles
195
+ hotfix / urgent Quick fixes for urgent production bugs
196
+
197
+ AGENT COMMANDS:
198
+ Use 'tapps-agents <agent> help' to see available commands for each agent:
199
+ analyst - Requirements gathering, stakeholder analysis, tech research
200
+ architect - System design, architecture diagrams, tech selection
201
+ debugger - Error debugging, stack trace analysis, code tracing
202
+ designer - API design, data models, UI/UX design, wireframes
203
+ documenter - Generate documentation, update README, docstrings
204
+ enhancer - Transform simple prompts into comprehensive specifications
205
+ evaluator - Framework effectiveness evaluation and usage analysis
206
+ implementer - Code generation, refactoring, file writing
207
+ improver - Code refactoring, performance optimization, quality improvement
208
+ ops - Security scanning, compliance checks, deployment
209
+ • orchestrator - Workflow management, step coordination, gate decisions
210
+ • planner - Create plans, user stories, task breakdowns
211
+ reviewer - Code review, scoring, linting, type checking, reports
212
+ • tester - Generate and run tests, test coverage
213
+
214
+ EXAMPLES:
215
+ # Create a new project
216
+ tapps-agents create "Build a task management web app with React and FastAPI"
217
+
218
+ # Score code quality
219
+ tapps-agents score src/main.py
220
+
221
+ # Initialize project setup
222
+ tapps-agents init
223
+
224
+ # Run rapid development workflow
225
+ tapps-agents workflow rapid --prompt "Add user authentication"
226
+
227
+ # Review code with detailed analysis
228
+ tapps-agents reviewer review src/app.py --format json
229
+
230
+ # Generate tests
231
+ tapps-agents tester test src/utils.py
232
+
233
+ # Get workflow recommendation
234
+ tapps-agents workflow recommend
235
+
236
+ # Check environment
237
+ tapps-agents doctor
238
+
239
+ For more information on a specific command, use:
240
+ tapps-agents <command> --help
241
+ tapps-agents <agent> help""",
242
+ formatter_class=argparse.RawDescriptionHelpFormatter,
243
+ )
244
+ parser.add_argument(
245
+ "--version",
246
+ action="version",
247
+ version=f"%(prog)s {PACKAGE_VERSION}",
248
+ help="Show version and exit",
249
+ )
250
+ parser.add_argument(
251
+ "--quiet",
252
+ "-q",
253
+ action="store_const",
254
+ const="quiet",
255
+ dest="verbosity",
256
+ help="Quiet mode: only show errors and final results",
257
+ )
258
+ parser.add_argument(
259
+ "--verbose",
260
+ "-v",
261
+ action="store_const",
262
+ const="verbose",
263
+ dest="verbosity",
264
+ help="Verbose mode: show detailed debugging information",
265
+ )
266
+ parser.add_argument(
267
+ "--progress",
268
+ choices=[m.value for m in ProgressMode],
269
+ default=None,
270
+ help=(
271
+ "Progress UI mode: auto (default), rich (animated), plain (single-line), off (disabled). "
272
+ "You can also set TAPPS_PROGRESS=auto|rich|plain|off or TAPPS_NO_PROGRESS=1."
273
+ ),
274
+ )
275
+ parser.add_argument(
276
+ "--no-progress",
277
+ action="store_true",
278
+ help="Disable progress UI (same as --progress off)",
279
+ )
280
+ return parser
281
+
282
+
283
+ def register_all_parsers(parser: argparse.ArgumentParser) -> None:
284
+ """
285
+ Register all agent and top-level command subparsers.
286
+
287
+ This function sets up the complete command structure by registering:
288
+ - Agent parsers (reviewer, planner, implementer, tester, etc.)
289
+ - Top-level command parsers (create, init, workflow, score, etc.)
290
+
291
+ Args:
292
+ parser: Root ArgumentParser to attach subparsers to.
293
+
294
+ Note:
295
+ This must be called after create_root_parser() and before parsing arguments.
296
+ """
297
+ subparsers = parser.add_subparsers(dest="agent", help="Agent or command to use")
298
+
299
+ # Register agent parsers
300
+ reviewer_parsers.add_reviewer_parser(subparsers)
301
+ planner_parsers.add_planner_parser(subparsers)
302
+ implementer_parsers.add_implementer_parser(subparsers)
303
+ tester_parsers.add_tester_parser(subparsers)
304
+ debugger_parsers.add_debugger_parser(subparsers)
305
+ documenter_parsers.add_documenter_parser(subparsers)
306
+ orchestrator_parsers.add_orchestrator_parser(subparsers)
307
+ analyst_parsers.add_analyst_parser(subparsers)
308
+ architect_parsers.add_architect_parser(subparsers)
309
+ designer_parsers.add_designer_parser(subparsers)
310
+ improver_parsers.add_improver_parser(subparsers)
311
+ ops_parsers.add_ops_parser(subparsers)
312
+ enhancer_parsers.add_enhancer_parser(subparsers)
313
+ evaluator_parsers.add_evaluator_parser(subparsers)
314
+ cleanup_agent_parsers.add_cleanup_agent_parser(subparsers)
315
+
316
+ # Register top-level parsers
317
+ top_level_parsers.add_top_level_parsers(subparsers)
318
+
319
+
320
+ def _get_agent_command_handlers() -> dict[str, Callable[[argparse.Namespace], None]]:
321
+ """
322
+ Get dictionary mapping agent names to their command handlers.
323
+
324
+ Returns:
325
+ Dictionary mapping agent names to handler functions
326
+ """
327
+ return {
328
+ "reviewer": reviewer.handle_reviewer_command,
329
+ "planner": planner.handle_planner_command,
330
+ "implementer": implementer.handle_implementer_command,
331
+ "tester": tester.handle_tester_command,
332
+ "debugger": debugger.handle_debugger_command,
333
+ "documenter": documenter.handle_documenter_command,
334
+ "orchestrator": orchestrator.handle_orchestrator_command,
335
+ "analyst": analyst.handle_analyst_command,
336
+ "architect": architect.handle_architect_command,
337
+ "designer": designer.handle_designer_command,
338
+ "improver": improver.handle_improver_command,
339
+ "ops": ops.handle_ops_command,
340
+ "enhancer": enhancer.handle_enhancer_command,
341
+ "evaluator": evaluator.handle_evaluator_command,
342
+ "cleanup-agent": cleanup_agent.handle_cleanup_agent_command,
343
+ }
344
+
345
+
346
+ def _get_top_level_command_handlers() -> dict[str, Callable[[argparse.Namespace], None]]:
347
+ """
348
+ Get dictionary mapping top-level command names to their handlers.
349
+
350
+ Returns:
351
+ Dictionary mapping command names to handler functions
352
+ """
353
+ return {
354
+ "create": top_level.handle_create_command,
355
+ "init": top_level.handle_init_command,
356
+ "generate-rules": top_level.handle_generate_rules_command,
357
+ "workflow": top_level.handle_workflow_command,
358
+ "score": top_level.handle_score_command,
359
+ "status": top_level.handle_status_command,
360
+ "doctor": top_level.handle_doctor_command,
361
+ "docs": top_level.handle_docs_command,
362
+ "install-dev": top_level.handle_install_dev_command,
363
+ "customize": top_level.handle_customize_command,
364
+ "commands": top_level.handle_commands_command,
365
+ "skill": top_level.handle_skill_command,
366
+ "skill-template": top_level.handle_skill_template_command,
367
+ "setup-experts": top_level.handle_setup_experts_command,
368
+ "cursor": top_level.handle_cursor_command,
369
+ "beads": top_level.handle_beads_command,
370
+ "continuous-bug-fix": top_level.handle_continuous_bug_fix_command,
371
+ "bug-fix-continuous": top_level.handle_continuous_bug_fix_command,
372
+ "brownfield": top_level.handle_brownfield_command,
373
+ }
374
+
375
+
376
+ def _handle_cleanup_command(args: argparse.Namespace) -> None:
377
+ """Handle cleanup command with sub-commands."""
378
+ cleanup_type = getattr(args, "cleanup_type", None)
379
+ if cleanup_type == "workflow-docs":
380
+ top_level.handle_cleanup_workflow_docs_command(args)
381
+ elif cleanup_type == "sessions":
382
+ top_level.handle_cleanup_sessions_command(args)
383
+ elif cleanup_type == "all":
384
+ top_level.handle_cleanup_all_command(args)
385
+ else:
386
+ print(f"Unknown cleanup type: {cleanup_type}", file=sys.stderr)
387
+ print("Use 'tapps-agents cleanup --help' for available cleanup operations", file=sys.stderr)
388
+ sys.exit(1)
389
+
390
+
391
+ def _handle_observability_command(args: argparse.Namespace) -> None:
392
+ """Handle observability command with sub-commands."""
393
+ from pathlib import Path
394
+
395
+ project_root = Path.cwd()
396
+ command = getattr(args, "observability_command", None)
397
+
398
+ observability_handlers = {
399
+ "dashboard": lambda: observability.handle_observability_dashboard_command(
400
+ workflow_id=getattr(args, "workflow_id", None),
401
+ output_format=getattr(args, "format", "text"),
402
+ output_file=getattr(args, "output", None),
403
+ project_root=project_root,
404
+ ),
405
+ "graph": lambda: observability.handle_observability_graph_command(
406
+ workflow_id=args.workflow_id,
407
+ output_format=getattr(args, "format", "dot"),
408
+ output_file=getattr(args, "output", None),
409
+ project_root=project_root,
410
+ ),
411
+ "otel": lambda: observability.handle_observability_otel_command(
412
+ workflow_id=args.workflow_id,
413
+ output_file=getattr(args, "output", None),
414
+ project_root=project_root,
415
+ ),
416
+ }
417
+
418
+ handler = observability_handlers.get(command)
419
+ if handler:
420
+ handler()
421
+ else:
422
+ print(f"Unknown observability subcommand: {command}")
423
+ sys.exit(1)
424
+
425
+
426
+ def _handle_health_command(args: argparse.Namespace) -> None:
427
+ """Handle health command with sub-commands."""
428
+ from pathlib import Path
429
+
430
+ from .commands import health
431
+
432
+ if not hasattr(args, "command"):
433
+ return
434
+
435
+ health_handlers = {
436
+ "check": lambda: health.handle_health_check_command(
437
+ check_name=getattr(args, "check", None),
438
+ output_format=getattr(args, "format", "text"),
439
+ save=getattr(args, "save", True),
440
+ ),
441
+ "dashboard": lambda: health.handle_health_dashboard_command(
442
+ output_format=getattr(args, "format", "text"),
443
+ ),
444
+ "show": lambda: health.handle_health_dashboard_command(
445
+ output_format=getattr(args, "format", "text"),
446
+ ),
447
+ "metrics": lambda: health.handle_health_metrics_command(
448
+ check_name=getattr(args, "check_name", None),
449
+ status=getattr(args, "status", None),
450
+ days=getattr(args, "days", 30),
451
+ output_format=getattr(args, "format", "text"),
452
+ ),
453
+ "trends": lambda: health.handle_health_trends_command(
454
+ check_name=getattr(args, "check_name", None) or "",
455
+ days=getattr(args, "days", 7),
456
+ output_format=getattr(args, "format", "text"),
457
+ ),
458
+ "usage": lambda: health.handle_health_usage_command(args),
459
+ "overview": lambda: health.handle_health_overview_command(
460
+ output_format=getattr(args, "format", "text"),
461
+ project_root=Path.cwd(),
462
+ ),
463
+ "summary": lambda: health.handle_health_overview_command(
464
+ output_format=getattr(args, "format", "text"),
465
+ project_root=Path.cwd(),
466
+ ),
467
+ }
468
+
469
+ handler = health_handlers.get(args.command)
470
+ if handler:
471
+ handler()
472
+
473
+
474
+ def route_command(args: argparse.Namespace) -> None:
475
+ """
476
+ Route parsed command arguments to the appropriate handler function.
477
+
478
+ This function acts as the central dispatcher, examining the 'agent' attribute
479
+ from parsed arguments and calling the corresponding handler function.
480
+
481
+ Supported routes:
482
+ - Agent commands: reviewer, planner, implementer, tester, debugger, etc.
483
+ - Top-level commands: create, init, workflow, score, doctor, etc.
484
+ - Special commands: customize, skill, etc.
485
+
486
+ Args:
487
+ args: Parsed command-line arguments with 'agent' attribute indicating the command.
488
+
489
+ Note:
490
+ If no agent is specified, prints the main help message.
491
+ """
492
+ # Apply prompt enhancement middleware if enabled
493
+ from ..cli.utils.prompt_enhancer import enhance_prompt_if_needed
494
+ from ..core.config import load_config
495
+
496
+ config = load_config()
497
+ if config.auto_enhancement.enabled:
498
+ args = enhance_prompt_if_needed(args, config.auto_enhancement)
499
+
500
+ agent = args.agent
501
+
502
+ # Handle None case (show help)
503
+ if agent is None:
504
+ help_parser = create_root_parser()
505
+ register_all_parsers(help_parser)
506
+ _safe_print_help(help_parser)
507
+ return
508
+
509
+ # Try agent command handlers first
510
+ agent_handlers = _get_agent_command_handlers()
511
+ if agent in agent_handlers:
512
+ agent_handlers[agent](args)
513
+ return
514
+
515
+ # Try top-level command handlers
516
+ top_level_handlers = _get_top_level_command_handlers()
517
+ if agent in top_level_handlers:
518
+ top_level_handlers[agent](args)
519
+ return
520
+
521
+ # Handle special cases with sub-commands or aliases
522
+ special_handlers = {
523
+ "cleanup": _handle_cleanup_command,
524
+ "health": _handle_health_command,
525
+ "observability": _handle_observability_command,
526
+ "simple-mode": simple_mode.handle_simple_mode_command,
527
+ "learning": learning.handle_learning_command,
528
+ "knowledge": top_level.handle_knowledge_command,
529
+ }
530
+
531
+ if agent in special_handlers:
532
+ special_handlers[agent](args)
533
+ return
534
+
535
+ # Unknown command - show help
536
+ help_parser = create_root_parser()
537
+ register_all_parsers(help_parser)
538
+ _safe_print_help(help_parser)
539
+
540
+
541
+ def _setup_windows_encoding() -> None:
542
+ """
543
+ Set up UTF-8 encoding for Windows console to prevent encoding errors.
544
+
545
+ This must be called before any argparse operations or help text printing.
546
+ """
547
+ if sys.platform == "win32":
548
+ # Set environment variable for subprocess encoding
549
+ os.environ.setdefault("PYTHONIOENCODING", "utf-8")
550
+
551
+ # Reconfigure stdout/stderr to UTF-8 if possible (Python 3.7+)
552
+ try:
553
+ if hasattr(sys.stdout, 'reconfigure'):
554
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
555
+ if hasattr(sys.stderr, 'reconfigure'):
556
+ sys.stderr.reconfigure(encoding='utf-8', errors='replace')
557
+ except (AttributeError, ValueError, OSError):
558
+ # Fallback: use environment variable only
559
+ # Some terminals may not support reconfiguration
560
+ pass
561
+
562
+
563
+ def _safe_print_help(parser: argparse.ArgumentParser) -> None:
564
+ """
565
+ Safely print argparse help text with Windows encoding handling.
566
+
567
+ Args:
568
+ parser: ArgumentParser instance to print help for
569
+ """
570
+ try:
571
+ parser.print_help()
572
+ except (UnicodeEncodeError, OSError) as e:
573
+ # If encoding fails, try to print ASCII-safe version
574
+ try:
575
+ # Get help text and encode safely
576
+ help_text = parser.format_help()
577
+ # Replace any problematic Unicode characters with ASCII equivalents
578
+ safe_text = help_text.encode('ascii', 'replace').decode('ascii')
579
+ print(safe_text, file=sys.stdout)
580
+ except Exception:
581
+ # Last resort: print basic error message
582
+ print("Help text unavailable due to encoding issues.", file=sys.stderr)
583
+ print(f"Error: {e}", file=sys.stderr)
584
+ sys.exit(1)
585
+
586
+
587
+ def main() -> None:
588
+ """Main CLI entry point - supports both *command and command formats"""
589
+ # Set up Windows encoding FIRST, before any argparse operations
590
+ _setup_windows_encoding()
591
+
592
+ # Run startup routines (documentation refresh) before main
593
+ from ..core.startup import startup_routines
594
+
595
+ async def startup():
596
+ """Run startup routines in background."""
597
+ try:
598
+ await startup_routines(refresh_docs=True, background_refresh=True)
599
+ except Exception:
600
+ # Don't fail if startup routines fail
601
+ return
602
+
603
+ # Start startup routines in background
604
+ asyncio.run(startup())
605
+
606
+ # Create parser and register all subparsers
607
+ parser = create_root_parser()
608
+ register_all_parsers(parser)
609
+
610
+ # Parse arguments
611
+ argv = _reorder_global_flags(sys.argv[1:])
612
+ try:
613
+ args = parser.parse_args(argv)
614
+ except SystemExit as e:
615
+ # argparse raises SystemExit(0) for --help, SystemExit(2) for errors
616
+ # We need to handle this gracefully with encoding safety
617
+ if e.code == 0:
618
+ # --help was used, argparse already printed help, but we need to ensure encoding
619
+ # If we get here, argparse.print_help() was called internally
620
+ # The encoding setup at the start should have handled it, but exit cleanly
621
+ sys.exit(0)
622
+ else:
623
+ # Parse error - argparse already printed error message
624
+ # Ensure encoding was set up, then exit with error code
625
+ sys.exit(e.code)
626
+
627
+ # Set verbosity level from arguments
628
+ verbosity_str = getattr(args, "verbosity", None)
629
+ if verbosity_str == "quiet":
630
+ FeedbackManager.set_verbosity(VerbosityLevel.QUIET)
631
+ elif verbosity_str == "verbose":
632
+ FeedbackManager.set_verbosity(VerbosityLevel.VERBOSE)
633
+ else:
634
+ FeedbackManager.set_verbosity(VerbosityLevel.NORMAL)
635
+
636
+ # Set progress mode (CLI flags override env; env is handled by FeedbackManager.AUTO)
637
+ if getattr(args, "no_progress", False):
638
+ FeedbackManager.set_progress_mode(ProgressMode.OFF)
639
+ else:
640
+ progress_str = getattr(args, "progress", None)
641
+ if progress_str:
642
+ FeedbackManager.set_progress_mode(ProgressMode(progress_str))
643
+
644
+ # Set format type if specified (for commands that support it)
645
+ format_type = getattr(args, "format", None)
646
+ if format_type:
647
+ FeedbackManager.set_format(format_type)
648
+
649
+ # Route to appropriate handler
650
+ route_command(args)
651
+