PraisonAI 2.3.57__tar.gz → 2.3.62__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 (110) hide show
  1. {praisonai-2.3.57 → praisonai-2.3.62}/PKG-INFO +17 -2
  2. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/PKG-INFO +17 -2
  3. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/SOURCES.txt +1 -0
  4. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/requires.txt +1 -1
  5. {praisonai-2.3.57 → praisonai-2.3.62}/README.md +15 -0
  6. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/main.py +150 -3
  7. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/deploy.py +1 -1
  8. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/base.py +6 -3
  9. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/claude_code.py +12 -12
  10. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/codex_cli.py +3 -0
  11. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/cursor_cli.py +3 -0
  12. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/gemini_cli.py +6 -6
  13. praisonai-2.3.62/praisonai/version.py +1 -0
  14. {praisonai-2.3.57 → praisonai-2.3.62}/pyproject.toml +1 -1
  15. praisonai-2.3.62/tests/test_approval_system.py +221 -0
  16. praisonai-2.3.57/praisonai/version.py +0 -1
  17. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/dependency_links.txt +0 -0
  18. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/entry_points.txt +0 -0
  19. {praisonai-2.3.57 → praisonai-2.3.62}/PraisonAI.egg-info/top_level.txt +0 -0
  20. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/__init__.py +0 -0
  21. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/__main__.py +0 -0
  22. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/agents_generator.py +0 -0
  23. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/api/call.py +0 -0
  24. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/auto.py +0 -0
  25. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/chainlit_ui.py +0 -0
  26. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/__init__.py +0 -0
  27. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/__init__.py +0 -0
  28. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/at_mentions.py +0 -0
  29. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/auto_memory.py +0 -0
  30. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/autonomy_mode.py +0 -0
  31. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/base.py +0 -0
  32. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/cost_tracker.py +0 -0
  33. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/external_agents.py +0 -0
  34. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/fast_context.py +0 -0
  35. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/flow_display.py +0 -0
  36. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/git_integration.py +0 -0
  37. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/guardrail.py +0 -0
  38. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/handoff.py +0 -0
  39. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/image.py +0 -0
  40. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/interactive_tui.py +0 -0
  41. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/knowledge.py +0 -0
  42. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/mcp.py +0 -0
  43. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/message_queue.py +0 -0
  44. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/metrics.py +0 -0
  45. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/n8n.py +0 -0
  46. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/repo_map.py +0 -0
  47. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/router.py +0 -0
  48. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/sandbox_executor.py +0 -0
  49. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/session.py +0 -0
  50. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/slash_commands.py +0 -0
  51. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/telemetry.py +0 -0
  52. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/todo.py +0 -0
  53. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/tools.py +0 -0
  54. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/cli/features/workflow.py +0 -0
  55. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/__init__.py +0 -0
  56. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/agent_tools.py +0 -0
  57. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/diff/__init__.py +0 -0
  58. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/diff/diff_strategy.py +0 -0
  59. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/__init__.py +0 -0
  60. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/apply_diff.py +0 -0
  61. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/execute_command.py +0 -0
  62. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/list_files.py +0 -0
  63. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/read_file.py +0 -0
  64. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/search_replace.py +0 -0
  65. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/tools/write_file.py +0 -0
  66. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/utils/__init__.py +0 -0
  67. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/utils/file_utils.py +0 -0
  68. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/utils/ignore_utils.py +0 -0
  69. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/code/utils/text_utils.py +0 -0
  70. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/inbuilt_tools/__init__.py +0 -0
  71. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/inbuilt_tools/autogen_tools.py +0 -0
  72. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/inc/__init__.py +0 -0
  73. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/inc/config.py +0 -0
  74. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/inc/models.py +0 -0
  75. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/integrations/__init__.py +0 -0
  76. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/profiler.py +0 -0
  77. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/scheduler.py +0 -0
  78. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/setup/__init__.py +0 -0
  79. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/setup/build.py +0 -0
  80. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/setup/post_install.py +0 -0
  81. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/setup/setup_conda_env.py +0 -0
  82. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/setup.py +0 -0
  83. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/test.py +0 -0
  84. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/train.py +0 -0
  85. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/train_vision.py +0 -0
  86. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/agents.py +0 -0
  87. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/callbacks.py +0 -0
  88. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/chat.py +0 -0
  89. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/code.py +0 -0
  90. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/colab.py +0 -0
  91. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/colab_chainlit.py +0 -0
  92. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/components/aicoder.py +0 -0
  93. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/context.py +0 -0
  94. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/database_config.py +0 -0
  95. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/db.py +0 -0
  96. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/realtime.py +0 -0
  97. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/realtimeclient/__init__.py +0 -0
  98. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/realtimeclient/tools.py +0 -0
  99. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/ui/sql_alchemy.py +0 -0
  100. {praisonai-2.3.57 → praisonai-2.3.62}/praisonai/upload_vision.py +0 -0
  101. {praisonai-2.3.57 → praisonai-2.3.62}/setup.cfg +0 -0
  102. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test.py +0 -0
  103. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_agents_playbook.py +0 -0
  104. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_basic.py +0 -0
  105. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_cli_features.py +0 -0
  106. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_custom_tools_fix.py +0 -0
  107. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_double_api_fix.py +0 -0
  108. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_n8n_integration.py +0 -0
  109. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_phase2_refactor.py +0 -0
  110. {praisonai-2.3.57 → praisonai-2.3.62}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.57
3
+ Version: 2.3.62
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
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: rich>=13.7
12
12
  Requires-Dist: markdown>=3.5
13
13
  Requires-Dist: pyparsing>=3.0.0
14
- Requires-Dist: praisonaiagents>=0.1.18
14
+ Requires-Dist: praisonaiagents>=0.1.21
15
15
  Requires-Dist: python-dotenv>=0.19.0
16
16
  Requires-Dist: instructor>=1.3.3
17
17
  Requires-Dist: PyYAML>=6.0
@@ -1155,6 +1155,20 @@ praisonai "Complex analysis task" --planning --planning-reasoning
1155
1155
  praisonai "Task" --planning --auto-approve-plan
1156
1156
  ```
1157
1157
 
1158
+ ### Tool Approval CLI:
1159
+ ```bash
1160
+ # Auto-approve ALL tool executions (use with caution!)
1161
+ praisonai "run ls command" --trust
1162
+
1163
+ # Auto-approve tools up to a risk level (prompt for higher)
1164
+ # Levels: low, medium, high, critical
1165
+ praisonai "write to file" --approve-level high # Prompts for critical tools only
1166
+ praisonai "task" --approve-level medium # Prompts for high and critical
1167
+
1168
+ # Default behavior (no flags): prompts for all dangerous tools
1169
+ praisonai "run shell command" # Will prompt for approval
1170
+ ```
1171
+
1158
1172
  ### Memory CLI:
1159
1173
  ```bash
1160
1174
  # Enable memory for agent (persists across sessions)
@@ -2496,6 +2510,7 @@ PraisonAI provides zero-dependency persistent memory for agents. For detailed ex
2496
2510
  - 🤝 **Agent Handoffs** - Transfer context between specialised agents
2497
2511
  - 🛡️ **Guardrails** - Input/output validation and safety checks
2498
2512
  - ✅ **Human Approval** - Require human confirmation for critical actions
2513
+ - 🔐 **Tool Approval CLI** - `--trust` (auto-approve all) and `--approve-level` (risk-based approval)
2499
2514
  - 💬 **Sessions Management** - Isolated conversation contexts
2500
2515
  - 🔄 **Stateful Agents** - Maintain state across interactions
2501
2516
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PraisonAI
3
- Version: 2.3.57
3
+ Version: 2.3.62
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
@@ -11,7 +11,7 @@ Description-Content-Type: text/markdown
11
11
  Requires-Dist: rich>=13.7
12
12
  Requires-Dist: markdown>=3.5
13
13
  Requires-Dist: pyparsing>=3.0.0
14
- Requires-Dist: praisonaiagents>=0.1.18
14
+ Requires-Dist: praisonaiagents>=0.1.21
15
15
  Requires-Dist: python-dotenv>=0.19.0
16
16
  Requires-Dist: instructor>=1.3.3
17
17
  Requires-Dist: PyYAML>=6.0
@@ -1155,6 +1155,20 @@ praisonai "Complex analysis task" --planning --planning-reasoning
1155
1155
  praisonai "Task" --planning --auto-approve-plan
1156
1156
  ```
1157
1157
 
1158
+ ### Tool Approval CLI:
1159
+ ```bash
1160
+ # Auto-approve ALL tool executions (use with caution!)
1161
+ praisonai "run ls command" --trust
1162
+
1163
+ # Auto-approve tools up to a risk level (prompt for higher)
1164
+ # Levels: low, medium, high, critical
1165
+ praisonai "write to file" --approve-level high # Prompts for critical tools only
1166
+ praisonai "task" --approve-level medium # Prompts for high and critical
1167
+
1168
+ # Default behavior (no flags): prompts for all dangerous tools
1169
+ praisonai "run shell command" # Will prompt for approval
1170
+ ```
1171
+
1158
1172
  ### Memory CLI:
1159
1173
  ```bash
1160
1174
  # Enable memory for agent (persists across sessions)
@@ -2496,6 +2510,7 @@ PraisonAI provides zero-dependency persistent memory for agents. For detailed ex
2496
2510
  - 🤝 **Agent Handoffs** - Transfer context between specialised agents
2497
2511
  - 🛡️ **Guardrails** - Input/output validation and safety checks
2498
2512
  - ✅ **Human Approval** - Require human confirmation for critical actions
2513
+ - 🔐 **Tool Approval CLI** - `--trust` (auto-approve all) and `--approve-level` (risk-based approval)
2499
2514
  - 💬 **Sessions Management** - Isolated conversation contexts
2500
2515
  - 🔄 **Stateful Agents** - Maintain state across interactions
2501
2516
 
@@ -97,6 +97,7 @@ praisonai/ui/realtimeclient/__init__.py
97
97
  praisonai/ui/realtimeclient/tools.py
98
98
  tests/test.py
99
99
  tests/test_agents_playbook.py
100
+ tests/test_approval_system.py
100
101
  tests/test_basic.py
101
102
  tests/test_cli_features.py
102
103
  tests/test_custom_tools_fix.py
@@ -1,7 +1,7 @@
1
1
  rich>=13.7
2
2
  markdown>=3.5
3
3
  pyparsing>=3.0.0
4
- praisonaiagents>=0.1.18
4
+ praisonaiagents>=0.1.21
5
5
  python-dotenv>=0.19.0
6
6
  instructor>=1.3.3
7
7
  PyYAML>=6.0
@@ -1058,6 +1058,20 @@ praisonai "Complex analysis task" --planning --planning-reasoning
1058
1058
  praisonai "Task" --planning --auto-approve-plan
1059
1059
  ```
1060
1060
 
1061
+ ### Tool Approval CLI:
1062
+ ```bash
1063
+ # Auto-approve ALL tool executions (use with caution!)
1064
+ praisonai "run ls command" --trust
1065
+
1066
+ # Auto-approve tools up to a risk level (prompt for higher)
1067
+ # Levels: low, medium, high, critical
1068
+ praisonai "write to file" --approve-level high # Prompts for critical tools only
1069
+ praisonai "task" --approve-level medium # Prompts for high and critical
1070
+
1071
+ # Default behavior (no flags): prompts for all dangerous tools
1072
+ praisonai "run shell command" # Will prompt for approval
1073
+ ```
1074
+
1061
1075
  ### Memory CLI:
1062
1076
  ```bash
1063
1077
  # Enable memory for agent (persists across sessions)
@@ -2399,6 +2413,7 @@ PraisonAI provides zero-dependency persistent memory for agents. For detailed ex
2399
2413
  - 🤝 **Agent Handoffs** - Transfer context between specialised agents
2400
2414
  - 🛡️ **Guardrails** - Input/output validation and safety checks
2401
2415
  - ✅ **Human Approval** - Require human confirmation for critical actions
2416
+ - 🔐 **Tool Approval CLI** - `--trust` (auto-approve all) and `--approve-level` (risk-based approval)
2402
2417
  - 💬 **Sessions Management** - Isolated conversation contexts
2403
2418
  - 🔄 **Stateful Agents** - Maintain state across interactions
2404
2419
 
@@ -68,13 +68,18 @@ AUTOGEN_AVAILABLE = importlib.util.find_spec("autogen") is not None
68
68
  PRAISONAI_AVAILABLE = importlib.util.find_spec("praisonaiagents") is not None
69
69
  TRAIN_AVAILABLE = importlib.util.find_spec("unsloth") is not None
70
70
 
71
- logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO'), format='%(asctime)s - %(levelname)s - %(message)s')
71
+ logging.basicConfig(level=os.environ.get('LOGLEVEL', 'WARNING'), format='%(asctime)s - %(levelname)s - %(message)s')
72
72
  logging.getLogger('alembic').setLevel(logging.ERROR)
73
73
  logging.getLogger('gradio').setLevel(logging.ERROR)
74
74
  logging.getLogger('gradio').setLevel(os.environ.get('GRADIO_LOGLEVEL', 'WARNING'))
75
75
  logging.getLogger('rust_logger').setLevel(logging.WARNING)
76
76
  logging.getLogger('duckduckgo').setLevel(logging.ERROR)
77
77
  logging.getLogger('_client').setLevel(logging.ERROR)
78
+ # Suppress praisonaiagents INFO logs unless LOGLEVEL is explicitly set to debug/info
79
+ if os.environ.get('LOGLEVEL', '').upper() not in ('DEBUG', 'INFO'):
80
+ logging.getLogger('praisonaiagents').setLevel(logging.WARNING)
81
+ logging.getLogger('praisonaiagents.llm').setLevel(logging.WARNING)
82
+ logging.getLogger('praisonaiagents.llm.llm').setLevel(logging.WARNING)
78
83
 
79
84
  def stream_subprocess(command, env=None):
80
85
  """
@@ -693,9 +698,18 @@ class PraisonAI:
693
698
  # Autonomy Mode - control AI action approval
694
699
  parser.add_argument("--autonomy", type=str, choices=["suggest", "auto_edit", "full_auto"], help="Set autonomy mode for AI actions")
695
700
 
701
+ # Tool Approval - control tool execution approval
702
+ parser.add_argument("--trust", action="store_true", help="Auto-approve all tool executions (skip approval prompts)")
703
+ parser.add_argument("--approve-level", type=str, choices=["low", "medium", "high", "critical"],
704
+ help="Auto-approve tools up to this risk level (e.g., --approve-level high approves low/medium/high but prompts for critical)")
705
+
696
706
  # Sandbox Execution - secure command execution
697
707
  parser.add_argument("--sandbox", type=str, choices=["off", "basic", "strict"], help="Enable sandboxed command execution")
698
708
 
709
+ # External Agent - use external AI CLI tools
710
+ parser.add_argument("--external-agent", type=str, choices=["claude", "gemini", "codex", "cursor"],
711
+ help="Use external AI CLI tool (claude, gemini, codex, cursor)")
712
+
699
713
  # If we're in a test environment, parse with empty args to avoid pytest interference
700
714
  if in_test_env:
701
715
  args, unknown_args = parser.parse_known_args([])
@@ -2922,7 +2936,8 @@ Provide ONLY the commit message, no explanations."""
2922
2936
  if max_tokens:
2923
2937
  # Pass llm as dict with model and max_tokens
2924
2938
  agent_config["llm"] = {"model": self.args.llm, "max_tokens": max_tokens}
2925
- print(f"[bold cyan]Max tokens set to: {max_tokens}[/bold cyan]")
2939
+ if os.environ.get('LOGLEVEL', '').upper() == 'DEBUG':
2940
+ print(f"[bold cyan]Max tokens set to: {max_tokens}[/bold cyan]")
2926
2941
  else:
2927
2942
  agent_config["llm"] = self.args.llm
2928
2943
 
@@ -2995,6 +3010,35 @@ Provide ONLY the commit message, no explanations."""
2995
3010
 
2996
3011
  # ===== NEW CLI FEATURES INTEGRATION =====
2997
3012
 
3013
+ # Tool Approval - Auto-approve tools based on --trust or --approve-level
3014
+ if getattr(self.args, 'trust', False) or getattr(self.args, 'approve_level', None):
3015
+ from praisonaiagents.approval import set_approval_callback, ApprovalDecision
3016
+
3017
+ if getattr(self.args, 'trust', False):
3018
+ # Auto-approve all tools
3019
+ def auto_approve_all(function_name, arguments, risk_level):
3020
+ return ApprovalDecision(approved=True, reason="Auto-approved via --trust flag")
3021
+ set_approval_callback(auto_approve_all)
3022
+ print("[bold yellow]⚠️ Trust mode enabled - all tool executions will be auto-approved[/bold yellow]")
3023
+ elif getattr(self.args, 'approve_level', None):
3024
+ # Auto-approve up to specified risk level
3025
+ max_level = self.args.approve_level
3026
+ risk_order = {"low": 1, "medium": 2, "high": 3, "critical": 4}
3027
+ max_level_value = risk_order.get(max_level, 3)
3028
+
3029
+ def level_based_approve(function_name, arguments, risk_level):
3030
+ tool_level_value = risk_order.get(risk_level, 4)
3031
+ if tool_level_value <= max_level_value:
3032
+ return ApprovalDecision(approved=True, reason=f"Auto-approved (level {risk_level} <= {max_level})")
3033
+ else:
3034
+ # Signal to pause Live display before showing approval prompt
3035
+ # This is handled by the approval_pending flag in status_info
3036
+ from praisonaiagents.approval import console_approval_callback
3037
+ return console_approval_callback(function_name, arguments, risk_level)
3038
+
3039
+ set_approval_callback(level_based_approve)
3040
+ print(f"[bold cyan]Auto-approve enabled for tools up to '{max_level}' risk level[/bold cyan]")
3041
+
2998
3042
  # Router - Smart model selection (must be before agent creation)
2999
3043
  if getattr(self.args, 'router', False):
3000
3044
  from .features.router import RouterHandler
@@ -3035,6 +3079,43 @@ Provide ONLY the commit message, no explanations."""
3035
3079
  existing_tools = list(mcp_tools)
3036
3080
  agent_config['tools'] = existing_tools
3037
3081
 
3082
+ # External Agent - Use external AI CLI tools directly
3083
+ if getattr(self.args, 'external_agent', None):
3084
+ from rich.console import Console
3085
+ ext_console = Console()
3086
+ external_agent_name = self.args.external_agent
3087
+ try:
3088
+ from .features.external_agents import ExternalAgentsHandler
3089
+ handler = ExternalAgentsHandler(verbose=getattr(self.args, 'verbose', False))
3090
+
3091
+ # Get workspace from current directory
3092
+ import os
3093
+ workspace = os.getcwd()
3094
+
3095
+ integration = handler.get_integration(external_agent_name, workspace=workspace)
3096
+
3097
+ if integration.is_available:
3098
+ ext_console.print(f"[bold cyan]🔌 Using external agent: {external_agent_name}[/bold cyan]")
3099
+
3100
+ # Run the external agent directly instead of PraisonAI agent
3101
+ import asyncio
3102
+ try:
3103
+ result = asyncio.run(integration.execute(prompt))
3104
+ ext_console.print(f"\n[bold green]Result from {external_agent_name}:[/bold green]")
3105
+ ext_console.print(result)
3106
+ # Return empty string to avoid duplicate printing by caller
3107
+ return ""
3108
+ except Exception as e:
3109
+ ext_console.print(f"[red]Error executing {external_agent_name}: {e}[/red]")
3110
+ return None
3111
+ else:
3112
+ ext_console.print(f"[yellow]⚠️ External agent '{external_agent_name}' is not installed[/yellow]")
3113
+ ext_console.print(f"[dim]Install with: {handler._get_install_instructions(external_agent_name)}[/dim]")
3114
+ return None
3115
+ except Exception as e:
3116
+ ext_console.print(f"[red]Error setting up external agent: {e}[/red]")
3117
+ return None
3118
+
3038
3119
  # Fast Context - Codebase search
