PraisonAI 3.0.0__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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- praisonai-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for PraisonAI Code module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .file_utils import (
|
|
6
|
+
add_line_numbers,
|
|
7
|
+
strip_line_numbers,
|
|
8
|
+
every_line_has_line_numbers,
|
|
9
|
+
normalize_line_endings,
|
|
10
|
+
get_file_extension,
|
|
11
|
+
is_binary_file,
|
|
12
|
+
create_directories_for_file,
|
|
13
|
+
file_exists,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .text_utils import (
|
|
17
|
+
normalize_string,
|
|
18
|
+
unescape_html_entities,
|
|
19
|
+
get_similarity,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .ignore_utils import (
|
|
23
|
+
FileAccessController,
|
|
24
|
+
load_gitignore_patterns,
|
|
25
|
+
should_ignore_path,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
# File utilities
|
|
30
|
+
"add_line_numbers",
|
|
31
|
+
"strip_line_numbers",
|
|
32
|
+
"every_line_has_line_numbers",
|
|
33
|
+
"normalize_line_endings",
|
|
34
|
+
"get_file_extension",
|
|
35
|
+
"is_binary_file",
|
|
36
|
+
"create_directories_for_file",
|
|
37
|
+
"file_exists",
|
|
38
|
+
# Text utilities
|
|
39
|
+
"normalize_string",
|
|
40
|
+
"unescape_html_entities",
|
|
41
|
+
"get_similarity",
|
|
42
|
+
# Ignore utilities
|
|
43
|
+
"FileAccessController",
|
|
44
|
+
"load_gitignore_patterns",
|
|
45
|
+
"should_ignore_path",
|
|
46
|
+
]
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File utility functions for PraisonAI Code module.
|
|
3
|
+
|
|
4
|
+
Provides utilities for file operations including:
|
|
5
|
+
- Line number handling (add/strip)
|
|
6
|
+
- File existence and type checking
|
|
7
|
+
- Directory creation
|
|
8
|
+
- Line ending normalization
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from typing import Optional, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_line_numbers(content: str, start_line: int = 1) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Add line numbers to content in the format: " N | content"
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
content: The text content to add line numbers to
|
|
22
|
+
start_line: The starting line number (1-indexed)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Content with line numbers prefixed to each line
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> add_line_numbers("hello\\nworld", 1)
|
|
29
|
+
' 1 | hello\\n 2 | world'
|
|
30
|
+
"""
|
|
31
|
+
if not content:
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
lines = content.split('\n')
|
|
35
|
+
max_line_num = start_line + len(lines) - 1
|
|
36
|
+
width = len(str(max_line_num))
|
|
37
|
+
|
|
38
|
+
numbered_lines = []
|
|
39
|
+
for i, line in enumerate(lines):
|
|
40
|
+
line_num = start_line + i
|
|
41
|
+
numbered_lines.append(f"{line_num:>{width}} | {line}")
|
|
42
|
+
|
|
43
|
+
return '\n'.join(numbered_lines)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def strip_line_numbers(content: str, aggressive: bool = False) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Strip line numbers from content.
|
|
49
|
+
|
|
50
|
+
Handles formats like:
|
|
51
|
+
- " 1 | content"
|
|
52
|
+
- "1| content"
|
|
53
|
+
- " 1\tcontent"
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
content: Content with line numbers
|
|
57
|
+
aggressive: If True, also strip formats like "1:" or just leading numbers
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Content with line numbers removed
|
|
61
|
+
"""
|
|
62
|
+
if not content:
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
lines = content.split('\n')
|
|
66
|
+
stripped_lines = []
|
|
67
|
+
|
|
68
|
+
# Pattern for standard format: optional spaces, digits, optional spaces, pipe/tab, content
|
|
69
|
+
standard_pattern = re.compile(r'^\s*\d+\s*[|\t]\s?(.*)$')
|
|
70
|
+
# Aggressive pattern: digits followed by colon or just leading digits with space
|
|
71
|
+
aggressive_pattern = re.compile(r'^\s*\d+[:\s]\s?(.*)$')
|
|
72
|
+
|
|
73
|
+
for line in lines:
|
|
74
|
+
match = standard_pattern.match(line)
|
|
75
|
+
if match:
|
|
76
|
+
stripped_lines.append(match.group(1))
|
|
77
|
+
elif aggressive:
|
|
78
|
+
match = aggressive_pattern.match(line)
|
|
79
|
+
if match:
|
|
80
|
+
stripped_lines.append(match.group(1))
|
|
81
|
+
else:
|
|
82
|
+
stripped_lines.append(line)
|
|
83
|
+
else:
|
|
84
|
+
stripped_lines.append(line)
|
|
85
|
+
|
|
86
|
+
return '\n'.join(stripped_lines)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def every_line_has_line_numbers(content: str) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Check if every non-empty line in content has line numbers.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
content: The content to check
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
True if all non-empty lines have line numbers
|
|
98
|
+
"""
|
|
99
|
+
if not content or not content.strip():
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
lines = content.split('\n')
|
|
103
|
+
pattern = re.compile(r'^\s*\d+\s*[|\t]')
|
|
104
|
+
|
|
105
|
+
for line in lines:
|
|
106
|
+
if line.strip(): # Only check non-empty lines
|
|
107
|
+
if not pattern.match(line):
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def normalize_line_endings(content: str, target: str = '\n') -> str:
|
|
114
|
+
"""
|
|
115
|
+
Normalize line endings to a consistent format.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
content: The content to normalize
|
|
119
|
+
target: The target line ending ('\\n' or '\\r\\n')
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Content with normalized line endings
|
|
123
|
+
"""
|
|
124
|
+
# First normalize all to \n, then convert to target
|
|
125
|
+
normalized = content.replace('\r\n', '\n').replace('\r', '\n')
|
|
126
|
+
if target == '\r\n':
|
|
127
|
+
normalized = normalized.replace('\n', '\r\n')
|
|
128
|
+
return normalized
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def detect_line_ending(content: str) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Detect the predominant line ending in content.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
content: The content to analyze
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
'\\r\\n' if Windows-style, '\\n' otherwise
|
|
140
|
+
"""
|
|
141
|
+
if '\r\n' in content:
|
|
142
|
+
return '\r\n'
|
|
143
|
+
return '\n'
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_file_extension(file_path: str) -> str:
|
|
147
|
+
"""
|
|
148
|
+
Get the file extension from a path.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
file_path: Path to the file
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
File extension without the dot, or empty string
|
|
155
|
+
"""
|
|
156
|
+
ext = os.path.splitext(file_path)[1]
|
|
157
|
+
return ext[1:] if ext else ""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def is_binary_file(file_path: str, sample_size: int = 8192) -> bool:
|
|
161
|
+
"""
|
|
162
|
+
Check if a file is binary by reading a sample.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
file_path: Path to the file
|
|
166
|
+
sample_size: Number of bytes to sample
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
True if the file appears to be binary
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
with open(file_path, 'rb') as f:
|
|
173
|
+
sample = f.read(sample_size)
|
|
174
|
+
|
|
175
|
+
# Check for null bytes (common in binary files)
|
|
176
|
+
if b'\x00' in sample:
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
# Check the ratio of non-text characters
|
|
180
|
+
text_chars = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7f})
|
|
181
|
+
non_text = sum(1 for byte in sample if byte not in text_chars)
|
|
182
|
+
|
|
183
|
+
# If more than 30% non-text characters, consider it binary
|
|
184
|
+
return len(sample) > 0 and (non_text / len(sample)) > 0.30
|
|
185
|
+
|
|
186
|
+
except (IOError, OSError):
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def create_directories_for_file(file_path: str) -> bool:
|
|
191
|
+
"""
|
|
192
|
+
Create parent directories for a file path if they don't exist.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
file_path: Path to the file
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True if directories were created or already exist
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
parent_dir = os.path.dirname(file_path)
|
|
202
|
+
if parent_dir:
|
|
203
|
+
os.makedirs(parent_dir, exist_ok=True)
|
|
204
|
+
return True
|
|
205
|
+
except OSError:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def file_exists(file_path: str) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Check if a file exists.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
file_path: Path to the file
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
True if the file exists and is a file (not directory)
|
|
218
|
+
"""
|
|
219
|
+
return os.path.isfile(file_path)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_relative_path(file_path: str, base_path: str) -> str:
|
|
223
|
+
"""
|
|
224
|
+
Get the relative path from base_path to file_path.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
file_path: The target file path
|
|
228
|
+
base_path: The base directory path
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Relative path string
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
return os.path.relpath(file_path, base_path)
|
|
235
|
+
except ValueError:
|
|
236
|
+
# On Windows, relpath fails for paths on different drives
|
|
237
|
+
return file_path
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def is_path_within_directory(file_path: str, directory: str) -> bool:
|
|
241
|
+
"""
|
|
242
|
+
Check if a file path is within a directory (prevents path traversal).
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
file_path: The file path to check
|
|
246
|
+
directory: The directory that should contain the file
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if file_path is within directory
|
|
250
|
+
"""
|
|
251
|
+
# Resolve to absolute paths
|
|
252
|
+
abs_file = os.path.abspath(file_path)
|
|
253
|
+
abs_dir = os.path.abspath(directory)
|
|
254
|
+
|
|
255
|
+
# Ensure directory ends with separator for proper prefix matching
|
|
256
|
+
if not abs_dir.endswith(os.sep):
|
|
257
|
+
abs_dir += os.sep
|
|
258
|
+
|
|
259
|
+
return abs_file.startswith(abs_dir) or abs_file == abs_dir.rstrip(os.sep)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def read_file_lines(file_path: str, start_line: int = 1, end_line: Optional[int] = None) -> Tuple[str, int]:
|
|
263
|
+
"""
|
|
264
|
+
Read specific lines from a file.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
file_path: Path to the file
|
|
268
|
+
start_line: First line to read (1-indexed)
|
|
269
|
+
end_line: Last line to read (inclusive), None for all remaining
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Tuple of (content, total_lines)
|
|
273
|
+
"""
|
|
274
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
275
|
+
lines = f.readlines()
|
|
276
|
+
|
|
277
|
+
total_lines = len(lines)
|
|
278
|
+
|
|
279
|
+
# Convert to 0-indexed
|
|
280
|
+
start_idx = max(0, start_line - 1)
|
|
281
|
+
end_idx = end_line if end_line else total_lines
|
|
282
|
+
|
|
283
|
+
selected_lines = lines[start_idx:end_idx]
|
|
284
|
+
content = ''.join(selected_lines)
|
|
285
|
+
|
|
286
|
+
# Remove trailing newline if present
|
|
287
|
+
if content.endswith('\n'):
|
|
288
|
+
content = content[:-1]
|
|
289
|
+
|
|
290
|
+
return content, total_lines
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def count_file_lines(file_path: str) -> int:
|
|
294
|
+
"""
|
|
295
|
+
Count the number of lines in a file efficiently.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
file_path: Path to the file
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Number of lines in the file
|
|
302
|
+
"""
|
|
303
|
+
count = 0
|
|
304
|
+
with open(file_path, 'rb') as f:
|
|
305
|
+
for _ in f:
|
|
306
|
+
count += 1
|
|
307
|
+
return count
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File access control utilities for PraisonAI Code module.
|
|
3
|
+
|
|
4
|
+
Provides utilities for:
|
|
5
|
+
- Loading and parsing .gitignore patterns
|
|
6
|
+
- Checking if paths should be ignored
|
|
7
|
+
- Controlling file access within workspaces
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import fnmatch
|
|
12
|
+
from typing import List, Optional, Set
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_gitignore_patterns(directory: str) -> List[str]:
|
|
16
|
+
"""
|
|
17
|
+
Load .gitignore patterns from a directory.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
directory: The directory to search for .gitignore
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of gitignore patterns
|
|
24
|
+
"""
|
|
25
|
+
patterns = []
|
|
26
|
+
gitignore_path = os.path.join(directory, '.gitignore')
|
|
27
|
+
|
|
28
|
+
if os.path.isfile(gitignore_path):
|
|
29
|
+
try:
|
|
30
|
+
with open(gitignore_path, 'r', encoding='utf-8') as f:
|
|
31
|
+
for line in f:
|
|
32
|
+
line = line.strip()
|
|
33
|
+
# Skip empty lines and comments
|
|
34
|
+
if line and not line.startswith('#'):
|
|
35
|
+
patterns.append(line)
|
|
36
|
+
except (IOError, OSError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
return patterns
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _pattern_to_regex_parts(pattern: str) -> tuple:
|
|
43
|
+
"""
|
|
44
|
+
Convert a gitignore pattern to matching components.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
pattern: A gitignore pattern
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple of (is_negation, is_directory_only, cleaned_pattern)
|
|
51
|
+
"""
|
|
52
|
+
is_negation = pattern.startswith('!')
|
|
53
|
+
if is_negation:
|
|
54
|
+
pattern = pattern[1:]
|
|
55
|
+
|
|
56
|
+
is_directory_only = pattern.endswith('/')
|
|
57
|
+
if is_directory_only:
|
|
58
|
+
pattern = pattern[:-1]
|
|
59
|
+
|
|
60
|
+
return is_negation, is_directory_only, pattern
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def should_ignore_path(
|
|
64
|
+
path: str,
|
|
65
|
+
patterns: List[str],
|
|
66
|
+
base_dir: str,
|
|
67
|
+
is_directory: bool = False
|
|
68
|
+
) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Check if a path should be ignored based on gitignore patterns.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
path: The path to check (relative or absolute)
|
|
74
|
+
patterns: List of gitignore patterns
|
|
75
|
+
base_dir: The base directory for relative path calculation
|
|
76
|
+
is_directory: Whether the path is a directory
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if the path should be ignored
|
|
80
|
+
"""
|
|
81
|
+
# Get relative path
|
|
82
|
+
if os.path.isabs(path):
|
|
83
|
+
try:
|
|
84
|
+
rel_path = os.path.relpath(path, base_dir)
|
|
85
|
+
except ValueError:
|
|
86
|
+
rel_path = path
|
|
87
|
+
else:
|
|
88
|
+
rel_path = path
|
|
89
|
+
|
|
90
|
+
# Normalize path separators
|
|
91
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
92
|
+
|
|
93
|
+
# Check each pattern
|
|
94
|
+
ignored = False
|
|
95
|
+
|
|
96
|
+
for pattern in patterns:
|
|
97
|
+
is_negation, is_directory_only, clean_pattern = _pattern_to_regex_parts(pattern)
|
|
98
|
+
|
|
99
|
+
# Skip directory-only patterns for files
|
|
100
|
+
if is_directory_only and not is_directory:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Check if pattern matches
|
|
104
|
+
matches = False
|
|
105
|
+
|
|
106
|
+
# Handle patterns with /
|
|
107
|
+
if '/' in clean_pattern:
|
|
108
|
+
# Pattern with / matches from root
|
|
109
|
+
if clean_pattern.startswith('/'):
|
|
110
|
+
clean_pattern = clean_pattern[1:]
|
|
111
|
+
matches = fnmatch.fnmatch(rel_path, clean_pattern)
|
|
112
|
+
else:
|
|
113
|
+
# Pattern without / matches any path component
|
|
114
|
+
path_parts = rel_path.split('/')
|
|
115
|
+
for part in path_parts:
|
|
116
|
+
if fnmatch.fnmatch(part, clean_pattern):
|
|
117
|
+
matches = True
|
|
118
|
+
break
|
|
119
|
+
# Also check full path for ** patterns
|
|
120
|
+
if not matches and '**' in clean_pattern:
|
|
121
|
+
matches = fnmatch.fnmatch(rel_path, clean_pattern)
|
|
122
|
+
|
|
123
|
+
if matches:
|
|
124
|
+
ignored = not is_negation
|
|
125
|
+
|
|
126
|
+
return ignored
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FileAccessController:
|
|
130
|
+
"""
|
|
131
|
+
Controls file access within a workspace.
|
|
132
|
+
|
|
133
|
+
Respects .gitignore patterns and can be configured with
|
|
134
|
+
additional ignore/allow patterns.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
workspace_root: The root directory of the workspace
|
|
138
|
+
ignore_patterns: Patterns for files to ignore
|
|
139
|
+
protected_patterns: Patterns for files that are read-only
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
workspace_root: str,
|
|
145
|
+
load_gitignore: bool = True,
|
|
146
|
+
additional_ignore_patterns: Optional[List[str]] = None,
|
|
147
|
+
protected_patterns: Optional[List[str]] = None
|
|
148
|
+
):
|
|
149
|
+
"""
|
|
150
|
+
Initialize the FileAccessController.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
workspace_root: The root directory of the workspace
|
|
154
|
+
load_gitignore: Whether to load .gitignore patterns
|
|
155
|
+
additional_ignore_patterns: Extra patterns to ignore
|
|
156
|
+
protected_patterns: Patterns for read-only files
|
|
157
|
+
"""
|
|
158
|
+
self.workspace_root = os.path.abspath(workspace_root)
|
|
159
|
+
self.ignore_patterns: List[str] = []
|
|
160
|
+
self.protected_patterns: List[str] = protected_patterns or []
|
|
161
|
+
self._ignored_cache: Set[str] = set()
|
|
162
|
+
|
|
163
|
+
# Load .gitignore patterns
|
|
164
|
+
if load_gitignore:
|
|
165
|
+
self.ignore_patterns.extend(load_gitignore_patterns(self.workspace_root))
|
|
166
|
+
|
|
167
|
+
# Add additional patterns
|
|
168
|
+
if additional_ignore_patterns:
|
|
169
|
+
self.ignore_patterns.extend(additional_ignore_patterns)
|
|
170
|
+
|
|
171
|
+
# Always ignore common patterns
|
|
172
|
+
default_ignores = [
|
|
173
|
+
'.git',
|
|
174
|
+
'.git/**',
|
|
175
|
+
'__pycache__',
|
|
176
|
+
'__pycache__/**',
|
|
177
|
+
'*.pyc',
|
|
178
|
+
'.DS_Store',
|
|
179
|
+
'node_modules',
|
|
180
|
+
'node_modules/**',
|
|
181
|
+
]
|
|
182
|
+
for pattern in default_ignores:
|
|
183
|
+
if pattern not in self.ignore_patterns:
|
|
184
|
+
self.ignore_patterns.append(pattern)
|
|
185
|
+
|
|
186
|
+
def is_path_allowed(self, path: str) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Check if a path is allowed for access.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
path: The path to check
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if the path is allowed
|
|
195
|
+
"""
|
|
196
|
+
# Resolve to absolute path
|
|
197
|
+
if not os.path.isabs(path):
|
|
198
|
+
abs_path = os.path.abspath(os.path.join(self.workspace_root, path))
|
|
199
|
+
else:
|
|
200
|
+
abs_path = os.path.abspath(path)
|
|
201
|
+
|
|
202
|
+
# Check if within workspace
|
|
203
|
+
if not self._is_within_workspace(abs_path):
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
# Check if ignored
|
|
207
|
+
is_dir = os.path.isdir(abs_path)
|
|
208
|
+
if should_ignore_path(abs_path, self.ignore_patterns, self.workspace_root, is_dir):
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
return True
|
|
212
|
+
|
|
213
|
+
def is_write_protected(self, path: str) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Check if a path is write-protected.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
path: The path to check
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if the path is write-protected
|
|
222
|
+
"""
|
|
223
|
+
if not self.protected_patterns:
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
# Get relative path
|
|
227
|
+
if os.path.isabs(path):
|
|
228
|
+
try:
|
|
229
|
+
rel_path = os.path.relpath(path, self.workspace_root)
|
|
230
|
+
except ValueError:
|
|
231
|
+
rel_path = path
|
|
232
|
+
else:
|
|
233
|
+
rel_path = path
|
|
234
|
+
|
|
235
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
236
|
+
|
|
237
|
+
for pattern in self.protected_patterns:
|
|
238
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
def validate_access(self, path: str, write: bool = False) -> bool:
|
|
244
|
+
"""
|
|
245
|
+
Validate if access to a path is allowed.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
path: The path to validate
|
|
249
|
+
write: Whether write access is needed
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
True if access is allowed
|
|
253
|
+
"""
|
|
254
|
+
if not self.is_path_allowed(path):
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
if write and self.is_write_protected(path):
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
def _is_within_workspace(self, abs_path: str) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Check if an absolute path is within the workspace.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
abs_path: The absolute path to check
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
True if within workspace
|
|
271
|
+
"""
|
|
272
|
+
# Normalize paths
|
|
273
|
+
workspace = os.path.normpath(self.workspace_root)
|
|
274
|
+
target = os.path.normpath(abs_path)
|
|
275
|
+
|
|
276
|
+
# Check if target starts with workspace path
|
|
277
|
+
return target.startswith(workspace + os.sep) or target == workspace
|
|
278
|
+
|
|
279
|
+
def get_relative_path(self, path: str) -> str:
|
|
280
|
+
"""
|
|
281
|
+
Get the relative path from workspace root.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
path: The path to convert
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Relative path string
|
|
288
|
+
"""
|
|
289
|
+
if os.path.isabs(path):
|
|
290
|
+
try:
|
|
291
|
+
return os.path.relpath(path, self.workspace_root)
|
|
292
|
+
except ValueError:
|
|
293
|
+
return path
|
|
294
|
+
return path
|
|
295
|
+
|
|
296
|
+
def get_absolute_path(self, path: str) -> str:
|
|
297
|
+
"""
|
|
298
|
+
Get the absolute path for a relative path.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
path: The relative path
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Absolute path string
|
|
305
|
+
"""
|
|
306
|
+
if os.path.isabs(path):
|
|
307
|
+
return path
|
|
308
|
+
return os.path.abspath(os.path.join(self.workspace_root, path))
|