cycode 3.15.3.dev8__tar.gz → 3.15.4.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/PKG-INFO +1 -1
- cycode-3.15.4.dev1/cycode/__init__.py +1 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/command_utils.py +25 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/consts.py +23 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/hooks_manager.py +51 -138
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/ides/__init__.py +44 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/ides/base.py +156 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/ides/claude_code.py +389 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/ides/cursor.py +119 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/install_command.py +14 -23
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/scan/handlers.py +46 -89
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/scan/payload.py +34 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/scan/scan_command.py +154 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/scan/types.py +43 -0
- cycode-3.15.4.dev1/cycode/cli/apps/ai_guardrails/session_start_command.py +91 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/status_command.py +13 -16
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/uninstall_command.py +12 -22
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/pyproject.toml +1 -1
- cycode-3.15.3.dev8/cycode/__init__.py +0 -1
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/command_utils.py +0 -68
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/consts.py +0 -155
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/claude_config.py +0 -159
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/cursor_config.py +0 -36
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -275
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/response_builders.py +0 -135
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -142
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/scan/types.py +0 -65
- cycode-3.15.3.dev8/cycode/cli/apps/ai_guardrails/session_start_command.py +0 -155
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/LICENCE +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/README.md +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/__main__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/app.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/activation_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/api/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/api/api_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/api/openapi_spec.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/auth/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/auth/auth_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/auth/auth_common.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/auth/auth_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/auth/models.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/configure/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/configure/configure_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/configure/consts.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/configure/messages.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/configure/prompts.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ignore/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/ignore/ignore_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/mcp/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/mcp/mcp_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/report_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/common.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report_import/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report_import/report_import_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/sca_options.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/aggregation_report.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/code_scanner.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/detection_excluder.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/path/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/path/path_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/repository/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_parameters.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/scan/scan_result.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/status/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/status/get_cli_status.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/status/models.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/status/status_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/apps/status/version_command.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/cli_types.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/config.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/console.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/consts.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/custom_exceptions.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/handle_errors.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/commit_range_documents.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/file_excluder.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/iac/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/models/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/path_documents.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/repository_documents.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/walk_ignore.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/files_collector/zip_documents.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/logger.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/main.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/models.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/console_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/json_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/printer_base.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/rich_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/table.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/table_models.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/table_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/tables/table_printer_base.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/text_printer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/detection_data.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/printers/utils/rich_helpers.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/base_file_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/config_file_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/configuration_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/credentials_manager.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/user_settings/jwt_creator.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/binary_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/enum_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/get_api_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/git_proxy.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/ignore_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/jwt_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/path_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/progress_bar.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/scan_batch.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/scan_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/shell_executor.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/string_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/task_timer.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/url_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/version_checker.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cli/utils/yaml_utils.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/config.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/__init__.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/ai_security_manager_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/auth_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/base_token_auth_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cli_activation_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/client_creator.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/config.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/config_dev.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cycode_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cycode_client_base.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cycode_dev_based_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/cycode_token_based_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/headers.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/import_sbom_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/logger.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/models.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/report_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/scan_client.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/cyclient/scan_config_base.py +0 -0
- {cycode-3.15.3.dev8 → cycode-3.15.4.dev1}/cycode/logger.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '3.15.4.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Common utilities for AI guardrails commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_scope(scope: str, allowed_scopes: tuple[str, ...] = ('user', 'repo')) -> None:
|
|
14
|
+
"""Validate scope parameter."""
|
|
15
|
+
if scope not in allowed_scopes:
|
|
16
|
+
scopes_list = ', '.join(f'"{s}"' for s in allowed_scopes)
|
|
17
|
+
console.print(f'[red]Error:[/] Invalid scope. Use {scopes_list}.', style='bold red')
|
|
18
|
+
raise typer.Exit(1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_repo_path(scope: str, repo_path: Optional[Path]) -> Optional[Path]:
|
|
22
|
+
"""Default repo_path to cwd for 'repo' scope; leave None for 'user' scope."""
|
|
23
|
+
if scope == 'repo' and repo_path is None:
|
|
24
|
+
return Path(os.getcwd())
|
|
25
|
+
return repo_path
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Shared constants and policy/mode enums for AI guardrails."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PolicyMode(str, Enum):
|
|
7
|
+
"""Policy enforcement mode for global mode and per-feature actions."""
|
|
8
|
+
|
|
9
|
+
BLOCK = 'block'
|
|
10
|
+
WARN = 'warn'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InstallMode(str, Enum):
|
|
14
|
+
"""Installation mode for ai-guardrails install command."""
|
|
15
|
+
|
|
16
|
+
REPORT = 'report'
|
|
17
|
+
BLOCK = 'block'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Base CLI commands invoked from installed hooks. IDE classes append --ide flags
|
|
21
|
+
# (and any other suffix) on top of these.
|
|
22
|
+
CYCODE_SCAN_PROMPT_COMMAND = 'cycode ai-guardrails scan'
|
|
23
|
+
CYCODE_SESSION_START_COMMAND = 'cycode ai-guardrails session-start'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Hooks manager for AI guardrails.
|
|
1
|
+
"""Hooks manager for AI guardrails.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Generic install/uninstall/status logic. All IDE-specific concerns (settings
|
|
4
|
+
paths, hooks template shape) live on the `IDE` instance; this module is
|
|
5
|
+
agent-agnostic.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
@@ -12,47 +12,45 @@ from typing import Optional
|
|
|
12
12
|
|
|
13
13
|
import yaml
|
|
14
14
|
|
|
15
|
-
from cycode.cli.apps.ai_guardrails.consts import
|
|
16
|
-
|
|
17
|
-
IDE_CONFIGS,
|
|
18
|
-
AIIDEType,
|
|
19
|
-
PolicyMode,
|
|
20
|
-
get_hooks_config,
|
|
21
|
-
)
|
|
15
|
+
from cycode.cli.apps.ai_guardrails.consts import PolicyMode
|
|
16
|
+
from cycode.cli.apps.ai_guardrails.ides.base import IDE
|
|
22
17
|
from cycode.cli.apps.ai_guardrails.scan.consts import DEFAULT_POLICY, POLICY_FILE_NAME
|
|
23
18
|
from cycode.logger import get_logger
|
|
24
19
|
|
|
25
20
|
logger = get_logger('AI Guardrails Hooks')
|
|
26
21
|
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
_CYCODE_COMMAND_MARKERS = ('cycode ai-guardrails',)
|
|
24
|
+
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
def _is_cycode_command(command: str) -> bool:
|
|
27
|
+
return any(marker in command for marker in _CYCODE_COMMAND_MARKERS)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_cycode_hook_entry(entry: dict) -> bool:
|
|
31
|
+
"""Detect Cycode hook entries in both Cursor (flat) and Claude Code (nested) shapes."""
|
|
32
|
+
command = entry.get('command', '')
|
|
33
|
+
if _is_cycode_command(command):
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
for hook in entry.get('hooks', []):
|
|
37
|
+
if isinstance(hook, dict) and _is_cycode_command(hook.get('command', '')):
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
return False
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def
|
|
43
|
-
"""Load existing hooks.json file."""
|
|
43
|
+
def _load_hooks_file(hooks_path: Path) -> Optional[dict]:
|
|
44
44
|
if not hooks_path.exists():
|
|
45
45
|
return None
|
|
46
46
|
try:
|
|
47
|
-
|
|
48
|
-
return json.loads(content)
|
|
47
|
+
return json.loads(hooks_path.read_text(encoding='utf-8'))
|
|
49
48
|
except Exception as e:
|
|
50
49
|
logger.debug('Failed to load hooks file', exc_info=e)
|
|
51
50
|
return None
|
|
52
51
|
|
|
53
52
|
|
|
54
|
-
def
|
|
55
|
-
"""Save hooks.json file."""
|
|
53
|
+
def _save_hooks_file(hooks_path: Path, hooks_config: dict) -> bool:
|
|
56
54
|
try:
|
|
57
55
|
hooks_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
56
|
hooks_path.write_text(json.dumps(hooks_config, indent=2), encoding='utf-8')
|
|
@@ -62,39 +60,7 @@ def save_hooks_file(hooks_path: Path, hooks_config: dict) -> bool:
|
|
|
62
60
|
return False
|
|
63
61
|
|
|
64
62
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _is_cycode_command(command: str) -> bool:
|
|
69
|
-
return any(marker in command for marker in _CYCODE_COMMAND_MARKERS)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def is_cycode_hook_entry(entry: dict) -> bool:
|
|
73
|
-
"""Check if a hook entry is from cycode-cli.
|
|
74
|
-
|
|
75
|
-
Handles both Cursor format (flat) and Claude Code format (nested).
|
|
76
|
-
|
|
77
|
-
Cursor format: {"command": "cycode ai-guardrails scan"}
|
|
78
|
-
Claude Code format: {"hooks": [{"type": "command", "command": "cycode ai-guardrails scan --ide claude-code"}]}
|
|
79
|
-
"""
|
|
80
|
-
# Check Cursor format (flat command)
|
|
81
|
-
command = entry.get('command', '')
|
|
82
|
-
if _is_cycode_command(command):
|
|
83
|
-
return True
|
|
84
|
-
|
|
85
|
-
# Check Claude Code format (nested hooks array)
|
|
86
|
-
hooks = entry.get('hooks', [])
|
|
87
|
-
for hook in hooks:
|
|
88
|
-
if isinstance(hook, dict):
|
|
89
|
-
hook_command = hook.get('command', '')
|
|
90
|
-
if _is_cycode_command(hook_command):
|
|
91
|
-
return True
|
|
92
|
-
|
|
93
|
-
return False
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def _load_policy(policy_path: Path) -> dict:
|
|
97
|
-
"""Load existing policy file merged with defaults, or return defaults if not found."""
|
|
63
|
+
def _load_policy_dict(policy_path: Path) -> dict:
|
|
98
64
|
if not policy_path.exists():
|
|
99
65
|
return copy.deepcopy(DEFAULT_POLICY)
|
|
100
66
|
try:
|
|
@@ -107,22 +73,13 @@ def _load_policy(policy_path: Path) -> dict:
|
|
|
107
73
|
def create_policy_file(scope: str, mode: PolicyMode, repo_path: Optional[Path] = None) -> tuple[bool, str]:
|
|
108
74
|
"""Create or update the ai-guardrails.yaml policy file.
|
|
109
75
|
|
|
110
|
-
If the file already exists, only the mode field is updated
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
scope: 'user' for user-level, 'repo' for repository-level
|
|
115
|
-
mode: The policy mode to set
|
|
116
|
-
repo_path: Repository path (required if scope is 'repo')
|
|
117
|
-
|
|
118
|
-
Returns:
|
|
119
|
-
Tuple of (success, message)
|
|
76
|
+
If the file already exists, only the mode field is updated; otherwise a new
|
|
77
|
+
file is created from the default policy.
|
|
120
78
|
"""
|
|
121
79
|
config_dir = repo_path / '.cycode' if scope == 'repo' and repo_path else Path.home() / '.cycode'
|
|
122
80
|
policy_path = config_dir / POLICY_FILE_NAME
|
|
123
81
|
|
|
124
|
-
policy =
|
|
125
|
-
|
|
82
|
+
policy = _load_policy_dict(policy_path)
|
|
126
83
|
policy['mode'] = mode.value
|
|
127
84
|
|
|
128
85
|
try:
|
|
@@ -135,35 +92,21 @@ def create_policy_file(scope: str, mode: PolicyMode, repo_path: Optional[Path] =
|
|
|
135
92
|
|
|
136
93
|
|
|
137
94
|
def install_hooks(
|
|
95
|
+
ide: IDE,
|
|
138
96
|
scope: str = 'user',
|
|
139
97
|
repo_path: Optional[Path] = None,
|
|
140
|
-
ide: AIIDEType = DEFAULT_IDE,
|
|
141
98
|
report_mode: bool = False,
|
|
142
99
|
) -> tuple[bool, str]:
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
148
|
-
repo_path: Repository path (required if scope is 'repo')
|
|
149
|
-
ide: The AI IDE type (default: Cursor)
|
|
150
|
-
report_mode: If True, install hooks in async mode (non-blocking)
|
|
100
|
+
"""Install Cycode AI guardrails hooks for ``ide``."""
|
|
101
|
+
hooks_path = ide.settings_path(scope, repo_path)
|
|
151
102
|
|
|
152
|
-
|
|
153
|
-
Tuple of (success, message)
|
|
154
|
-
"""
|
|
155
|
-
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
156
|
-
|
|
157
|
-
# Load existing hooks or create new
|
|
158
|
-
existing = load_hooks_file(hooks_path) or {'version': 1, 'hooks': {}}
|
|
103
|
+
existing = _load_hooks_file(hooks_path) or {'version': 1, 'hooks': {}}
|
|
159
104
|
existing.setdefault('version', 1)
|
|
160
105
|
existing.setdefault('hooks', {})
|
|
161
106
|
|
|
162
|
-
|
|
163
|
-
hooks_config = get_hooks_config(ide, async_mode=report_mode)
|
|
107
|
+
rendered = ide.render_hooks_config(async_mode=report_mode)
|
|
164
108
|
|
|
165
|
-
|
|
166
|
-
for event, entries in hooks_config['hooks'].items():
|
|
109
|
+
for event, entries in rendered['hooks'].items():
|
|
167
110
|
existing['hooks'].setdefault(event, [])
|
|
168
111
|
|
|
169
112
|
# Remove any existing Cycode entries for this event
|
|
@@ -173,47 +116,31 @@ def install_hooks(
|
|
|
173
116
|
for entry in entries:
|
|
174
117
|
existing['hooks'][event].append(entry)
|
|
175
118
|
|
|
176
|
-
|
|
177
|
-
if save_hooks_file(hooks_path, existing):
|
|
119
|
+
if _save_hooks_file(hooks_path, existing):
|
|
178
120
|
return True, f'AI guardrails hooks installed: {hooks_path}'
|
|
179
121
|
return False, f'Failed to install hooks to {hooks_path}'
|
|
180
122
|
|
|
181
123
|
|
|
182
|
-
def uninstall_hooks(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"""
|
|
186
|
-
Remove Cycode AI guardrails hooks.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
190
|
-
repo_path: Repository path (required if scope is 'repo')
|
|
191
|
-
ide: The AI IDE type (default: Cursor)
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
Tuple of (success, message)
|
|
195
|
-
"""
|
|
196
|
-
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
124
|
+
def uninstall_hooks(ide: IDE, scope: str = 'user', repo_path: Optional[Path] = None) -> tuple[bool, str]:
|
|
125
|
+
"""Remove Cycode AI guardrails hooks for ``ide``."""
|
|
126
|
+
hooks_path = ide.settings_path(scope, repo_path)
|
|
197
127
|
|
|
198
|
-
existing =
|
|
128
|
+
existing = _load_hooks_file(hooks_path)
|
|
199
129
|
if existing is None:
|
|
200
130
|
return True, f'No hooks file found at {hooks_path}'
|
|
201
131
|
|
|
202
|
-
# Remove Cycode entries from all events
|
|
203
132
|
modified = False
|
|
204
133
|
for event in list(existing.get('hooks', {}).keys()):
|
|
205
134
|
original_count = len(existing['hooks'][event])
|
|
206
135
|
existing['hooks'][event] = [e for e in existing['hooks'][event] if not is_cycode_hook_entry(e)]
|
|
207
136
|
if len(existing['hooks'][event]) != original_count:
|
|
208
137
|
modified = True
|
|
209
|
-
# Remove empty event lists
|
|
210
138
|
if not existing['hooks'][event]:
|
|
211
139
|
del existing['hooks'][event]
|
|
212
140
|
|
|
213
141
|
if not modified:
|
|
214
142
|
return True, 'No Cycode hooks found to remove'
|
|
215
143
|
|
|
216
|
-
# Save or delete if empty
|
|
217
144
|
if not existing.get('hooks'):
|
|
218
145
|
try:
|
|
219
146
|
hooks_path.unlink()
|
|
@@ -222,48 +149,35 @@ def uninstall_hooks(
|
|
|
222
149
|
logger.debug('Failed to delete hooks file', exc_info=e)
|
|
223
150
|
return False, f'Failed to remove hooks file: {hooks_path}'
|
|
224
151
|
|
|
225
|
-
if
|
|
152
|
+
if _save_hooks_file(hooks_path, existing):
|
|
226
153
|
return True, f'Cycode hooks removed from: {hooks_path}'
|
|
227
154
|
return False, f'Failed to update hooks file: {hooks_path}'
|
|
228
155
|
|
|
229
156
|
|
|
230
|
-
def get_hooks_status(scope: str = 'user', repo_path: Optional[Path] = None
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Args:
|
|
235
|
-
scope: 'user' for user-level hooks, 'repo' for repository-level hooks
|
|
236
|
-
repo_path: Repository path (required if scope is 'repo')
|
|
237
|
-
ide: The AI IDE type (default: Cursor)
|
|
238
|
-
|
|
239
|
-
Returns:
|
|
240
|
-
Dict with status information
|
|
241
|
-
"""
|
|
242
|
-
hooks_path = get_hooks_path(scope, repo_path, ide)
|
|
157
|
+
def get_hooks_status(ide: IDE, scope: str = 'user', repo_path: Optional[Path] = None) -> dict:
|
|
158
|
+
"""Return installation status of Cycode hooks for ``ide``."""
|
|
159
|
+
hooks_path = ide.settings_path(scope, repo_path)
|
|
243
160
|
|
|
244
|
-
status = {
|
|
161
|
+
status: dict = {
|
|
245
162
|
'scope': scope,
|
|
246
|
-
'ide': ide.
|
|
247
|
-
'ide_name':
|
|
163
|
+
'ide': ide.name,
|
|
164
|
+
'ide_name': ide.display_name,
|
|
248
165
|
'hooks_path': str(hooks_path),
|
|
249
166
|
'file_exists': hooks_path.exists(),
|
|
250
167
|
'cycode_installed': False,
|
|
251
168
|
'hooks': {},
|
|
252
169
|
}
|
|
253
170
|
|
|
254
|
-
existing =
|
|
171
|
+
existing = _load_hooks_file(hooks_path)
|
|
255
172
|
if existing is None:
|
|
256
173
|
return status
|
|
257
174
|
|
|
258
|
-
# Check each hook event for this IDE
|
|
259
|
-
ide_config = IDE_CONFIGS[ide]
|
|
260
175
|
has_cycode_hooks = False
|
|
261
|
-
for event in
|
|
262
|
-
#
|
|
176
|
+
for event in ide.hook_events:
|
|
177
|
+
# '<event>:<matcher>' filters entries to a specific tool/matcher.
|
|
263
178
|
if ':' in event:
|
|
264
179
|
actual_event, matcher_prefix = event.split(':', 1)
|
|
265
180
|
all_entries = existing.get('hooks', {}).get(actual_event, [])
|
|
266
|
-
# Filter entries by matcher
|
|
267
181
|
entries = [e for e in all_entries if e.get('matcher', '').startswith(matcher_prefix)]
|
|
268
182
|
else:
|
|
269
183
|
entries = existing.get('hooks', {}).get(event, [])
|
|
@@ -278,5 +192,4 @@ def get_hooks_status(scope: str = 'user', repo_path: Optional[Path] = None, ide:
|
|
|
278
192
|
}
|
|
279
193
|
|
|
280
194
|
status['cycode_installed'] = has_cycode_hooks
|
|
281
|
-
|
|
282
195
|
return status
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Registry of supported AI guardrails IDE integrations.
|
|
2
|
+
|
|
3
|
+
Adding a new IDE: create `ides/<name>.py` with a subclass of `IDE`, import it
|
|
4
|
+
here, and include an instance in the `IDES` tuple. Nothing else in the package
|
|
5
|
+
needs to change.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from cycode.cli.apps.ai_guardrails.ides.base import IDE
|
|
11
|
+
from cycode.cli.apps.ai_guardrails.ides.claude_code import ClaudeCode
|
|
12
|
+
from cycode.cli.apps.ai_guardrails.ides.cursor import Cursor
|
|
13
|
+
|
|
14
|
+
# Single source of truth: name → singleton instance.
|
|
15
|
+
# `--ide` choices and install/uninstall/status iteration both derive from this.
|
|
16
|
+
IDES: dict[str, IDE] = {ide.name: ide for ide in (Cursor(), ClaudeCode())}
|
|
17
|
+
|
|
18
|
+
# Default IDE used when `--ide` is omitted. Kept here so the value is colocated
|
|
19
|
+
# with the registry; no module outside `ides/` needs to know which IDE wins.
|
|
20
|
+
DEFAULT_IDE_NAME = 'cursor'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_ide(name: str) -> IDE:
|
|
24
|
+
"""Look up the IDE integration registered under ``name``.
|
|
25
|
+
|
|
26
|
+
Raises ``typer.BadParameter`` when the name is unknown — surfaces as a
|
|
27
|
+
user-friendly CLI error rather than a KeyError stack trace.
|
|
28
|
+
"""
|
|
29
|
+
ide = IDES.get(name.lower())
|
|
30
|
+
if ide is None:
|
|
31
|
+
valid = ', '.join(IDES.keys())
|
|
32
|
+
raise typer.BadParameter(f'Unknown IDE "{name}". Supported: {valid}.')
|
|
33
|
+
return ide
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def resolve_ides(name: str) -> list[IDE]:
|
|
37
|
+
"""Resolve an ``--ide`` argument to one or all IDE instances.
|
|
38
|
+
|
|
39
|
+
``"all"`` returns every registered IDE; anything else returns a single
|
|
40
|
+
matching IDE (raising ``typer.BadParameter`` for unknown names).
|
|
41
|
+
"""
|
|
42
|
+
if name.lower() == 'all':
|
|
43
|
+
return list(IDES.values())
|
|
44
|
+
return [get_ide(name)]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Base abstractions for AI guardrails IDE integrations.
|
|
2
|
+
|
|
3
|
+
Each AI IDE (Cursor, Claude Code, …) is represented by a subclass of `IDE`
|
|
4
|
+
that consolidates every IDE-specific concern in a single module: settings file
|
|
5
|
+
paths, hooks template rendering, payload parsing, response building, and any
|
|
6
|
+
IDE-specific session-context lookup.
|
|
7
|
+
|
|
8
|
+
Adding a new IDE is a matter of:
|
|
9
|
+
1. Subclassing `IDE` and implementing the abstract methods.
|
|
10
|
+
2. Registering the instance in `cycode/cli/apps/ai_guardrails/ides/__init__.py`.
|
|
11
|
+
|
|
12
|
+
The `HookDecision` dataclass is the canonical, IDE-agnostic return type for
|
|
13
|
+
event handlers; `IDE.build_hook_response` translates it into the IDE-specific
|
|
14
|
+
JSON response shape that the IDE expects on stdout.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import ClassVar, Optional
|
|
22
|
+
|
|
23
|
+
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
|
|
24
|
+
from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DecisionAction(str, Enum):
|
|
28
|
+
"""Canonical decision action returned by event handlers."""
|
|
29
|
+
|
|
30
|
+
ALLOW = 'allow'
|
|
31
|
+
DENY = 'deny'
|
|
32
|
+
ASK = 'ask'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class HookDecision:
|
|
37
|
+
"""Canonical, IDE-agnostic decision returned by event handlers.
|
|
38
|
+
|
|
39
|
+
Carries the event type so `IDE.build_hook_response` can pick the right
|
|
40
|
+
IDE-specific response shape (Cursor's "permission" style for tool events
|
|
41
|
+
vs. "continue" style for prompts; Claude Code's "hookSpecificOutput"
|
|
42
|
+
vs. "decision: block").
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
action: DecisionAction
|
|
46
|
+
event_type: AiHookEventType
|
|
47
|
+
user_message: Optional[str] = None
|
|
48
|
+
agent_message: Optional[str] = None
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def allow(cls, event_type: AiHookEventType) -> 'HookDecision':
|
|
52
|
+
return cls(action=DecisionAction.ALLOW, event_type=event_type)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def deny(
|
|
56
|
+
cls, event_type: AiHookEventType, user_message: str, agent_message: Optional[str] = None
|
|
57
|
+
) -> 'HookDecision':
|
|
58
|
+
return cls(
|
|
59
|
+
action=DecisionAction.DENY,
|
|
60
|
+
event_type=event_type,
|
|
61
|
+
user_message=user_message,
|
|
62
|
+
agent_message=agent_message,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def ask(cls, event_type: AiHookEventType, user_message: str, agent_message: Optional[str] = None) -> 'HookDecision':
|
|
67
|
+
return cls(
|
|
68
|
+
action=DecisionAction.ASK,
|
|
69
|
+
event_type=event_type,
|
|
70
|
+
user_message=user_message,
|
|
71
|
+
agent_message=agent_message,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class IDE(ABC):
|
|
76
|
+
"""Per-IDE integration. Owns every IDE-specific concern in a single module.
|
|
77
|
+
|
|
78
|
+
Subclasses declare identity via class attributes and implement the abstract
|
|
79
|
+
methods. Defaults are provided for `get_user_email` and `get_session_context`
|
|
80
|
+
so IDEs without those capabilities (e.g. no plugin system, no local
|
|
81
|
+
account file) can skip them.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# CLI value passed to --ide (e.g. 'cursor', 'claude-code').
|
|
85
|
+
name: ClassVar[str]
|
|
86
|
+
# Human-friendly name for output ('Cursor', 'Claude Code').
|
|
87
|
+
display_name: ClassVar[str]
|
|
88
|
+
# Event names for status display. Use '<event>:<matcher>' for IDEs that
|
|
89
|
+
# qualify a single hook by a sub-matcher (e.g. Claude Code's PreToolUse:Read).
|
|
90
|
+
hook_events: ClassVar[list[str]]
|
|
91
|
+
|
|
92
|
+
# --- install / status ---
|
|
93
|
+
|
|
94
|
+
@abstractmethod
|
|
95
|
+
def settings_path(self, scope: str, repo_path: Optional[Path] = None) -> Path:
|
|
96
|
+
"""Return the hooks/settings file path for the given scope.
|
|
97
|
+
|
|
98
|
+
`scope` is 'user' or 'repo'. `repo_path` is required when scope == 'repo'.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def render_hooks_config(self, async_mode: bool = False) -> dict:
|
|
103
|
+
"""Return the settings blob to merge into the IDE's settings file.
|
|
104
|
+
|
|
105
|
+
Shape is IDE-specific (Cursor uses a flat ``{event: [{command}]}`` dict;
|
|
106
|
+
Claude Code uses a nested ``{event: [{hooks: [{type, command}]}]}``
|
|
107
|
+
dict). Both share the outer ``{"hooks": ...}`` wrapper so
|
|
108
|
+
``hooks_manager`` can treat them uniformly.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# --- runtime scan ---
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def matches_payload(self, raw_payload: dict) -> bool:
|
|
115
|
+
"""Return True if ``raw_payload`` originated from this IDE.
|
|
116
|
+
|
|
117
|
+
Prevents double-processing when an IDE forwards another IDE's hook
|
|
118
|
+
event (e.g. Cursor reading Claude Code hooks from ~/.claude/settings.json).
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def parse_hook_payload(self, raw_payload: dict) -> AIHookPayload:
|
|
123
|
+
"""Normalize a raw stdin payload into the canonical ``AIHookPayload``."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def build_hook_response(self, decision: HookDecision) -> dict:
|
|
127
|
+
"""Translate a canonical ``HookDecision`` into the IDE-specific JSON.
|
|
128
|
+
|
|
129
|
+
The result is what ``scan_command`` writes to stdout for the IDE to
|
|
130
|
+
act on.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
# --- session lifecycle (optional; sensible defaults) ---
|
|
134
|
+
|
|
135
|
+
def build_session_payload(self, raw_payload: dict) -> AIHookPayload:
|
|
136
|
+
"""Build a session-start payload from the raw stdin payload.
|
|
137
|
+
|
|
138
|
+
Default: a minimal payload tagged with this IDE's ``name``. IDEs
|
|
139
|
+
that need to enrich with transcript/version info should override.
|
|
140
|
+
"""
|
|
141
|
+
return AIHookPayload(ide_provider=self.name)
|
|
142
|
+
|
|
143
|
+
def get_user_email(self) -> Optional[str]:
|
|
144
|
+
"""Best-effort read of the user's email from IDE-specific config.
|
|
145
|
+
|
|
146
|
+
Default: None. Override if the IDE stores a usable account locally.
|
|
147
|
+
"""
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def get_session_context(self) -> tuple[dict, dict]:
|
|
151
|
+
"""Return ``(mcp_servers, enabled_plugins)`` for session-context reporting.
|
|
152
|
+
|
|
153
|
+
Default: empty dicts (no plugin system, no discoverable MCP config).
|
|
154
|
+
Override to surface MCP/plugin inventory.
|
|
155
|
+
"""
|
|
156
|
+
return {}, {}
|