3039
3120
  if getattr(self.args, 'fast_context', None):
3040
3121
  from .features.fast_context import FastContextHandler
@@ -3264,7 +3345,9 @@ Now, {final_instruction.lower()}:"""
3264
3345
  'result': None,
3265
3346
  'error': None,
3266
3347
  'start_time': time.time(),
3267
- 'available_tools': tool_names
3348
+ 'available_tools': tool_names,
3349
+ 'approval_pending': False, # Flag to pause Live display during approval
3350
+ 'live_instance': None # Reference to Live instance for stopping
3268
3351
  }
3269
3352
 
3270
3353
  def tool_call_callback(message):
@@ -3327,6 +3410,53 @@ Now, {final_instruction.lower()}:"""
3327
3410
  finally:
3328
3411
  status_info['done'] = True
3329
3412
 
3413
+ # Set up approval callback that stops Live display before prompting
3414
+ # Only if not using --trust (which auto-approves everything)
3415
+ if not getattr(self.args, 'trust', False):
3416
+ from praisonaiagents.approval import set_approval_callback, ApprovalDecision
3417
+ from rich.prompt import Confirm
3418
+ from rich.panel import Panel
3419
+
3420
+ def cli_approval_with_live_pause(function_name, arguments, risk_level):
3421
+ """Approval callback that stops Live display before prompting."""
3422
+ # Signal to stop Live display
3423
+ status_info['approval_pending'] = True
3424
+
3425
+ # Wait a moment for Live to stop
3426
+ time.sleep(0.2)
3427
+
3428
+ # Now show the approval prompt
3429
+ risk_colors = {"critical": "bold red", "high": "red", "medium": "yellow", "low": "blue"}
3430
+ risk_color = risk_colors.get(risk_level, "white")
3431
+
3432
+ tool_info = f"[bold]Function:[/] {function_name}\n"
3433
+ tool_info += f"[bold]Risk Level:[/] [{risk_color}]{risk_level.upper()}[/{risk_color}]\n"
3434
+ tool_info += "[bold]Arguments:[/]\n"
3435
+ for key, value in arguments.items():
3436
+ str_value = str(value)[:100] + "..." if len(str(value)) > 100 else str(value)
3437
+ tool_info += f" {key}: {str_value}\n"
3438
+
3439
+ console.print(Panel(tool_info.strip(), title="🔒 Tool Approval Required", border_style=risk_color))
3440
+
3441
+ try:
3442
+ approved = Confirm.ask(f"[{risk_color}]Execute this {risk_level} risk tool?[/{risk_color}]", default=False)
3443
+ status_info['approval_pending'] = False
3444
+
3445
+ if approved:
3446
+ console.print("[green]✅ Approved[/green]")
3447
+ return ApprovalDecision(approved=True, reason="User approved")
3448
+ else:
3449
+ console.print("[red]❌ Denied[/red]")
3450
+ return ApprovalDecision(approved=False, reason="User denied")
3451
+ except (KeyboardInterrupt, EOFError):
3452
+ status_info['approval_pending'] = False
3453
+ console.print("\n[red]❌ Cancelled[/red]")
3454
+ return ApprovalDecision(approved=False, reason="User cancelled")
3455
+
3456
+ # Only set if not already set by --approve-level
3457
+ if not getattr(self.args, 'approve_level', None):
3458
+ set_approval_callback(cli_approval_with_live_pause)
3459
+
3330
3460
  # Start agent in background thread
3331
3461
  thread = threading.Thread(target=run_agent, daemon=True)
3332
3462
  thread.start()
