praisonAI 2.3.77__tar.gz → 2.3.78__tar.gz

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 (124) hide show
  1. {praisonai-2.3.77 → praisonai-2.3.78}/PKG-INFO +1 -1
  2. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/PKG-INFO +1 -1
  3. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/compare.py +49 -8
  4. praisonai-2.3.78/praisonai/cli/features/fast_context.py +374 -0
  5. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/handoff.py +3 -3
  6. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/main.py +12 -0
  7. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/deploy.py +1 -1
  8. praisonai-2.3.78/praisonai/version.py +1 -0
  9. praisonai-2.3.77/praisonai/cli/features/fast_context.py +0 -197
  10. praisonai-2.3.77/praisonai/version.py +0 -1
  11. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/SOURCES.txt +0 -0
  12. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/dependency_links.txt +0 -0
  13. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/entry_points.txt +0 -0
  14. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/requires.txt +0 -0
  15. {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/top_level.txt +0 -0
  16. {praisonai-2.3.77 → praisonai-2.3.78}/README.md +0 -0
  17. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/__init__.py +0 -0
  18. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/__main__.py +0 -0
  19. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/agent_scheduler.py +0 -0
  20. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/agents_generator.py +0 -0
  21. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/api/call.py +0 -0
  22. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/auto.py +0 -0
  23. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/chainlit_ui.py +0 -0
  24. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/__init__.py +0 -0
  25. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/__init__.py +0 -0
  26. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/agent_scheduler.py +0 -0
  27. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/at_mentions.py +0 -0
  28. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/auto_memory.py +0 -0
  29. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/autonomy_mode.py +0 -0
  30. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/base.py +0 -0
  31. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/cost_tracker.py +0 -0
  32. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/eval.py +0 -0
  33. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/external_agents.py +0 -0
  34. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/flow_display.py +0 -0
  35. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/git_integration.py +0 -0
  36. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/guardrail.py +0 -0
  37. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/image.py +0 -0
  38. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/interactive_tui.py +0 -0
  39. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/knowledge.py +0 -0
  40. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/mcp.py +0 -0
  41. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/message_queue.py +0 -0
  42. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/metrics.py +0 -0
  43. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/n8n.py +0 -0
  44. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/profiling.py +0 -0
  45. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/repo_map.py +0 -0
  46. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/router.py +0 -0
  47. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/sandbox_executor.py +0 -0
  48. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/session.py +0 -0
  49. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/skills.py +0 -0
  50. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/slash_commands.py +0 -0
  51. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/telemetry.py +0 -0
  52. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/todo.py +0 -0
  53. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/tools.py +0 -0
  54. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/workflow.py +0 -0
  55. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/schedule_cli.py +0 -0
  56. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/__init__.py +0 -0
  57. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/agent_tools.py +0 -0
  58. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/diff/__init__.py +0 -0
  59. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/diff/diff_strategy.py +0 -0
  60. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/__init__.py +0 -0
  61. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/apply_diff.py +0 -0
  62. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/execute_command.py +0 -0
  63. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/list_files.py +0 -0
  64. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/read_file.py +0 -0
  65. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/search_replace.py +0 -0
  66. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/write_file.py +0 -0
  67. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/__init__.py +0 -0
  68. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/file_utils.py +0 -0
  69. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/ignore_utils.py +0 -0
  70. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/text_utils.py +0 -0
  71. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inbuilt_tools/__init__.py +0 -0
  72. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  73. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/__init__.py +0 -0
  74. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/config.py +0 -0
  75. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/models.py +0 -0
  76. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/__init__.py +0 -0
  77. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/base.py +0 -0
  78. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/claude_code.py +0 -0
  79. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/codex_cli.py +0 -0
  80. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/cursor_cli.py +0 -0
  81. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/gemini_cli.py +0 -0
  82. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/profiler.py +0 -0
  83. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/__init__.py +0 -0
  84. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/agent_scheduler.py +0 -0
  85. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/base.py +0 -0
  86. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/daemon_manager.py +0 -0
  87. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/state_manager.py +0 -0
  88. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/yaml_loader.py +0 -0
  89. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler.py +0 -0
  90. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/__init__.py +0 -0
  91. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/build.py +0 -0
  92. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/post_install.py +0 -0
  93. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/setup_conda_env.py +0 -0
  94. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup.py +0 -0
  95. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/test.py +0 -0
  96. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/train.py +0 -0
  97. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/train_vision.py +0 -0
  98. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/agents.py +0 -0
  99. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/callbacks.py +0 -0
  100. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/chat.py +0 -0
  101. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/code.py +0 -0
  102. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/colab.py +0 -0
  103. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/colab_chainlit.py +0 -0
  104. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/components/aicoder.py +0 -0
  105. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/context.py +0 -0
  106. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/database_config.py +0 -0
  107. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/db.py +0 -0
  108. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtime.py +0 -0
  109. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtimeclient/__init__.py +0 -0
  110. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtimeclient/tools.py +0 -0
  111. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/sql_alchemy.py +0 -0
  112. {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/upload_vision.py +0 -0
  113. {praisonai-2.3.77 → praisonai-2.3.78}/pyproject.toml +0 -0
  114. {praisonai-2.3.77 → praisonai-2.3.78}/setup.cfg +0 -0
  115. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test.py +0 -0
  116. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_agents_playbook.py +0 -0
  117. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_approval_system.py +0 -0
  118. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_basic.py +0 -0
  119. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_cli_features.py +0 -0
  120. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_custom_tools_fix.py +0 -0
  121. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_double_api_fix.py +0 -0
  122. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_n8n_integration.py +0 -0
  123. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_phase2_refactor.py +0 -0
  124. {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.77
3
+ Version: 2.3.78
4
4
  Summary: PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration.
5
5
  Author: Mervin Praison
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.77
3
+ Version: 2.3.78
4
4
  Summary: PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration.
5
5
  Author: Mervin Praison
6
6
  License: MIT
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  COMPARE_MODES = {
18
18
  "basic": {},
19
- "tools": {"tools": "internet_search"},
19
+ "tools": {"tools": "calculator,internet_search"},
20
20
  "research": {"research": True},
21
21
  "planning": {"planning": True},
22
22
  "memory": {"memory": True},
@@ -407,13 +407,7 @@ class CompareHandler:
407
407
  from praisonaiagents.tools import internet_search
408
408
  tools.append(internet_search)
409
409
  elif name == "calculator":
410
- def calculator(expression: str) -> str:
411
- """Evaluate a mathematical expression."""
412
- try:
413
- return str(eval(expression))
414
- except Exception as e:
415
- return f"Error: {e}"
416
- tools.append(calculator)
410
+ tools.append(_calculator_tool)
417
411
  except ImportError:
418
412
  logger.warning(f"Could not load tool: {name}")
419
413
 
@@ -466,3 +460,50 @@ class CompareHandler:
466
460
  print(f"[red]Failed to save results to: {output_path}[/red]")
467
461
 
468
462
  return result
463
+
464
+
465
+ def _calculator_tool(expression: str) -> str:
466
+ """
467
+ Evaluate a mathematical expression safely.
468
+
469
+ Args:
470
+ expression: Mathematical expression to evaluate (e.g., "2+2", "15*3", "100/4")
471
+
472
+ Returns:
473
+ The result of the calculation as a string
474
+ """
475
+ import ast
476
+ import operator
477
+
478
+ # Safe operators
479
+ operators = {
480
+ ast.Add: operator.add,
481
+ ast.Sub: operator.sub,
482
+ ast.Mult: operator.mul,
483
+ ast.Div: operator.truediv,
484
+ ast.Pow: operator.pow,
485
+ ast.USub: operator.neg,
486
+ ast.UAdd: operator.pos,
487
+ ast.Mod: operator.mod,
488
+ ast.FloorDiv: operator.floordiv,
489
+ }
490
+
491
+ def _eval(node):
492
+ if isinstance(node, ast.Constant):
493
+ return node.value
494
+ elif isinstance(node, ast.BinOp):
495
+ left = _eval(node.left)
496
+ right = _eval(node.right)
497
+ return operators[type(node.op)](left, right)
498
+ elif isinstance(node, ast.UnaryOp):
499
+ operand = _eval(node.operand)
500
+ return operators[type(node.op)](operand)
501
+ else:
502
+ raise ValueError(f"Unsupported operation: {type(node)}")
503
+
504
+ try:
505
+ tree = ast.parse(expression, mode='eval')
506
+ result = _eval(tree.body)
507
+ return str(result)
508
+ except Exception as e:
509
+ return f"Error evaluating '{expression}': {e}"
@@ -0,0 +1,374 @@
1
+ """
2
+ Fast Context Handler for CLI.
3
+
4
+ Provides codebase search capability using FastContext.
5
+ Usage: praisonai "Find authentication code" --fast-context ./src
6
+
7
+ Fast Context Strategy:
8
+ 1. Extract search keywords from natural language query using LLM
9
+ 2. Provide folder tree context to help understand codebase structure
10
+ 3. Use both file name search AND content search (ripgrep-style)
11
+ 4. Return relevant code snippets for context
12
+ """
13
+
14
+ import os
15
+ import logging
16
+ from typing import Any, Dict, Tuple, List
17
+ from .base import FlagHandler
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class FastContextHandler(FlagHandler):
23
+ """
24
+ Handler for --fast-context flag.
25
+
26
+ Searches codebase for relevant context before agent execution.
27
+
28
+ Example:
29
+ praisonai "Find authentication code" --fast-context ./src
30
+ praisonai "Explain the database schema" --fast-context /path/to/project
31
+ """
32
+
33
+ @property
34
+ def feature_name(self) -> str:
35
+ return "fast_context"
36
+
37
+ @property
38
+ def flag_name(self) -> str:
39
+ return "fast-context"
40
+
41
+ @property
42
+ def flag_help(self) -> str:
43
+ return "Path to search for relevant code context"
44
+
45
+ def check_dependencies(self) -> Tuple[bool, str]:
46
+ """Check if FastContext is available."""
47
+ try:
48
+ import importlib.util
49
+ if importlib.util.find_spec("praisonaiagents") is not None:
50
+ return True, ""
51
+ return False, "praisonaiagents not installed"
52
+ except ImportError:
53
+ return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
54
+
55
+ def validate_search_path(self, path: str) -> Tuple[bool, str]:
56
+ """
57
+ Validate that the search path exists.
58
+
59
+ Args:
60
+ path: Path to search directory
61
+
62
+ Returns:
63
+ Tuple of (is_valid, error_message)
64
+ """
65
+ if not path:
66
+ return False, "No search path provided"
67
+
68
+ if not os.path.isdir(path):
69
+ return False, f"Directory not found: {path}"
70
+
71
+ return True, ""
72
+
73
+ def _get_folder_tree(self, path: str, max_depth: int = 2) -> str:
74
+ """Get folder tree structure for context."""
75
+ tree_lines = []
76
+ path = os.path.abspath(path)
77
+
78
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.tox', 'dist', 'build', '.egg-info'}
79
+ ignore_exts = {'.pyc', '.pyo', '.so', '.o', '.a', '.dylib'}
80
+
81
+ def walk_tree(current_path, prefix="", depth=0):
82
+ if depth > max_depth:
83
+ return
84
+ try:
85
+ entries = sorted(os.listdir(current_path))
86
+ except PermissionError:
87
+ return
88
+
89
+ dirs = []
90
+ files = []
91
+ for entry in entries:
92
+ full_path = os.path.join(current_path, entry)
93
+ if os.path.isdir(full_path):
94
+ if entry not in ignore_dirs and not entry.startswith('.'):
95
+ dirs.append(entry)
96
+ else:
97
+ ext = os.path.splitext(entry)[1]
98
+ if ext not in ignore_exts and not entry.startswith('.'):
99
+ files.append(entry)
100
+
101
+ for i, d in enumerate(dirs):
102
+ is_last = (i == len(dirs) - 1) and not files
103
+ tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{d}/")
104
+ walk_tree(os.path.join(current_path, d),
105
+ prefix + (' ' if is_last else '│ '), depth + 1)
106
+
107
+ for i, f in enumerate(files[:10]): # Limit files shown
108
+ is_last = i == len(files[:10]) - 1
109
+ tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{f}")
110
+ if len(files) > 10:
111
+ tree_lines.append(f"{prefix} ... and {len(files) - 10} more files")
112
+
113
+ tree_lines.append(os.path.basename(path) + "/")
114
+ walk_tree(path)
115
+ return "\n".join(tree_lines[:50]) # Limit total lines
116
+
117
+ def _extract_keywords(self, query: str, folder_tree: str) -> List[str]:
118
+ """Extract search keywords from natural language query using LLM."""
119
+ try:
120
+ from praisonaiagents import Agent
121
+
122
+ extractor = Agent(
123
+ name="KeywordExtractor",
124
+ role="Search Query Analyzer",
125
+ goal="Extract file search keywords from natural language",
126
+ backstory="Expert at understanding code search queries",
127
+ llm="gpt-4o-mini",
128
+ verbose=False
129
+ )
130
+
131
+ prompt = f"""Given this folder structure:
132
+ {folder_tree}
133
+
134
+ And this search query: "{query}"
135
+
136
+ Extract 1-5 specific keywords/patterns to search for in file names and content.
137
+ Return ONLY the keywords, one per line, no explanations.
138
+ Focus on: file names, function names, class names, variable names that might match.
139
+ If the query mentions a folder name that exists, include files from that folder."""
140
+
141
+ response = extractor.chat(prompt, stream=False)
142
+ keywords = [k.strip() for k in str(response).strip().split('\n') if k.strip()]
143
+ logger.debug(f"Extracted keywords: {keywords}")
144
+ return keywords[:5] # Limit to 5 keywords
145
+ except Exception as e:
146
+ logger.debug(f"Keyword extraction failed: {e}")
147
+ # Fallback: extract words from query
148
+ return [w for w in query.split() if len(w) > 2][:3]
149
+
150
+ def _search_files_by_name(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
151
+ """Search for files matching keywords in their names."""
152
+ matches = []
153
+ path = os.path.abspath(path)
154
+
155
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
156
+
157
+ for root, dirs, files in os.walk(path):
158
+ dirs[:] = [d for d in dirs if d not in ignore_dirs]
159
+
160
+ for filename in files:
161
+ filepath = os.path.join(root, filename)
162
+ rel_path = os.path.relpath(filepath, path)
163
+
164
+ # Check if any keyword matches filename or path
165
+ for kw in keywords:
166
+ kw_lower = kw.lower()
167
+ if kw_lower in filename.lower() or kw_lower in rel_path.lower():
168
+ matches.append({
169
+ 'file': rel_path,
170
+ 'lines': [(1, 50)], # First 50 lines
171
+ 'relevance': 1.0,
172
+ 'match_type': 'filename'
173
+ })
174
+ break
175
+
176
+ return matches[:20] # Limit results
177
+
178
+ def _search_files_by_content(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
179
+ """Search for files containing keywords in content."""
180
+ matches = []
181
+ path = os.path.abspath(path)
182
+
183
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
184
+ code_exts = {'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rs', '.rb', '.sh', '.yaml', '.yml', '.json', '.md', '.txt'}
185
+
186
+ for root, dirs, files in os.walk(path):
187
+ dirs[:] = [d for d in dirs if d not in ignore_dirs]
188
+
189
+ for filename in files:
190
+ ext = os.path.splitext(filename)[1]
191
+ if ext not in code_exts:
192
+ continue
193
+
194
+ filepath = os.path.join(root, filename)
195
+ rel_path = os.path.relpath(filepath, path)
196
+
197
+ try:
198
+ with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
199
+ content = f.read(50000) # Read first 50KB
200
+
201
+ content_lower = content.lower()
202
+ for kw in keywords:
203
+ if kw.lower() in content_lower:
204
+ # Find line numbers with matches
205
+ lines = content.split('\n')
206
+ match_lines = []
207
+ for i, line in enumerate(lines[:200], 1): # Check first 200 lines
208
+ if kw.lower() in line.lower():
209
+ match_lines.append((max(1, i-2), min(len(lines), i+2)))
210
+
211
+ if match_lines:
212
+ matches.append({
213
+ 'file': rel_path,
214
+ 'lines': match_lines[:5], # Limit line ranges
215
+ 'relevance': 0.8,
216
+ 'match_type': 'content'
217
+ })
218
+ break
219
+ except Exception:
220
+ continue
221
+
222
+ return matches[:15] # Limit results
223
+
224
+ def search_context(self, query: str, path: str, **kwargs) -> List[Dict[str, Any]]:
225
+ """
226
+ Search for relevant context in the codebase.
227
+
228
+ Uses a smart multi-step approach:
229
+ 1. Get folder tree structure
230
+ 2. Use LLM to extract search keywords from query
231
+ 3. Search by filename AND content
232
+ 4. Return combined results
233
+
234
+ Args:
235
+ query: Natural language search query
236
+ path: Path to search
237
+ **kwargs: Additional search options
238
+ - use_llm_keywords: Use LLM to extract keywords (default: True)
239
+
240
+ Returns:
241
+ List of matching results
242
+ """
243
+ available, msg = self.check_dependencies()
244
+ if not available:
245
+ self.print_status(msg, "error")
246
+ return []
247
+
248
+ valid, msg = self.validate_search_path(path)
249
+ if not valid:
250
+ self.print_status(msg, "error")
251
+ return []
252
+
253
+ try:
254
+ use_llm_keywords = kwargs.get('use_llm_keywords', True)
255
+
256
+ # Step 1: Get folder tree for context
257
+ logger.debug(f"Getting folder tree for: {path}")
258
+ folder_tree = self._get_folder_tree(path)
259
+ logger.debug(f"Folder tree:\n{folder_tree}")
260
+
261
+ # Step 2: Extract keywords
262
+ if use_llm_keywords:
263
+ logger.debug(f"Extracting keywords from query: {query}")
264
+ keywords = self._extract_keywords(query, folder_tree)
265
+ else:
266
+ keywords = [w for w in query.split() if len(w) > 2][:3]
267
+
268
+ logger.debug(f"Search keywords: {keywords}")
269
+
270
+ if not keywords:
271
+ self.print_status("Could not extract search keywords", "warning")
272
+ return []
273
+
274
+ # Step 3: Search by filename
275
+ logger.debug("Searching by filename...")
276
+ filename_matches = self._search_files_by_name(path, keywords)
277
+ logger.debug(f"Found {len(filename_matches)} filename matches")
278
+
279
+ # Step 4: Search by content
280
+ logger.debug("Searching by content...")
281
+ content_matches = self._search_files_by_content(path, keywords)
282
+ logger.debug(f"Found {len(content_matches)} content matches")
283
+
284
+ # Combine and deduplicate
285
+ seen_files = set()
286
+ matches = []
287
+
288
+ for m in filename_matches + content_matches:
289
+ if m['file'] not in seen_files:
290
+ seen_files.add(m['file'])
291
+ matches.append(m)
292
+
293
+ self.print_status(f"🔍 Found {len(matches)} relevant files", "success")
294
+ return matches
295
+
296
+ except Exception as e:
297
+ logger.error(f"FastContext search error: {e}")
298
+ self.log(f"FastContext search error: {e}", "error")
299
+ return []
300
+
301
+ def format_context_for_prompt(self, matches: List[Dict[str, Any]], max_chars: int = 10000) -> str:
302
+ """
303
+ Format search results as context for the prompt.
304
+
305
+ Args:
306
+ matches: List of search results
307
+ max_chars: Maximum characters to include
308
+
309
+ Returns:
310
+ Formatted context string
311
+ """
312
+ if not matches:
313
+ return ""
314
+
315
+ context_parts = ["## Relevant Code Context\n"]
316
+ total_chars = 0
317
+
318
+ for match in matches:
319
+ file_path = match.get('file', '')
320
+ lines = match.get('lines', [])
321
+
322
+ if not file_path or not os.path.exists(file_path):
323
+ continue
324
+
325
+ try:
326
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
327
+ content = f.readlines()
328
+
329
+ for start, end in lines:
330
+ snippet = ''.join(content[max(0, start-1):end])
331
+
332
+ if total_chars + len(snippet) > max_chars:
333
+ break
334
+
335
+ context_parts.append(f"\n### {file_path} (lines {start}-{end})\n```\n{snippet}```\n")
336
+ total_chars += len(snippet)
337
+
338
+ except Exception:
339
+ continue
340
+
341
+ return '\n'.join(context_parts)
342
+
343
+ def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
344
+ """
345
+ Apply fast context configuration.
346
+
347
+ Args:
348
+ config: Agent configuration dictionary
349
+ flag_value: Search path
350
+
351
+ Returns:
352
+ Modified configuration
353
+ """
354
+ if flag_value:
355
+ config['fast_context'] = True
356
+ config['fast_context_path'] = flag_value
357
+ return config
358
+
359
+ def execute(self, query: str = None, path: str = None, **kwargs) -> str:
360
+ """
361
+ Execute fast context search and return formatted context.
362
+
363
+ Args:
364
+ query: Search query
365
+ path: Search path
366
+
367
+ Returns:
368
+ Formatted context string
369
+ """
370
+ if not query or not path:
371
+ return ""
372
+
373
+ matches = self.search_context(query, path, **kwargs)
374
+ return self.format_context_for_prompt(matches)
@@ -95,9 +95,9 @@ class HandoffHandler(FlagHandler):
95
95
  for i, agent in enumerate(agents[:-1]):
96
96
  next_agent = agents[i + 1]
97
97
  handoff = Handoff(
98
- target=next_agent,
99
- name=f"handoff_to_{agent_names[i + 1]}",
100
- description=f"Hand off task to {agent_names[i + 1]} for further processing"
98
+ agent=next_agent,
99
+ tool_name_override=f"handoff_to_{agent_names[i + 1]}",
100
+ tool_description_override=f"Hand off task to {agent_names[i + 1]} for further processing"
101
101
  )
102
102
 
103
103
  # Add handoff to agent
@@ -313,6 +313,18 @@ class PraisonAI:
313
313
  elif hasattr(args, 'direct_prompt') and args.direct_prompt:
314
314
  # Only handle direct prompt if agent_file wasn't explicitly set in constructor
315
315
  if original_agent_file == "agents.yaml": # Default value, so safe to use direct prompt
316
+ # Handle --compare flag for CLI mode comparison
317
+ if hasattr(args, 'compare') and args.compare:
318
+ from .features.compare import CompareHandler
319
+ handler = CompareHandler(verbose=getattr(args, 'verbose', False))
320
+ result = handler.execute(
321
+ args.direct_prompt,
322
+ args.compare,
323
+ model=getattr(args, 'llm', None),
324
+ output_path=getattr(args, 'compare_output', None)
325
+ )
326
+ return result
327
+
316
328
  # Combine direct prompt with any available inputs (stdin and/or file)
317
329
  prompt_parts = [args.direct_prompt]
318
330
  if stdin_input:
@@ -57,7 +57,7 @@ class CloudDeployer:
57
57
  file.write("FROM python:3.11-slim\n")
58
58
  file.write("WORKDIR /app\n")
59
59
  file.write("COPY . .\n")
60
- file.write("RUN pip install flask praisonai==2.3.77 gunicorn markdown\n")
60
+ file.write("RUN pip install flask praisonai==2.3.78 gunicorn markdown\n")
61
61
  file.write("EXPOSE 8080\n")
62
62
  file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
63
63
 
@@ -0,0 +1 @@
1
+ __version__ = "2.3.78"
@@ -1,197 +0,0 @@
1
- """
2
- Fast Context Handler for CLI.
3
-
4
- Provides codebase search capability using FastContext.
5
- Usage: praisonai "Find authentication code" --fast-context ./src
6
- """
7
-
8
- import os
9
- from typing import Any, Dict, Tuple, List
10
- from .base import FlagHandler
11
-
12
-
13
- class FastContextHandler(FlagHandler):
14
- """
15
- Handler for --fast-context flag.
16
-
17
- Searches codebase for relevant context before agent execution.
18
-
19
- Example:
20
- praisonai "Find authentication code" --fast-context ./src
21
- praisonai "Explain the database schema" --fast-context /path/to/project
22
- """
23
-
24
- @property
25
- def feature_name(self) -> str:
26
- return "fast_context"
27
-
28
- @property
29
- def flag_name(self) -> str:
30
- return "fast-context"
31
-
32
- @property
33
- def flag_help(self) -> str:
34
- return "Path to search for relevant code context"
35
-
36
- def check_dependencies(self) -> Tuple[bool, str]:
37
- """Check if FastContext is available."""
38
- try:
39
- import importlib.util
40
- if importlib.util.find_spec("praisonaiagents") is not None:
41
- return True, ""
42
- return False, "praisonaiagents not installed"
43
- except ImportError:
44
- return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
45
-
46
- def validate_search_path(self, path: str) -> Tuple[bool, str]:
47
- """
48
- Validate that the search path exists.
49
-
50
- Args:
51
- path: Path to search directory
52
-
53
- Returns:
54
- Tuple of (is_valid, error_message)
55
- """
56
- if not path:
57
- return False, "No search path provided"
58
-
59
- if not os.path.isdir(path):
60
- return False, f"Directory not found: {path}"
61
-
62
- return True, ""
63
-
64
- def search_context(self, query: str, path: str, **kwargs) -> List[Dict[str, Any]]:
65
- """
66
- Search for relevant context in the codebase.
67
-
68
- Args:
69
- query: Search query
70
- path: Path to search
71
- **kwargs: Additional search options
72
- - use_llm: Use LLM for intelligent search (slower, default: False)
73
- - max_turns: Max search turns for LLM mode
74
- - model: LLM model for intelligent search
75
-
76
- Returns:
77
- List of matching results
78
- """
79
- available, msg = self.check_dependencies()
80
- if not available:
81
- self.print_status(msg, "error")
82
- return []
83
-
84
- valid, msg = self.validate_search_path(path)
85
- if not valid:
86
- self.print_status(msg, "error")
87
- return []
88
-
89
- try:
90
- from praisonaiagents import FastContext
91
-
92
- # Use fast non-LLM search by default for better performance
93
- use_llm = kwargs.get('use_llm', False)
94
-
95
- fc = FastContext(
96
- workspace_path=path,
97
- model=kwargs.get('model', 'gpt-4o-mini'),
98
- max_turns=kwargs.get('max_turns', 2), # Reduced from 4 for speed
99
- max_parallel=kwargs.get('parallelism', 8),
100
- timeout=kwargs.get('timeout', 15.0), # Reduced timeout
101
- verbose=self.verbose
102
- )
103
-
104
- # Use non-LLM search by default (much faster)
105
- result = fc.search(query, use_llm=use_llm)
106
-
107
- # Convert to list of dicts
108
- matches = []
109
- if hasattr(result, 'files'):
110
- for file_match in result.files:
111
- matches.append({
112
- 'file': file_match.path,
113
- 'lines': [(lr.start, lr.end) for lr in file_match.line_ranges],
114
- 'relevance': getattr(file_match, 'relevance', 1.0)
115
- })
116
-
117
- self.print_status(f"🔍 Found {len(matches)} relevant files", "success")
118
- return matches
119
-
120
- except Exception as e:
121
- self.log(f"FastContext search error: {e}", "error")
122
- return []
123
-
124
- def format_context_for_prompt(self, matches: List[Dict[str, Any]], max_chars: int = 10000) -> str:
125
- """
126
- Format search results as context for the prompt.
127
-
128
- Args:
129
- matches: List of search results
130
- max_chars: Maximum characters to include
131
-
132
- Returns:
133
- Formatted context string
134
- """
135
- if not matches:
136
- return ""
137
-
138
- context_parts = ["## Relevant Code Context\n"]
139
- total_chars = 0
140
-
141
- for match in matches:
142
- file_path = match.get('file', '')
143
- lines = match.get('lines', [])
144
-
145
- if not file_path or not os.path.exists(file_path):
146
- continue
147
-
148
- try:
149
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
150
- content = f.readlines()
151
-
152
- for start, end in lines:
153
- snippet = ''.join(content[max(0, start-1):end])
154
-
155
- if total_chars + len(snippet) > max_chars:
156
- break
157
-
158
- context_parts.append(f"\n### {file_path} (lines {start}-{end})\n```\n{snippet}```\n")
159
- total_chars += len(snippet)
160
-
161
- except Exception:
162
- continue
163
-
164
- return '\n'.join(context_parts)
165
-
166
- def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
167
- """
168
- Apply fast context configuration.
169
-
170
- Args:
171
- config: Agent configuration dictionary
172
- flag_value: Search path
173
-
174
- Returns:
175
- Modified configuration
176
- """
177
- if flag_value:
178
- config['fast_context'] = True
179
- config['fast_context_path'] = flag_value
180
- return config
181
-
182
- def execute(self, query: str = None, path: str = None, **kwargs) -> str:
183
- """
184
- Execute fast context search and return formatted context.
185
-
186
- Args:
187
- query: Search query
188
- path: Search path
189
-
190
- Returns:
191
- Formatted context string
192
- """
193
- if not query or not path:
194
- return ""
195
-
196
- matches = self.search_context(query, path, **kwargs)
197
- return self.format_context_for_prompt(matches)
@@ -1 +0,0 @@
1
- __version__ = "2.3.77"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes