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.
- {praisonai-2.3.77 → praisonai-2.3.78}/PKG-INFO +1 -1
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/PKG-INFO +1 -1
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/compare.py +49 -8
- praisonai-2.3.78/praisonai/cli/features/fast_context.py +374 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/handoff.py +3 -3
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/main.py +12 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/deploy.py +1 -1
- praisonai-2.3.78/praisonai/version.py +1 -0
- praisonai-2.3.77/praisonai/cli/features/fast_context.py +0 -197
- praisonai-2.3.77/praisonai/version.py +0 -1
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/SOURCES.txt +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/dependency_links.txt +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/entry_points.txt +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/requires.txt +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/PraisonAI.egg-info/top_level.txt +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/README.md +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/__main__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/agent_scheduler.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/agents_generator.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/api/call.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/auto.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/chainlit_ui.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/agent_scheduler.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/at_mentions.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/auto_memory.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/autonomy_mode.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/base.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/cost_tracker.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/eval.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/external_agents.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/flow_display.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/git_integration.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/guardrail.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/image.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/interactive_tui.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/knowledge.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/mcp.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/message_queue.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/metrics.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/n8n.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/profiling.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/repo_map.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/router.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/sandbox_executor.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/session.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/skills.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/slash_commands.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/telemetry.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/todo.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/tools.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/features/workflow.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/cli/schedule_cli.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/agent_tools.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/diff/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/diff/diff_strategy.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/apply_diff.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/execute_command.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/list_files.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/read_file.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/search_replace.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/tools/write_file.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/file_utils.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/ignore_utils.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/code/utils/text_utils.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inbuilt_tools/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/config.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/inc/models.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/base.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/claude_code.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/codex_cli.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/cursor_cli.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/integrations/gemini_cli.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/profiler.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/agent_scheduler.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/base.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/daemon_manager.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/state_manager.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler/yaml_loader.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/scheduler.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/build.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/post_install.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup/setup_conda_env.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/setup.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/test.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/train.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/train_vision.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/agents.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/callbacks.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/chat.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/code.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/colab.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/colab_chainlit.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/components/aicoder.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/context.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/database_config.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/db.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtime.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtimeclient/__init__.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/realtimeclient/tools.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/ui/sql_alchemy.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/praisonai/upload_vision.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/pyproject.toml +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/setup.cfg +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_agents_playbook.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_approval_system.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_basic.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_cli_features.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_custom_tools_fix.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_double_api_fix.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_n8n_integration.py +0 -0
- {praisonai-2.3.77 → praisonai-2.3.78}/tests/test_phase2_refactor.py +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|