@@ -3334,9 +3464,26 @@ Now, {final_instruction.lower()}:"""
3334
3464
  # Show live status while processing
3335
3465
  try:
3336
3466
  with Live(build_status_display(), console=console, refresh_per_second=4, transient=True) as live:
3467
+ status_info['live_instance'] = live
3337
3468
  while not status_info['done']:
3469
+ # Check if approval is pending - stop Live to show prompt
3470
+ if status_info['approval_pending']:
3471
+ break
3338
3472
  live.update(build_status_display())
3339
3473
  time.sleep(0.1)
3474
+
3475
+ # If approval was pending, wait for it to complete then restart Live
3476
+ while status_info['approval_pending']:
3477
+ time.sleep(0.1)
3478
+
3479
+ # Continue with Live display if not done
3480
+ if not status_info['done']:
3481
+ with Live(build_status_display(), console=console, refresh_per_second=4, transient=True) as live:
3482
+ while not status_info['done']:
3483
+ if status_info['approval_pending']:
3484
+ break
3485
+ live.update(build_status_display())
3486
+ time.sleep(0.1)
3340
3487
  except KeyboardInterrupt:
3341
3488
  console.print("\n[dim]Interrupted[/dim]")
3342
3489
  # Unregister callback
@@ -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.57 gunicorn markdown\n")
60
+ file.write("RUN pip install flask praisonai==2.3.62 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
 
@@ -135,7 +135,8 @@ class BaseCLIIntegration(ABC):
135
135
  *cmd,
136
136
  stdout=asyncio.subprocess.PIPE,
137
137
  stderr=asyncio.subprocess.PIPE,
138
- cwd=self.workspace
138
+ cwd=self.workspace,
139
+ env=self.get_env()
139
140
  )
140
141
 
141
142
  try:
@@ -173,7 +174,8 @@ class BaseCLIIntegration(ABC):
173
174
  *cmd,
174
175
  stdout=asyncio.subprocess.PIPE,
175
176
  stderr=asyncio.subprocess.PIPE,
176
- cwd=self.workspace
177
+ cwd=self.workspace,
178
+ env=self.get_env()
177
179
  )
178
180
 
179
181
  try:
@@ -208,7 +210,8 @@ class BaseCLIIntegration(ABC):
208
210
  *cmd,
209
211
  stdout=asyncio.subprocess.PIPE,
210
212
  stderr=asyncio.subprocess.PIPE,
211
- cwd=self.workspace
213
+ cwd=self.workspace,
214
+ env=self.get_env()
212
215
  )
213
216
 
214
217
  try:
@@ -127,23 +127,16 @@ class ClaudeCodeIntegration(BaseCLIIntegration):
127
127
  """
128
128
  cmd = ["claude"]
129
129
 
130
- # Add continue flag if needed
131
- if continue_session or self._session_active:
132
- cmd.append("--continue")
133
-
134
- # Add skip permissions flag
135
- if self.skip_permissions:
136
- cmd.append("--dangerously-skip-permissions")
137
-
138
- # Add print mode flag
130
+ # Add print mode flag for non-interactive output
139
131
  cmd.append("-p")
140
132
 
141
- # Add prompt
142
- cmd.append(prompt)
143
-
144
133
  # Add output format
145
134
  cmd.extend(["--output-format", self.output_format])
146
135
 
136
+ # Add continue flag if needed
137
+ if continue_session or self._session_active:
138
+ cmd.append("--continue")
139
+
147
140
  # Add model if specified
148
141
  if self.model:
149
142
  cmd.extend(["--model", self.model])
@@ -160,6 +153,13 @@ class ClaudeCodeIntegration(BaseCLIIntegration):
160
153
  if self.disallowed_tools:
161
154
  cmd.extend(["--disallowedTools", ",".join(self.disallowed_tools)])
162
155
 
156
+ # Add verbose if needed
157
+ if options.get('verbose'):
158
+ cmd.append("--verbose")
159
+
160
+ # Add prompt last
161
+ cmd.append(prompt)
162
+
163
163
  return cmd
164
164
 
165
165
  async def execute(self, prompt: str, **options) -> str:
@@ -93,6 +93,9 @@ class CodexCLIIntegration(BaseCLIIntegration):
93
93
  """
94
94
  cmd = ["codex", "exec"]
95
95
 
96
+ # Add working directory
97
+ cmd.extend(["-C", self.workspace])
98
+
96
99
  # Add task
97
100
  cmd.append(task)
98
101
 
@@ -97,6 +97,9 @@ class CursorCLIIntegration(BaseCLIIntegration):
97
97
  # Add print mode flag
98
98
  cmd.append("-p")
99
99
 
100
+ # Add workspace
101
+ cmd.extend(["--workspace", self.workspace])
102
+
100
103
  # Add force flag if enabled
101
104
  if self.force:
102
105
  cmd.append("--force")
@@ -92,12 +92,6 @@ class GeminiCLIIntegration(BaseCLIIntegration):
92
92
  """
93
93
  cmd = ["gemini"]
94
94
 
95
- # Add print mode flag
96
- cmd.append("-p")
97
-
98
- # Add prompt
99
- cmd.append(prompt)
100
-
101
95
  # Add model
102
96
  cmd.extend(["-m", self.model])
103
97
 
@@ -112,6 +106,12 @@ class GeminiCLIIntegration(BaseCLIIntegration):
112
106
  if self.sandbox:
