praisonai-code 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,536 @@
1
+ """
2
+ Sandboxed Execution System for PraisonAI CLI.
3
+
4
+ Inspired by Codex CLI's sandbox modes for secure command execution.
5
+ Provides isolated execution environment when enabled via --sandbox flag.
6
+
7
+ Architecture:
8
+ - SandboxExecutor: Base class for sandboxed execution
9
+ - SubprocessSandbox: Subprocess-based isolation with resource limits
10
+ - SandboxPolicy: Configurable security policies
11
+
12
+ Note: Sandbox is ONLY activated when explicitly requested via CLI flag.
13
+ """
14
+
15
+ from dataclasses import dataclass, field
16
+ from typing import Dict, List, Optional, Set
17
+ from pathlib import Path
18
+ from enum import Enum
19
+ import shlex
20
+ import subprocess
21
+ import logging
22
+ import tempfile
23
+ import shutil
24
+ import time
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ # ============================================================================
30
+ # Sandbox Modes
31
+ # ============================================================================
32
+
33
+ class SandboxMode(Enum):
34
+ """
35
+ Sandbox execution modes.
36
+
37
+ - DISABLED: No sandboxing (default)
38
+ - BASIC: Basic isolation with resource limits
39
+ - STRICT: Strict isolation with filesystem restrictions
40
+ - NETWORK_ISOLATED: No network access
41
+ """
42
+ DISABLED = "disabled"
43
+ BASIC = "basic"
44
+ STRICT = "strict"
45
+ NETWORK_ISOLATED = "network_isolated"
46
+
47
+ @classmethod
48
+ def from_string(cls, value: str) -> "SandboxMode":
49
+ """Parse mode from string."""
50
+ value = value.lower().strip().replace("-", "_")
51
+ for mode in cls:
52
+ if mode.value == value:
53
+ return mode
54
+ raise ValueError(f"Unknown sandbox mode: {value}")
55
+
56
+
57
+ # ============================================================================
58
+ # Data Classes
59
+ # ============================================================================
60
+
61
+ @dataclass
62
+ class SandboxPolicy:
63
+ """
64
+ Security policy for sandbox execution.
65
+ """
66
+ mode: SandboxMode = SandboxMode.DISABLED
67
+
68
+ # Resource limits
69
+ max_memory_mb: int = 512
70
+ max_cpu_seconds: int = 30
71
+ max_file_size_mb: int = 10
72
+ max_processes: int = 10
73
+
74
+ # Filesystem restrictions
75
+ allowed_paths: Set[str] = field(default_factory=set)
76
+ blocked_paths: Set[str] = field(default_factory=lambda: {
77
+ "/etc", "/var", "/usr", "/bin", "/sbin",
78
+ "/root", "/home", "/sys", "/proc"
79
+ })
80
+ temp_dir: Optional[str] = None
81
+
82
+ # Network restrictions
83
+ allow_network: bool = True
84
+ allowed_hosts: Set[str] = field(default_factory=set)
85
+
86
+ # Command restrictions
87
+ blocked_commands: Set[str] = field(default_factory=lambda: {
88
+ "rm", "rmdir", "mv", "dd", "mkfs", "fdisk",
89
+ "sudo", "su", "chmod", "chown", "kill", "pkill",
90
+ "sh", "bash", "dash", "zsh", "csh", "ksh"
91
+ })
92
+
93
+ @classmethod
94
+ def for_mode(cls, mode: SandboxMode) -> "SandboxPolicy":
95
+ """Create policy for a given mode."""
96
+ if mode == SandboxMode.DISABLED:
97
+ return cls(mode=mode)
98
+
99
+ elif mode == SandboxMode.BASIC:
100
+ return cls(
101
+ mode=mode,
102
+ max_memory_mb=512,
103
+ max_cpu_seconds=60,
104
+ allow_network=True
105
+ )
106
+
107
+ elif mode == SandboxMode.STRICT:
108
+ return cls(
109
+ mode=mode,
110
+ max_memory_mb=256,
111
+ max_cpu_seconds=30,
112
+ max_processes=5,
113
+ allow_network=True,
114
+ blocked_commands={
115
+ "rm", "rmdir", "mv", "dd", "mkfs", "fdisk",
116
+ "sudo", "su", "chmod", "chown", "kill", "pkill",
117
+ "curl", "wget", "nc", "netcat", "ssh", "scp",
118
+ "sh", "bash", "dash", "zsh", "csh", "ksh"
119
+ }
120
+ )
121
+
122
+ elif mode == SandboxMode.NETWORK_ISOLATED:
123
+ return cls(
124
+ mode=mode,
125
+ max_memory_mb=512,
126
+ max_cpu_seconds=60,
127
+ allow_network=False
128
+ )
129
+
130
+ return cls(mode=mode)
131
+
132
+
133
+ @dataclass
134
+ class ExecutionResult:
135
+ """Result of sandboxed execution."""
136
+ success: bool
137
+ exit_code: int
138
+ stdout: str
139
+ stderr: str
140
+ duration_ms: float
141
+ was_sandboxed: bool
142
+ policy_violations: List[str] = field(default_factory=list)
143
+
144
+ @property
145
+ def output(self) -> str:
146
+ """Get combined output."""
147
+ return self.stdout + self.stderr
148
+
149
+
150
+ # ============================================================================
151
+ # Command Validator
152
+ # ============================================================================
153
+
154
+ class CommandValidator:
155
+ """
156
+ Validates commands against sandbox policy.
157
+ """
158
+
159
+ def __init__(self, policy: SandboxPolicy):
160
+ self.policy = policy
161
+
162
+ def validate(self, command: str) -> List[str]:
163
+ """
164
+ Validate a command against the policy.
165
+
166
+ Returns:
167
+ List of policy violations (empty if valid)
168
+ """
169
+ violations = []
170
+
171
+ if self.policy.mode == SandboxMode.DISABLED:
172
+ return violations
173
+
174
+ # Parse command
175
+ parts = command.split()
176
+ if not parts:
177
+ return violations
178
+
179
+ cmd_name = Path(parts[0]).name
180
+
181
+ # Check blocked commands
182
+ if cmd_name in self.policy.blocked_commands:
183
+ violations.append(f"Command '{cmd_name}' is blocked by sandbox policy")
184
+
185
+ if cmd_name == "find" and "-exec" in command:
186
+ violations.append("find -exec is blocked by sandbox policy")
187
+
188
+ # Check for dangerous patterns
189
+ dangerous_patterns = [
190
+ ("rm -rf", "Recursive force delete is blocked"),
191
+ ("> /dev/", "Writing to device files is blocked"),
192
+ ("| sh", "Piping to shell is blocked"),
193
+ ("| bash", "Piping to bash is blocked"),
194
+ ("$(", "Command substitution is blocked"),
195
+ ("`", "Backtick command substitution is blocked"),
196
+ ]
197
+
198
+ for pattern, message in dangerous_patterns:
199
+ if pattern in command:
200
+ violations.append(message)
201
+
202
+ # Check path access
203
+ for part in parts:
204
+ if part.startswith("/"):
205
+ for blocked in self.policy.blocked_paths:
206
+ if part.startswith(blocked):
207
+ violations.append(f"Access to '{blocked}' is blocked")
208
+ break
209
+
210
+ return violations
211
+
212
+ def is_allowed(self, command: str) -> bool:
213
+ """Check if command is allowed."""
214
+ return len(self.validate(command)) == 0
215
+
216
+
217
+ # ============================================================================
218
+ # Subprocess Sandbox
219
+ # ============================================================================
220
+
221
+ class SubprocessSandbox:
222
+ """
223
+ Subprocess-based sandbox execution.
224
+
225
+ Provides basic isolation through:
226
+ - Resource limits (timeout, memory via ulimit)
227
+ - Working directory isolation
228
+ - Environment variable control
229
+ - Command validation
230
+ """
231
+
232
+ def __init__(
233
+ self,
234
+ policy: Optional[SandboxPolicy] = None,
235
+ working_dir: Optional[str] = None,
236
+ verbose: bool = False
237
+ ):
238
+ self.policy = policy or SandboxPolicy()
239
+ self.working_dir = Path(working_dir) if working_dir else Path.cwd()
240
+ self.verbose = verbose
241
+ self.validator = CommandValidator(self.policy)
242
+
243
+ self._temp_dir: Optional[Path] = None
244
+
245
+ def _setup_temp_dir(self) -> Path:
246
+ """Set up temporary directory for isolated execution."""
247
+ if self._temp_dir is None:
248
+ self._temp_dir = Path(tempfile.mkdtemp(prefix="praisonai_sandbox_"))
249
+ return self._temp_dir
250
+
251
+ def _cleanup_temp_dir(self) -> None:
252
+ """Clean up temporary directory."""
253
+ if self._temp_dir and self._temp_dir.exists():
254
+ try:
255
+ shutil.rmtree(self._temp_dir)
256
+ except Exception as e:
257
+ logger.debug(f"Failed to cleanup temp dir: {e}")
258
+ self._temp_dir = None
259
+
260
+ def _build_environment(self) -> Dict[str, str]:
261
+ """Build restricted environment variables."""
262
+ import os
263
+
264
+ # Start with minimal environment
265
+ env = {
266
+ "PATH": "/usr/local/bin:/usr/bin:/bin",
267
+ "HOME": str(self._temp_dir or self.working_dir),
268
+ "TERM": "xterm",
269
+ "LANG": "en_US.UTF-8",
270
+ }
271
+
272
+ # Add some safe variables from current environment
273
+ safe_vars = ["USER", "SHELL", "PYTHONPATH"]
274
+ for var in safe_vars:
275
+ if var in os.environ:
276
+ env[var] = os.environ[var]
277
+
278
+ return env
279
+
280
+ def execute(
281
+ self,
282
+ command: str,
283
+ timeout: Optional[float] = None,
284
+ capture_output: bool = True
285
+ ) -> ExecutionResult:
286
+ """
287
+ Execute a command in the sandbox.
288
+
289
+ Args:
290
+ command: Command to execute
291
+ timeout: Timeout in seconds (overrides policy)
292
+ capture_output: Whether to capture stdout/stderr
293
+
294
+ Returns:
295
+ ExecutionResult with execution details
296
+ """
297
+ start_time = time.time()
298
+
299
+ # Check if sandbox is enabled
300
+ if self.policy.mode == SandboxMode.DISABLED:
301
+ return self._execute_unsandboxed(command, timeout, capture_output)
302
+
303
+ # Validate command
304
+ violations = self.validator.validate(command)
305
+ if violations:
306
+ return ExecutionResult(
307
+ success=False,
308
+ exit_code=-1,
309
+ stdout="",
310
+ stderr="Command blocked by sandbox policy:\n" + "\n".join(violations),
311
+ duration_ms=(time.time() - start_time) * 1000,
312
+ was_sandboxed=True,
313
+ policy_violations=violations
314
+ )
315
+
316
+ # Set up sandbox
317
+ if self.policy.mode == SandboxMode.STRICT:
318
+ self._setup_temp_dir()
319
+ cwd = self._temp_dir
320
+ else:
321
+ cwd = self.working_dir
322
+
323
+ # Build environment
324
+ env = self._build_environment()
325
+
326
+ # Set timeout
327
+ if timeout is None:
328
+ timeout = float(self.policy.max_cpu_seconds)
329
+
330
+ # Execute
331
+ try:
332
+ result = subprocess.run(
333
+ shlex.split(command),
334
+ shell=False,
335
+ cwd=cwd,
336
+ env=env,
337
+ capture_output=capture_output,
338
+ text=True,
339
+ timeout=timeout
340
+ )
341
+
342
+ return ExecutionResult(
343
+ success=result.returncode == 0,
344
+ exit_code=result.returncode,
345
+ stdout=result.stdout if capture_output else "",
346
+ stderr=result.stderr if capture_output else "",
347
+ duration_ms=(time.time() - start_time) * 1000,
348
+ was_sandboxed=True
349
+ )
350
+
351
+ except subprocess.TimeoutExpired:
352
+ return ExecutionResult(
353
+ success=False,
354
+ exit_code=-1,
355
+ stdout="",
356
+ stderr=f"Command timed out after {timeout}s",
357
+ duration_ms=(time.time() - start_time) * 1000,
358
+ was_sandboxed=True,
359
+ policy_violations=["Timeout exceeded"]
360
+ )
361
+
362
+ except Exception as e:
363
+ return ExecutionResult(
364
+ success=False,
365
+ exit_code=-1,
366
+ stdout="",
367
+ stderr=str(e),
368
+ duration_ms=(time.time() - start_time) * 1000,
369
+ was_sandboxed=True,
370
+ policy_violations=[str(e)]
371
+ )
372
+
373
+ finally:
374
+ if self.policy.mode == SandboxMode.STRICT:
375
+ self._cleanup_temp_dir()
376
+
377
+ def _execute_unsandboxed(
378
+ self,
379
+ command: str,
380
+ timeout: Optional[float],
381
+ capture_output: bool
382
+ ) -> ExecutionResult:
383
+ """Execute without sandbox (when disabled)."""
384
+ start_time = time.time()
385
+
386
+ # Block dangerous shell injection characters when using shell=True
387
+ banned_chars = [';', '&', '|', '$', '`']
388
+ if any(char in command for char in banned_chars):
389
+ return ExecutionResult(
390
+ success=False,
391
+ exit_code=-1,
392
+ stdout="",
393
+ stderr="Command contains blocked shell characters",
394
+ duration_ms=(time.time() - start_time) * 1000,
395
+ was_sandboxed=False,
396
+ policy_violations=["Command contains blocked shell characters"]
397
+ )
398
+
399
+ try:
400
+ # Use shell=False with shlex.split for safer execution
401
+ args = shlex.split(command)
402
+ result = subprocess.run(
403
+ args,
404
+ shell=False, # Use shell=False for security
405
+ cwd=self.working_dir,
406
+ capture_output=capture_output,
407
+ text=True,
408
+ timeout=timeout
409
+ )
410
+
411
+ return ExecutionResult(
412
+ success=result.returncode == 0,
413
+ exit_code=result.returncode,
414
+ stdout=result.stdout if capture_output else "",
415
+ stderr=result.stderr if capture_output else "",
416
+ duration_ms=(time.time() - start_time) * 1000,
417
+ was_sandboxed=False
418
+ )
419
+
420
+ except subprocess.TimeoutExpired:
421
+ return ExecutionResult(
422
+ success=False,
423
+ exit_code=-1,
424
+ stdout="",
425
+ stderr=f"Command timed out after {timeout}s",
426
+ duration_ms=(time.time() - start_time) * 1000,
427
+ was_sandboxed=False
428
+ )
429
+
430
+ except Exception as e:
431
+ return ExecutionResult(
432
+ success=False,
433
+ exit_code=-1,
434
+ stdout="",
435
+ stderr=str(e),
436
+ duration_ms=(time.time() - start_time) * 1000,
437
+ was_sandboxed=False
438
+ )
439
+
440
+
441
+ # ============================================================================
442
+ # CLI Integration Handler
443
+ # ============================================================================
444
+
445
+ class SandboxExecutorHandler:
446
+ """
447
+ Handler for integrating Sandbox Execution with PraisonAI CLI.
448
+
449
+ Note: Sandbox is ONLY activated when --sandbox flag is passed.
450
+ """
451
+
452
+ def __init__(self, verbose: bool = False):
453
+ self.verbose = verbose
454
+ self._sandbox: Optional[SubprocessSandbox] = None
455
+ self._enabled: bool = False
456
+
457
+ @property
458
+ def feature_name(self) -> str:
459
+ return "sandbox_executor"
460
+
461
+ @property
462
+ def is_enabled(self) -> bool:
463
+ """Check if sandbox is enabled."""
464
+ return self._enabled
465
+
466
+ def initialize(
467
+ self,
468
+ mode: str = "disabled",
469
+ working_dir: Optional[str] = None,
470
+ policy: Optional[SandboxPolicy] = None
471
+ ) -> SubprocessSandbox:
472
+ """
473
+ Initialize the sandbox.
474
+
475
+ Args:
476
+ mode: Sandbox mode (disabled, basic, strict, network_isolated)
477
+ working_dir: Working directory for execution
478
+ policy: Optional custom policy
479
+
480
+ Returns:
481
+ Configured SubprocessSandbox
482
+ """
483
+ try:
484
+ sandbox_mode = SandboxMode.from_string(mode)
485
+ except ValueError:
486
+ logger.warning(f"Unknown sandbox mode '{mode}', defaulting to 'disabled'")
487
+ sandbox_mode = SandboxMode.DISABLED
488
+
489
+ if policy is None:
490
+ policy = SandboxPolicy.for_mode(sandbox_mode)
491
+
492
+ self._sandbox = SubprocessSandbox(
493
+ policy=policy,
494
+ working_dir=working_dir,
495
+ verbose=self.verbose
496
+ )
497
+
498
+ self._enabled = sandbox_mode != SandboxMode.DISABLED
499
+
500
+ if self.verbose and self._enabled:
501
+ from rich import print as rprint
502
+ rprint(f"[yellow]⚠ Sandbox enabled: {sandbox_mode.value}[/yellow]")
503
+
504
+ return self._sandbox
505
+
506
+ def get_sandbox(self) -> Optional[SubprocessSandbox]:
507
+ """Get the sandbox executor."""
508
+ return self._sandbox
509
+
510
+ def execute(
511
+ self,
512
+ command: str,
513
+ timeout: Optional[float] = None
514
+ ) -> ExecutionResult:
515
+ """
516
+ Execute a command.
517
+
518
+ If sandbox is not initialized, runs without sandboxing.
519
+ """
520
+ if not self._sandbox:
521
+ self._sandbox = self.initialize()
522
+
523
+ return self._sandbox.execute(command, timeout=timeout)
524
+
525
+ def validate_command(self, command: str) -> List[str]:
526
+ """Validate a command against sandbox policy."""
527
+ if not self._sandbox:
528
+ return []
529
+
530
+ return self._sandbox.validator.validate(command)
531
+
532
+ def get_mode(self) -> str:
533
+ """Get current sandbox mode."""
534
+ if self._sandbox:
535
+ return self._sandbox.policy.mode.value
536
+ return SandboxMode.DISABLED.value