@jaguilar87/gaia-ops 4.4.0 → 4.7.2
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +12 -3
- package/ARCHITECTURE.md +9 -8
- package/CHANGELOG.md +34 -0
- package/README.md +43 -11
- package/agents/terraform-architect.md +1 -1
- package/bin/README.md +2 -2
- package/bin/gaia-doctor.js +18 -5
- package/bin/gaia-history.js +0 -1
- package/bin/gaia-metrics.js +2 -2
- package/bin/gaia-scan.py +23 -1
- package/bin/gaia-update.js +346 -54
- package/bin/pre-publish-validate.js +33 -10
- package/commands/gaia.md +37 -0
- package/config/README.md +3 -9
- package/config/context-contracts.json +47 -15
- package/config/surface-routing.json +9 -1
- package/dist/gaia-ops/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
- package/dist/gaia-ops/agents/devops-developer.md +57 -0
- package/dist/gaia-ops/agents/gaia-system.md +58 -0
- package/dist/gaia-ops/agents/gitops-operator.md +60 -0
- package/dist/gaia-ops/agents/speckit-planner.md +71 -0
- package/dist/gaia-ops/agents/terraform-architect.md +60 -0
- package/dist/gaia-ops/commands/gaia.md +37 -0
- package/dist/gaia-ops/config/README.md +58 -0
- package/dist/gaia-ops/config/cloud/aws.json +140 -0
- package/dist/gaia-ops/config/cloud/gcp.json +145 -0
- package/dist/gaia-ops/config/context-contracts.json +131 -0
- package/dist/gaia-ops/config/git_standards.json +72 -0
- package/dist/gaia-ops/config/surface-routing.json +197 -0
- package/dist/gaia-ops/config/universal-rules.json +10 -0
- package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-ops/hooks/adapters/base.py +219 -0
- package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
- package/dist/gaia-ops/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-ops/hooks/adapters/types.py +194 -0
- package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
- package/dist/gaia-ops/hooks/hooks.json +126 -0
- package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
- package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-ops/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-ops/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-ops/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
- package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-ops/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-ops/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-ops/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-ops/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-ops/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-ops/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-ops/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-ops/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-ops/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-ops/hooks/post_compact.py +43 -0
- package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
- package/dist/gaia-ops/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-ops/hooks/session_start.py +69 -0
- package/dist/gaia-ops/hooks/stop_hook.py +69 -0
- package/dist/gaia-ops/hooks/subagent_start.py +71 -0
- package/dist/gaia-ops/hooks/subagent_stop.py +288 -0
- package/dist/gaia-ops/hooks/task_completed.py +70 -0
- package/dist/gaia-ops/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-ops/settings.json +72 -0
- package/dist/gaia-ops/skills/README.md +109 -0
- package/dist/gaia-ops/skills/agent-protocol/SKILL.md +105 -0
- package/dist/gaia-ops/skills/agent-protocol/examples.md +170 -0
- package/dist/gaia-ops/skills/agent-response/SKILL.md +53 -0
- package/dist/gaia-ops/skills/approval/SKILL.md +85 -0
- package/dist/gaia-ops/skills/approval/examples.md +140 -0
- package/dist/gaia-ops/skills/approval/reference.md +57 -0
- package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
- package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
- package/dist/gaia-ops/skills/context-updater/SKILL.md +76 -0
- package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
- package/dist/gaia-ops/skills/developer-patterns/SKILL.md +93 -0
- package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
- package/dist/gaia-ops/skills/execution/SKILL.md +66 -0
- package/dist/gaia-ops/skills/fast-queries/SKILL.md +47 -0
- package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +92 -0
- package/dist/gaia-ops/skills/gaia-patterns/reference.md +22 -0
- package/dist/gaia-ops/skills/git-conventions/SKILL.md +48 -0
- package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +73 -0
- package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
- package/dist/gaia-ops/skills/investigation/SKILL.md +77 -0
- package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +64 -0
- package/dist/gaia-ops/skills/reference.md +134 -0
- package/dist/gaia-ops/skills/security-tiers/SKILL.md +61 -0
- package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
- package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
- package/dist/gaia-ops/skills/skill-creation/SKILL.md +119 -0
- package/dist/gaia-ops/skills/specification/SKILL.md +186 -0
- package/dist/gaia-ops/skills/speckit-workflow/SKILL.md +165 -0
- package/dist/gaia-ops/skills/speckit-workflow/reference.md +117 -0
- package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +63 -0
- package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
- package/dist/gaia-ops/speckit/README.md +516 -0
- package/dist/gaia-ops/speckit/scripts/.gitkeep +0 -0
- package/dist/gaia-ops/speckit/templates/adr-template.md +118 -0
- package/dist/gaia-ops/speckit/templates/agent-file-template.md +23 -0
- package/dist/gaia-ops/speckit/templates/plan-template.md +227 -0
- package/dist/gaia-ops/speckit/templates/spec-template.md +140 -0
- package/dist/gaia-ops/speckit/templates/tasks-template.md +257 -0
- package/dist/gaia-ops/tools/context/README.md +132 -0
- package/dist/gaia-ops/tools/context/__init__.py +42 -0
- package/dist/gaia-ops/tools/context/_paths.py +20 -0
- package/dist/gaia-ops/tools/context/context_provider.py +476 -0
- package/dist/gaia-ops/tools/context/context_section_reader.py +330 -0
- package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
- package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
- package/dist/gaia-ops/tools/context/surface_router.py +278 -0
- package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
- package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
- package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
- package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
- package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
- package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
- package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
- package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
- package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
- package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
- package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
- package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
- package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
- package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +262 -0
- package/dist/gaia-ops/tools/memory/README.md +0 -0
- package/dist/gaia-ops/tools/memory/__init__.py +20 -0
- package/dist/gaia-ops/tools/memory/episodic.py +1196 -0
- package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
- package/dist/gaia-ops/tools/review/__init__.py +1 -0
- package/dist/gaia-ops/tools/review/review_engine.py +157 -0
- package/dist/gaia-ops/tools/scan/__init__.py +35 -0
- package/dist/gaia-ops/tools/scan/config.py +247 -0
- package/dist/gaia-ops/tools/scan/merge.py +212 -0
- package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
- package/dist/gaia-ops/tools/scan/registry.py +127 -0
- package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
- package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
- package/dist/gaia-ops/tools/scan/scanners/environment.py +324 -0
- package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
- package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
- package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
- package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
- package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
- package/dist/gaia-ops/tools/scan/setup.py +753 -0
- package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
- package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
- package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
- package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
- package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
- package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
- package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
- package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
- package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
- package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
- package/dist/gaia-ops/tools/scan/ui.py +624 -0
- package/dist/gaia-ops/tools/scan/verify.py +266 -0
- package/dist/gaia-ops/tools/scan/walk.py +118 -0
- package/dist/gaia-ops/tools/scan/workspace.py +85 -0
- package/dist/gaia-ops/tools/validation/README.md +244 -0
- package/dist/gaia-ops/tools/validation/__init__.py +17 -0
- package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
- package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
- package/dist/gaia-security/.claude-plugin/plugin.json +22 -0
- package/dist/gaia-security/config/universal-rules.json +10 -0
- package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
- package/dist/gaia-security/hooks/adapters/base.py +219 -0
- package/dist/gaia-security/hooks/adapters/channel.py +17 -0
- package/dist/gaia-security/hooks/adapters/claude_code.py +1477 -0
- package/dist/gaia-security/hooks/adapters/types.py +194 -0
- package/dist/gaia-security/hooks/adapters/utils.py +25 -0
- package/dist/gaia-security/hooks/hooks.json +57 -0
- package/dist/gaia-security/hooks/modules/__init__.py +15 -0
- package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
- package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
- package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
- package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +124 -0
- package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
- package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
- package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
- package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
- package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
- package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +576 -0
- package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
- package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
- package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
- package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +215 -0
- package/dist/gaia-security/hooks/modules/context/context_cache.py +129 -0
- package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
- package/dist/gaia-security/hooks/modules/context/context_injector.py +427 -0
- package/dist/gaia-security/hooks/modules/context/context_writer.py +518 -0
- package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
- package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
- package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
- package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
- package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
- package/dist/gaia-security/hooks/modules/core/plugin_setup.py +558 -0
- package/dist/gaia-security/hooks/modules/core/state.py +179 -0
- package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
- package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
- package/dist/gaia-security/hooks/modules/identity/__init__.py +0 -0
- package/dist/gaia-security/hooks/modules/identity/identity_provider.py +21 -0
- package/dist/gaia-security/hooks/modules/identity/ops_identity.py +34 -0
- package/dist/gaia-security/hooks/modules/identity/security_identity.py +10 -0
- package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/memory/episode_writer.py +227 -0
- package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
- package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
- package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
- package/dist/gaia-security/hooks/modules/security/__init__.py +89 -0
- package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
- package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
- package/dist/gaia-security/hooks/modules/security/approval_grants.py +912 -0
- package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
- package/dist/gaia-security/hooks/modules/security/approval_scopes.py +153 -0
- package/dist/gaia-security/hooks/modules/security/blocked_commands.py +584 -0
- package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +86 -0
- package/dist/gaia-security/hooks/modules/security/command_semantics.py +130 -0
- package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
- package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +850 -0
- package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
- package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
- package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
- package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
- package/dist/gaia-security/hooks/modules/session/session_event_injector.py +158 -0
- package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
- package/dist/gaia-security/hooks/modules/tools/__init__.py +25 -0
- package/dist/gaia-security/hooks/modules/tools/bash_validator.py +708 -0
- package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +181 -0
- package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
- package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
- package/dist/gaia-security/hooks/modules/tools/task_validator.py +283 -0
- package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
- package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
- package/dist/gaia-security/hooks/post_tool_use.py +54 -0
- package/dist/gaia-security/hooks/pre_tool_use.py +383 -0
- package/dist/gaia-security/hooks/session_start.py +69 -0
- package/dist/gaia-security/hooks/stop_hook.py +69 -0
- package/dist/gaia-security/hooks/user_prompt_submit.py +177 -0
- package/dist/gaia-security/settings.json +58 -0
- package/git-hooks/commit-msg +41 -0
- package/hooks/README.md +8 -6
- package/hooks/adapters/channel.py +0 -25
- package/hooks/adapters/claude_code.py +364 -125
- package/hooks/elicitation_result.py +132 -0
- package/hooks/hooks.json +10 -1
- package/hooks/modules/README.md +3 -2
- package/hooks/modules/agents/contract_validator.py +3 -51
- package/hooks/modules/agents/response_contract.py +4 -8
- package/hooks/modules/agents/transcript_reader.py +4 -5
- package/hooks/modules/audit/__init__.py +4 -6
- package/hooks/modules/audit/event_detector.py +0 -2
- package/hooks/modules/audit/metrics.py +108 -187
- package/hooks/modules/audit/workflow_auditor.py +0 -4
- package/hooks/modules/audit/workflow_recorder.py +0 -5
- package/hooks/modules/context/compact_context_builder.py +1 -0
- package/hooks/modules/context/context_cache.py +129 -0
- package/hooks/modules/context/context_injector.py +18 -40
- package/hooks/modules/context/context_writer.py +1 -25
- package/hooks/modules/context/contracts_loader.py +7 -10
- package/hooks/modules/core/hook_entry.py +1 -0
- package/hooks/modules/core/paths.py +12 -13
- package/hooks/modules/core/plugin_mode.py +74 -4
- package/hooks/modules/core/plugin_setup.py +395 -23
- package/hooks/modules/events/__init__.py +1 -0
- package/hooks/modules/events/event_writer.py +210 -0
- package/hooks/modules/identity/ops_identity.py +18 -27
- package/hooks/modules/memory/episode_writer.py +1 -6
- package/hooks/modules/orchestrator/__init__.py +1 -0
- package/hooks/modules/orchestrator/delegate_mode.py +128 -0
- package/hooks/modules/security/__init__.py +2 -4
- package/hooks/modules/security/approval_constants.py +5 -1
- package/hooks/modules/security/approval_grants.py +189 -6
- package/hooks/modules/security/approval_messages.py +9 -21
- package/hooks/modules/security/blocked_commands.py +98 -34
- package/hooks/modules/security/command_semantics.py +0 -4
- package/hooks/modules/security/gitops_validator.py +1 -11
- package/hooks/modules/security/mutative_verbs.py +179 -38
- package/hooks/modules/security/tiers.py +1 -19
- package/hooks/modules/session/session_event_injector.py +1 -25
- package/hooks/modules/tools/bash_validator.py +310 -94
- package/hooks/modules/tools/shell_parser.py +0 -1
- package/hooks/modules/tools/task_validator.py +9 -29
- package/hooks/post_tool_use.py +0 -72
- package/hooks/pre_tool_use.py +42 -102
- package/hooks/session_start.py +4 -2
- package/hooks/subagent_start.py +6 -2
- package/hooks/subagent_stop.py +1 -13
- package/hooks/user_prompt_submit.py +119 -37
- package/index.js +1 -1
- package/package.json +5 -3
- package/skills/README.md +3 -5
- package/skills/agent-protocol/SKILL.md +17 -16
- package/skills/agent-protocol/examples.md +6 -6
- package/skills/agent-response/SKILL.md +11 -14
- package/skills/approval/SKILL.md +28 -13
- package/skills/approval/reference.md +2 -2
- package/skills/execution/SKILL.md +1 -1
- package/skills/gaia-patterns/SKILL.md +2 -3
- package/skills/orchestrator-approval/SKILL.md +22 -50
- package/skills/security-tiers/SKILL.md +1 -1
- package/templates/README.md +9 -9
- package/templates/managed-settings.template.json +43 -0
- package/tools/gaia_simulator/runner.py +34 -1
- package/tools/scan/orchestrator.py +13 -0
- package/tools/scan/scanners/base.py +8 -0
- package/tools/scan/scanners/git.py +78 -0
- package/tools/scan/scanners/infrastructure.py +65 -0
- package/tools/scan/scanners/stack.py +110 -0
- package/tools/scan/setup.py +120 -13
- package/tools/scan/workspace.py +85 -0
- package/config/context-contracts.aws.json +0 -42
- package/config/context-contracts.gcp.json +0 -39
- package/skills/project-dispatch/SKILL.md +0 -34
- package/templates/settings.template.json +0 -226
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cloud pipe/redirect/chaining validator.
|
|
3
|
+
|
|
4
|
+
Detects pipe, redirect, and command-chaining violations in cloud/infra commands.
|
|
5
|
+
Cloud CLIs (gcloud, kubectl, aws, terraform, helm, flux) expose native flags
|
|
6
|
+
for filtering and formatting — there is never a valid reason to pipe their output.
|
|
7
|
+
|
|
8
|
+
This validator runs before tier classification so violations are caught early
|
|
9
|
+
and the agent receives a corrective response rather than a blocked execution.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .hook_response import build_hook_permission_response
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# Cloud/infra CLIs covered by this policy
|
|
22
|
+
CLOUD_CLI_PATTERN = re.compile(
|
|
23
|
+
r'^\s*(gcloud|kubectl|aws|terraform|helm|flux)\b',
|
|
24
|
+
re.IGNORECASE
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Violation definitions: (name, regex, corrected_approach)
|
|
28
|
+
VIOLATIONS = [
|
|
29
|
+
(
|
|
30
|
+
"pipe",
|
|
31
|
+
# Match a single pipe `|` but NOT logical OR `||`.
|
|
32
|
+
# Negative lookbehind (?<!\|) skips the second `|` of `||`.
|
|
33
|
+
# Negative lookahead (?!\|) skips the first `|` of `||`.
|
|
34
|
+
re.compile(r'(?<!\|)\|(?!\|)'),
|
|
35
|
+
(
|
|
36
|
+
"Use native output flags instead of piping to shell utilities.\n"
|
|
37
|
+
" gcloud: --filter='...' --format='value(field)'\n"
|
|
38
|
+
" kubectl: -o jsonpath='{...}' or -o go-template='{{...}}'\n"
|
|
39
|
+
" aws: --query '...' --output text\n"
|
|
40
|
+
" terraform: use terraform output or -json flag"
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
(
|
|
44
|
+
"redirect",
|
|
45
|
+
# Match `>` or `>>` for file redirection, but NOT:
|
|
46
|
+
# - `>&` (file descriptor duplication, e.g. `2>&1`)
|
|
47
|
+
# - `--` prefixed flags or other non-redirect uses
|
|
48
|
+
# Negative lookbehind (?<![>&]) avoids matching the `>` in `>&1`.
|
|
49
|
+
# Negative lookahead (?![>&]) avoids matching `>` when followed by `&`.
|
|
50
|
+
re.compile(r'(?<![>&])>{1,2}(?![>&])'),
|
|
51
|
+
(
|
|
52
|
+
"Use the Write tool to write output to a file instead of shell redirection.\n"
|
|
53
|
+
" Write tool: creates or overwrites files cleanly without shell quoting issues.\n"
|
|
54
|
+
" For append patterns, use the Edit tool or Read + Write."
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
"chaining",
|
|
59
|
+
# Match `;` or `&&` but NOT `||` (which is now correctly handled
|
|
60
|
+
# by the pipe regex above via negative lookahead).
|
|
61
|
+
re.compile(r';|&&'),
|
|
62
|
+
(
|
|
63
|
+
"Run each command as a separate, atomic Bash call instead of chaining.\n"
|
|
64
|
+
" One command per step preserves exit-code isolation and avoids\n"
|
|
65
|
+
" interactive prompts mid-chain that block Claude Code execution."
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class PipeViolation:
|
|
73
|
+
"""A detected pipe/redirect/chaining violation."""
|
|
74
|
+
rule: str # e.g. "pipe", "redirect", "chaining"
|
|
75
|
+
pattern: str # the literal character(s) that triggered it
|
|
76
|
+
correction: str # human-readable corrected approach
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _find_violation(command: str) -> Optional[PipeViolation]:
|
|
80
|
+
"""
|
|
81
|
+
Return the first pipe/redirect/chaining violation found in command,
|
|
82
|
+
or None if the command is clean.
|
|
83
|
+
|
|
84
|
+
Only checks commands that start with a cloud/infra CLI.
|
|
85
|
+
Skips characters inside single or double quoted strings to avoid
|
|
86
|
+
false positives (e.g. --filter='status:RUNNING' contains no violation).
|
|
87
|
+
"""
|
|
88
|
+
if not CLOUD_CLI_PATTERN.match(command):
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
# Strip quoted substrings before scanning for operators.
|
|
92
|
+
# This prevents false positives from flag values like --filter='a|b'.
|
|
93
|
+
unquoted = _strip_quoted_sections(command)
|
|
94
|
+
|
|
95
|
+
for rule_name, pattern, correction in VIOLATIONS:
|
|
96
|
+
match = pattern.search(unquoted)
|
|
97
|
+
if match:
|
|
98
|
+
return PipeViolation(
|
|
99
|
+
rule=rule_name,
|
|
100
|
+
pattern=match.group(0),
|
|
101
|
+
correction=correction,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _strip_quoted_sections(text: str) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Replace content inside single and double quotes with spaces.
|
|
110
|
+
Handles simple quoting (no nested quotes, no escape sequences needed
|
|
111
|
+
for the operators we scan for).
|
|
112
|
+
"""
|
|
113
|
+
result = []
|
|
114
|
+
in_single = False
|
|
115
|
+
in_double = False
|
|
116
|
+
|
|
117
|
+
for ch in text:
|
|
118
|
+
if ch == "'" and not in_double:
|
|
119
|
+
in_single = not in_single
|
|
120
|
+
result.append(ch)
|
|
121
|
+
elif ch == '"' and not in_single:
|
|
122
|
+
in_double = not in_double
|
|
123
|
+
result.append(ch)
|
|
124
|
+
elif in_single or in_double:
|
|
125
|
+
result.append(' ') # mask the character
|
|
126
|
+
else:
|
|
127
|
+
result.append(ch)
|
|
128
|
+
|
|
129
|
+
return ''.join(result)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def build_block_response(violation: PipeViolation, command: str) -> dict:
|
|
133
|
+
"""
|
|
134
|
+
Build the structured JSON block that tells Claude Code to block the command
|
|
135
|
+
and return a corrective reason to the agent.
|
|
136
|
+
|
|
137
|
+
Uses permissionDecision: "deny" with exit 0 (NOT exit 2) so the agent
|
|
138
|
+
receives the correction message and adjusts rather than stopping entirely.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
violation: The detected violation.
|
|
142
|
+
command: The original command string (truncated in reason for readability).
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict suitable for json.dumps() and print() in the hook entry point.
|
|
146
|
+
"""
|
|
147
|
+
truncated = command[:120] + ('...' if len(command) > 120 else '')
|
|
148
|
+
|
|
149
|
+
reason = (
|
|
150
|
+
f"Command-execution rule violated: no {violation.rule}s in cloud/infra commands.\n\n"
|
|
151
|
+
f"Violating pattern: '{violation.pattern}' detected in:\n"
|
|
152
|
+
f" {truncated}\n\n"
|
|
153
|
+
f"Corrected approach:\n"
|
|
154
|
+
f"{violation.correction}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return build_hook_permission_response("deny", reason)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def validate_cloud_pipe(command: str) -> Optional[dict]:
|
|
161
|
+
"""
|
|
162
|
+
Check a command for cloud pipe/redirect/chaining violations.
|
|
163
|
+
|
|
164
|
+
Returns a block-response dict if a violation is found, None otherwise.
|
|
165
|
+
The caller should json.dumps() the result and exit(0).
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
command: The raw bash command string.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Block response dict, or None if command is clean.
|
|
172
|
+
"""
|
|
173
|
+
violation = _find_violation(command)
|
|
174
|
+
if violation is None:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
logger.warning(
|
|
178
|
+
f"Cloud pipe violation [{violation.rule}] pattern='{violation.pattern}' "
|
|
179
|
+
f"in: {command[:80]}"
|
|
180
|
+
)
|
|
181
|
+
return build_block_response(violation, command)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared builder for hookSpecificOutput responses.
|
|
3
|
+
|
|
4
|
+
Claude Code hooks communicate permission decisions via a standard JSON
|
|
5
|
+
structure. This module provides a single builder so the three call sites
|
|
6
|
+
(bash_validator allow, bash_validator deny, cloud_pipe_validator deny)
|
|
7
|
+
share the same shape and field names.
|
|
8
|
+
|
|
9
|
+
Internally delegates to the adapter layer's ``format_validation_response``
|
|
10
|
+
for structural consistency, while preserving the original function signature
|
|
11
|
+
so callers (bash_validator.py, cloud_pipe_validator) require zero changes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# Ensure the hooks directory is on sys.path so ``adapters`` resolves.
|
|
20
|
+
_hooks_dir = str(Path(__file__).resolve().parent.parent.parent)
|
|
21
|
+
if _hooks_dir not in sys.path:
|
|
22
|
+
sys.path.insert(0, _hooks_dir)
|
|
23
|
+
|
|
24
|
+
from adapters.claude_code import ClaudeCodeAdapter
|
|
25
|
+
from adapters.types import ValidationResult
|
|
26
|
+
|
|
27
|
+
# Module-level singleton -- lightweight, no I/O in __init__.
|
|
28
|
+
_adapter = ClaudeCodeAdapter()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_hook_permission_response(
|
|
32
|
+
decision: str, reason: str, updated_input: dict | None = None
|
|
33
|
+
) -> dict:
|
|
34
|
+
"""Build a hookSpecificOutput dict for a PreToolUse permission decision.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
decision: "allow", "deny", or "ask".
|
|
38
|
+
reason: Human-readable explanation forwarded to the agent.
|
|
39
|
+
updated_input: Optional modified tool input to pass through for
|
|
40
|
+
"ask" decisions (e.g. footer-stripped command).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dict suitable for ``json.dumps()`` and ``print()`` in the hook
|
|
44
|
+
entry point.
|
|
45
|
+
"""
|
|
46
|
+
if decision == "ask":
|
|
47
|
+
response = _adapter.format_ask_response(reason, updated_input=updated_input)
|
|
48
|
+
return response.output
|
|
49
|
+
|
|
50
|
+
vr = ValidationResult(
|
|
51
|
+
allowed=(decision == "allow"),
|
|
52
|
+
reason=reason,
|
|
53
|
+
)
|
|
54
|
+
response = _adapter.format_validation_response(vr)
|
|
55
|
+
return response.output
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shell Command Parser - Native Python Implementation
|
|
3
|
+
|
|
4
|
+
Parses bash commands into individual components for permission validation.
|
|
5
|
+
This is a workaround for Claude Code bug where settings.json permissions
|
|
6
|
+
are not respected (GitHub Issue #13340).
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Parse piped commands: "cmd1 | cmd2" -> ["cmd1", "cmd2"]
|
|
10
|
+
- Parse chained commands: "cmd1 && cmd2" -> ["cmd1", "cmd2"]
|
|
11
|
+
- Parse semicolon-separated: "cmd1; cmd2" -> ["cmd1", "cmd2"]
|
|
12
|
+
- Handle OR chains: "cmd1 || cmd2" -> ["cmd1", "cmd2"]
|
|
13
|
+
- Preserve quoted strings: echo 'a|b' -> ["echo 'a|b'"]
|
|
14
|
+
|
|
15
|
+
Dependencies: Python stdlib only (shlex, re)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ParsedCommand:
|
|
24
|
+
"""Represents a parsed shell command component."""
|
|
25
|
+
command: str # The actual command string
|
|
26
|
+
operator: Optional[str] = None # The operator that follows (|, &&, ||, ;)
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
return self.command
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ShellCommandParser:
|
|
33
|
+
"""
|
|
34
|
+
Native Python shell command parser.
|
|
35
|
+
|
|
36
|
+
Parses bash commands into individual components while respecting:
|
|
37
|
+
- Single quotes (preserve everything inside)
|
|
38
|
+
- Double quotes (preserve with variable expansion awareness)
|
|
39
|
+
- Escape sequences
|
|
40
|
+
- Subshells
|
|
41
|
+
- Command substitution
|
|
42
|
+
|
|
43
|
+
Zero external dependencies - uses Python stdlib only.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Shell operators that separate commands
|
|
47
|
+
OPERATORS = {
|
|
48
|
+
'|': 'pipe',
|
|
49
|
+
'&&': 'and',
|
|
50
|
+
'||': 'or',
|
|
51
|
+
';': 'sequence',
|
|
52
|
+
'\n': 'newline'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""Initialize parser."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def parse(self, command: str) -> List[str]:
|
|
60
|
+
"""
|
|
61
|
+
Parse shell command into individual components.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
command: Full shell command string
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of individual command strings
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> parser = ShellCommandParser()
|
|
71
|
+
>>> parser.parse("ls | grep foo && wc -l")
|
|
72
|
+
["ls", "grep foo", "wc -l"]
|
|
73
|
+
|
|
74
|
+
>>> parser.parse("echo 'test | grep' | cat")
|
|
75
|
+
["echo 'test | grep'", "cat"]
|
|
76
|
+
"""
|
|
77
|
+
if not command or not command.strip():
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
# Normalize whitespace
|
|
81
|
+
command = command.strip()
|
|
82
|
+
|
|
83
|
+
# Split on operators while preserving quotes
|
|
84
|
+
components = self._split_on_operators(command)
|
|
85
|
+
|
|
86
|
+
# Clean and filter empty components
|
|
87
|
+
result = [comp.strip() for comp in components if comp.strip()]
|
|
88
|
+
|
|
89
|
+
return result
|
|
90
|
+
|
|
91
|
+
def _split_on_operators(self, command: str) -> List[str]:
|
|
92
|
+
"""
|
|
93
|
+
Split command on operators (|, &&, ||, ;) while preserving quoted strings.
|
|
94
|
+
|
|
95
|
+
Uses state machine to track quote context.
|
|
96
|
+
"""
|
|
97
|
+
components = []
|
|
98
|
+
current = []
|
|
99
|
+
i = 0
|
|
100
|
+
|
|
101
|
+
# Quote state
|
|
102
|
+
in_single_quote = False
|
|
103
|
+
in_double_quote = False
|
|
104
|
+
|
|
105
|
+
while i < len(command):
|
|
106
|
+
char = command[i]
|
|
107
|
+
|
|
108
|
+
# Handle escape sequences
|
|
109
|
+
if char == '\\' and i + 1 < len(command):
|
|
110
|
+
current.append(char)
|
|
111
|
+
current.append(command[i + 1])
|
|
112
|
+
i += 2
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
# Handle single quotes
|
|
116
|
+
if char == "'" and not in_double_quote:
|
|
117
|
+
in_single_quote = not in_single_quote
|
|
118
|
+
current.append(char)
|
|
119
|
+
i += 1
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# Handle double quotes
|
|
123
|
+
if char == '"' and not in_single_quote:
|
|
124
|
+
in_double_quote = not in_double_quote
|
|
125
|
+
current.append(char)
|
|
126
|
+
i += 1
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# If inside quotes, add character and continue
|
|
130
|
+
if in_single_quote or in_double_quote:
|
|
131
|
+
current.append(char)
|
|
132
|
+
i += 1
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
# Check for two-character operators (&&, ||)
|
|
136
|
+
if i + 1 < len(command):
|
|
137
|
+
two_char = command[i:i+2]
|
|
138
|
+
if two_char in ['&&', '||']:
|
|
139
|
+
# Found operator - save current component
|
|
140
|
+
if current:
|
|
141
|
+
components.append(''.join(current))
|
|
142
|
+
current = []
|
|
143
|
+
i += 2
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# Check for single-character operators (|, ;)
|
|
147
|
+
if char in ['|', ';', '\n']:
|
|
148
|
+
# Found operator - save current component
|
|
149
|
+
if current:
|
|
150
|
+
components.append(''.join(current))
|
|
151
|
+
current = []
|
|
152
|
+
i += 1
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
# Regular character
|
|
156
|
+
current.append(char)
|
|
157
|
+
i += 1
|
|
158
|
+
|
|
159
|
+
# Add final component
|
|
160
|
+
if current:
|
|
161
|
+
components.append(''.join(current))
|
|
162
|
+
|
|
163
|
+
return components
|
|
164
|
+
|
|
165
|
+
def parse_with_operators(self, command: str) -> List[ParsedCommand]:
|
|
166
|
+
"""
|
|
167
|
+
Parse command and preserve operator information.
|
|
168
|
+
|
|
169
|
+
Useful for understanding command flow (AND vs OR vs pipe).
|
|
170
|
+
"""
|
|
171
|
+
if not command or not command.strip():
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
commands = self.parse(command)
|
|
175
|
+
return [ParsedCommand(command=cmd) for cmd in commands]
|
|
176
|
+
|
|
177
|
+
def contains_operators(self, command: str) -> bool:
|
|
178
|
+
"""Check if command contains shell operators (outside of quotes)."""
|
|
179
|
+
components = self.parse(command)
|
|
180
|
+
return len(components) > 1
|
|
181
|
+
|
|
182
|
+
def is_simple_command(self, command: str) -> bool:
|
|
183
|
+
"""Check if command is a simple command (no operators)."""
|
|
184
|
+
return not self.contains_operators(command)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# Singleton instance for reuse
|
|
188
|
+
_shell_parser: Optional[ShellCommandParser] = None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_shell_parser() -> ShellCommandParser:
|
|
192
|
+
"""
|
|
193
|
+
Get singleton ShellCommandParser instance.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
ShellCommandParser instance
|
|
197
|
+
"""
|
|
198
|
+
global _shell_parser
|
|
199
|
+
if _shell_parser is None:
|
|
200
|
+
_shell_parser = ShellCommandParser()
|
|
201
|
+
return _shell_parser
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def parse_command(command: str) -> List[str]:
|
|
205
|
+
"""
|
|
206
|
+
Parse shell command into components (convenience function).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
command: Shell command string
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
List of command components
|
|
213
|
+
"""
|
|
214
|
+
return get_shell_parser().parse(command)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_compound_command(command: str) -> bool:
|
|
218
|
+
"""
|
|
219
|
+
Check if command is compound (contains operators).
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
command: Shell command string
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
True if compound, False if simple
|
|
226
|
+
"""
|
|
227
|
+
return get_shell_parser().contains_operators(command)
|