113
107
  cmd.append("--sandbox")
114
108
 
109
+ # Add YOLO mode for non-interactive execution
110
+ cmd.append("--yolo")
111
+
112
+ # Add prompt as positional argument (must be last)
113
+ cmd.append(prompt)
114
+
115
115
  return cmd
116
116
 
117
117
  async def execute(self, prompt: str, **options) -> str:
@@ -0,0 +1 @@
1
+ __version__ = "2.3.62"
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "rich>=13.7",
13
13
  "markdown>=3.5",
14
14
  "pyparsing>=3.0.0",
15
- "praisonaiagents>=0.1.18",
15
+ "praisonaiagents>=0.1.21",
16
16
  "python-dotenv>=0.19.0",
17
17
  "instructor>=1.3.3",
18
18
  "PyYAML>=6.0",
@@ -0,0 +1,221 @@
1
+ """
2
+ Tests for the CLI approval system.
3
+
4
+ Tests:
5
+ 1. --trust flag auto-approves all tools
6
+ 2. --approve-level flag with different risk levels
7
+ 3. Approval callback integration
8
+ """
9
+
10
+ import pytest
11
+ import sys
12
+ import os
13
+
14
+ # Add the src directory to the path
15
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
16
+
17
+
18
+ class TestApprovalFlags:
19
+ """Test CLI approval flags."""
20
+
21
+ def test_trust_flag_exists(self):
22
+ """Test that --trust flag is defined in argparse."""
23
+ import argparse
24
+
25
+ # Check that the args object has the trust attribute after parsing
26
+ # Parse with --help to avoid actually running anything
27
+ parser = argparse.ArgumentParser()
28
+ parser.add_argument("--trust", action="store_true")
29
+ parser.add_argument("--approve-level", type=str, choices=["low", "medium", "high", "critical"])
30
+
31
+ # Test that the arguments can be parsed
32
+ args = parser.parse_args(['--trust'])
33
+ assert args.trust is True
34
+
35
+ args = parser.parse_args([])
36
+ assert args.trust is False
37
+
38
+ def test_approve_level_flag_exists(self):
39
+ """Test that --approve-level flag is defined in argparse."""
40
+ import argparse
41
+
42
+ parser = argparse.ArgumentParser()
43
+ parser.add_argument("--approve-level", type=str, choices=["low", "medium", "high", "critical"])
44
+
45
+ # Test that the arguments can be parsed
46
+ args = parser.parse_args(['--approve-level', 'high'])
47
+ assert args.approve_level == 'high'
48
+
49
+ args = parser.parse_args(['--approve-level', 'critical'])
50
+ assert args.approve_level == 'critical'
51
+
52
+ def test_trust_flag_in_praisonai_parser(self):
53
+ """Test that --trust flag is in PraisonAI's actual parser."""
54
+ from praisonai.cli.main import PraisonAI
55
+
56
+ # Create instance - parse_args is called in __init__
57
+ praison = PraisonAI()
58
+
59
+ # Call parse_args to get the args
60
+ args = praison.parse_args()
61
+
62
+ # The args should have trust attribute after parse_args
63
+ assert hasattr(args, 'trust'), "PraisonAI.args should have 'trust' attribute"
64
+ # Cleanup
65
+ del praison
66
+
67
+ def test_approve_level_flag_in_praisonai_parser(self):
68
+ """Test that --approve-level flag is in PraisonAI's actual parser."""
69
+ from praisonai.cli.main import PraisonAI
70
+
71
+ # Create instance - parse_args is called in __init__
72
+ praison = PraisonAI()
73
+
74
+ # Call parse_args to get the args
75
+ args = praison.parse_args()
76
+
77
+ # The args should have approve_level attribute after parse_args
78
+ assert hasattr(args, 'approve_level'), "PraisonAI.args should have 'approve_level' attribute"
79
+
80
+
81
+ class TestApprovalCallback:
82
+ """Test approval callback system."""
83
+
84
+ def test_set_approval_callback(self):
85
+ """Test that we can set a custom approval callback."""
86
+ from praisonaiagents.approval import set_approval_callback, ApprovalDecision
87
+
88
+ callback_called = []
89
+
90
+ def custom_callback(function_name, arguments, risk_level):
91
+ callback_called.append((function_name, arguments, risk_level))
92
+ return ApprovalDecision(approved=True, reason="Test approved")
93
+
94
+ set_approval_callback(custom_callback)
95
+
96
+ # Verify callback was set
97
+ from praisonaiagents.approval import approval_callback
98
+ assert approval_callback == custom_callback
99
+
100
+ # Reset callback
101
+ set_approval_callback(None)
102
+
103
+ def test_auto_approve_callback(self):
104
+ """Test auto-approve callback returns approved decision."""
105
+ from praisonaiagents.approval import ApprovalDecision
106
+
107
+ def auto_approve_callback(function_name, arguments, risk_level):
108
+ return ApprovalDecision(approved=True, reason="Auto-approved")
109
+
110
+ decision = auto_approve_callback("execute_command", {"command": "ls"}, "critical")
111
+ assert decision.approved is True
112
+ assert decision.reason == "Auto-approved"
113
+
114
+ def test_level_based_callback(self):
115
+ """Test level-based approval callback."""
116
+ from praisonaiagents.approval import ApprovalDecision
117
+
118
+ def level_based_callback(function_name, arguments, risk_level, max_level="high"):
119
+ """Approve based on risk level threshold."""
120
+ levels = {"low": 1, "medium": 2, "high": 3, "critical": 4}
121
+ tool_level = levels.get(risk_level, 4)
122
+ max_allowed = levels.get(max_level, 3)
123
+
124
+ if tool_level <= max_allowed:
125
+ return ApprovalDecision(approved=True, reason=f"Auto-approved (level {risk_level} <= {max_level})")
126
+ else:
127
+ return ApprovalDecision(approved=False, reason=f"Denied (level {risk_level} > {max_level})")
128
+
129
+ # Test with max_level="high"
130
+ # Should approve low, medium, high but deny critical
131
+ assert level_based_callback("test", {}, "low", "high").approved is True
132
+ assert level_based_callback("test", {}, "medium", "high").approved is True
133
+ assert level_based_callback("test", {}, "high", "high").approved is True
134
+ assert level_based_callback("test", {}, "critical", "high").approved is False
135
+
136
+ # Test with max_level="critical"
137
+ # Should approve everything
138
+ assert level_based_callback("test", {}, "critical", "critical").approved is True
139
+
140
+
141
+ class TestApprovalRequirements:
142
+ """Test approval requirement configuration."""
143
+
144
+ def test_default_dangerous_tools(self):
145
+ """Test that default dangerous tools are configured."""
146
+ from praisonaiagents.approval import APPROVAL_REQUIRED_TOOLS, TOOL_RISK_LEVELS
147
+
148
+ # Check critical tools
149
+ assert "execute_command" in APPROVAL_REQUIRED_TOOLS
150
+ assert TOOL_RISK_LEVELS.get("execute_command") == "critical"
151
+
152
+ # Check high risk tools
153
+ assert "write_file" in APPROVAL_REQUIRED_TOOLS
154
+ assert TOOL_RISK_LEVELS.get("write_file") == "high"
155
+
156
+ def test_remove_approval_requirement(self):
157
+ """Test removing approval requirement."""
158
+ from praisonaiagents.approval import (
159
+ add_approval_requirement,
160
+ remove_approval_requirement,
161
+ is_approval_required,
162
+ APPROVAL_REQUIRED_TOOLS
163
+ )
164
+
165
+ # Add a test tool
166
+ add_approval_requirement("test_tool", "medium")
167
+ assert is_approval_required("test_tool") is True
168
+
169
+ # Remove it
170
+ remove_approval_requirement("test_tool")
171
+ assert is_approval_required("test_tool") is False
172
+
173
+ def test_is_approval_required(self):
174
+ """Test checking if approval is required."""
175
+ from praisonaiagents.approval import is_approval_required
176
+
177
+ # execute_command should require approval
178
+ assert is_approval_required("execute_command") is True
179
+
180
+ # read_file should not require approval
181
+ assert is_approval_required("read_file") is False
182
+
183
+ # list_files should not require approval
184
+ assert is_approval_required("list_files") is False
185
+
186
+
187
+ class TestApprovalDecision:
188
+ """Test ApprovalDecision class."""
189
+
190
+ def test_approval_decision_approved(self):
191
+ """Test approved decision."""
192
+ from praisonaiagents.approval import ApprovalDecision
193
+
194
+ decision = ApprovalDecision(approved=True, reason="User approved")
195
+ assert decision.approved is True
196
+ assert decision.reason == "User approved"
197
+ assert decision.modified_args == {}
198
+
199
+ def test_approval_decision_denied(self):
200
+ """Test denied decision."""
201
+ from praisonaiagents.approval import ApprovalDecision
202
+
203
+ decision = ApprovalDecision(approved=False, reason="User denied")
204
+ assert decision.approved is False
205
+ assert decision.reason == "User denied"
206
+
207
+ def test_approval_decision_with_modified_args(self):
208
+ """Test decision with modified arguments."""
209
+ from praisonaiagents.approval import ApprovalDecision
210
+
211
+ decision = ApprovalDecision(
212
+ approved=True,
213
+ reason="Approved with modifications",
214
+ modified_args={"command": "ls -la"}
215
+ )
216
+ assert decision.approved is True
217
+ assert decision.modified_args == {"command": "ls -la"}
218
+
219
+
220
+ if __name__ == "__main__":
221
+ pytest.main([__file__, "-v"])
@@ -1 +0,0 @@
1
- __version__ = "2.3.57"
File without changes
File without changes
File without changes
File without changes