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