cycode 3.15.4.dev1__tar.gz → 3.15.4.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/PKG-INFO +3 -1
  2. cycode-3.15.4.dev3/cycode/__init__.py +1 -0
  3. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/hooks_manager.py +83 -25
  4. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/ides/__init__.py +2 -1
  5. cycode-3.15.4.dev3/cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +73 -0
  6. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/ides/base.py +20 -0
  7. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/ides/claude_code.py +32 -52
  8. cycode-3.15.4.dev3/cycode/cli/apps/ai_guardrails/ides/codex.py +310 -0
  9. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/ides/cursor.py +1 -1
  10. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/handlers.py +66 -22
  11. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_command.py +20 -2
  12. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/jwt_utils.py +8 -0
  13. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/pyproject.toml +3 -1
  14. cycode-3.15.4.dev1/cycode/__init__.py +0 -1
  15. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/LICENCE +0 -0
  16. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/README.md +0 -0
  17. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/__main__.py +0 -0
  18. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/__init__.py +0 -0
  19. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/app.py +0 -0
  20. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/__init__.py +0 -0
  21. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/activation_manager.py +0 -0
  22. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/__init__.py +0 -0
  23. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/command_utils.py +0 -0
  24. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/consts.py +0 -0
  25. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/install_command.py +0 -0
  26. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/__init__.py +0 -0
  27. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/consts.py +0 -0
  28. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/payload.py +0 -0
  29. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/policy.py +0 -0
  30. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/scan_command.py +0 -0
  31. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/types.py +0 -0
  32. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/scan/utils.py +0 -0
  33. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/session_start_command.py +0 -0
  34. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/status_command.py +0 -0
  35. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_guardrails/uninstall_command.py +0 -0
  36. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_remediation/__init__.py +0 -0
  37. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_remediation/ai_remediation_command.py +0 -0
  38. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_remediation/apply_fix.py +0 -0
  39. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ai_remediation/print_remediation.py +0 -0
  40. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/api/__init__.py +0 -0
  41. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/api/api_command.py +0 -0
  42. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/api/openapi_spec.py +0 -0
  43. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/auth/__init__.py +0 -0
  44. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/auth/auth_command.py +0 -0
  45. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/auth/auth_common.py +0 -0
  46. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/auth/auth_manager.py +0 -0
  47. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/auth/models.py +0 -0
  48. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/configure/__init__.py +0 -0
  49. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/configure/configure_command.py +0 -0
  50. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/configure/consts.py +0 -0
  51. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/configure/messages.py +0 -0
  52. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/configure/prompts.py +0 -0
  53. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ignore/__init__.py +0 -0
  54. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/ignore/ignore_command.py +0 -0
  55. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/mcp/__init__.py +0 -0
  56. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/mcp/mcp_command.py +0 -0
  57. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/__init__.py +0 -0
  58. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/report_command.py +0 -0
  59. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/__init__.py +0 -0
  60. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/common.py +0 -0
  61. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/path/__init__.py +0 -0
  62. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/path/path_command.py +0 -0
  63. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/repository_url/__init__.py +0 -0
  64. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/repository_url/repository_url_command.py +0 -0
  65. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/sbom_command.py +0 -0
  66. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report/sbom/sbom_report_file.py +0 -0
  67. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report_import/__init__.py +0 -0
  68. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report_import/report_import_command.py +0 -0
  69. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report_import/sbom/__init__.py +0 -0
  70. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/report_import/sbom/sbom_command.py +0 -0
  71. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/sca_options.py +0 -0
  72. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/__init__.py +0 -0
  73. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/aggregation_report.py +0 -0
  74. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/code_scanner.py +0 -0
  75. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/commit_history/__init__.py +0 -0
  76. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/commit_history/commit_history_command.py +0 -0
  77. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/commit_range_scanner.py +0 -0
  78. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/detection_excluder.py +0 -0
  79. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/path/__init__.py +0 -0
  80. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/path/path_command.py +0 -0
  81. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_commit/__init__.py +0 -0
  82. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_commit/pre_commit_command.py +0 -0
  83. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_push/__init__.py +0 -0
  84. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_push/pre_push_command.py +0 -0
  85. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_receive/__init__.py +0 -0
  86. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/pre_receive/pre_receive_command.py +0 -0
  87. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/remote_url_resolver.py +0 -0
  88. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/repository/__init__.py +0 -0
  89. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/repository/repository_command.py +0 -0
  90. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_ci/__init__.py +0 -0
  91. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_ci/ci_integrations.py +0 -0
  92. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_ci/scan_ci_command.py +0 -0
  93. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_parameters.py +0 -0
  94. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/scan/scan_result.py +0 -0
  95. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/status/__init__.py +0 -0
  96. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/status/get_cli_status.py +0 -0
  97. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/status/models.py +0 -0
  98. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/status/status_command.py +0 -0
  99. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/apps/status/version_command.py +0 -0
  100. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/cli_types.py +0 -0
  101. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/config.py +0 -0
  102. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/console.py +0 -0
  103. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/consts.py +0 -0
  104. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/__init__.py +0 -0
  105. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/custom_exceptions.py +0 -0
  106. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/handle_ai_remediation_errors.py +0 -0
  107. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/handle_auth_errors.py +0 -0
  108. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/handle_errors.py +0 -0
  109. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/handle_report_sbom_errors.py +0 -0
  110. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/exceptions/handle_scan_errors.py +0 -0
  111. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/__init__.py +0 -0
  112. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/commit_range_documents.py +0 -0
  113. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/documents_walk_ignore.py +0 -0
  114. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/file_excluder.py +0 -0
  115. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/iac/__init__.py +0 -0
  116. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/iac/tf_content_generator.py +0 -0
  117. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/models/__init__.py +0 -0
  118. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/models/in_memory_zip.py +0 -0
  119. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/path_documents.py +0 -0
  120. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/repository_documents.py +0 -0
  121. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/__init__.py +0 -0
  122. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/base_restore_dependencies.py +0 -0
  123. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/go/__init__.py +0 -0
  124. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/go/restore_go_dependencies.py +0 -0
  125. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/maven/__init__.py +0 -0
  126. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +0 -0
  127. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +0 -0
  128. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/npm/__init__.py +0 -0
  129. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +0 -0
  130. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/npm/restore_npm_dependencies.py +0 -0
  131. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +0 -0
  132. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +0 -0
  133. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/nuget/__init__.py +0 -0
  134. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/nuget/restore_nuget_dependencies.py +0 -0
  135. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/php/__init__.py +0 -0
  136. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +0 -0
  137. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/python/__init__.py +0 -0
  138. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +0 -0
  139. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +0 -0
  140. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +0 -0
  141. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/ruby/__init__.py +0 -0
  142. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/ruby/restore_ruby_dependencies.py +0 -0
  143. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/sbt/__init__.py +0 -0
  144. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/sbt/restore_sbt_dependencies.py +0 -0
  145. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/sca/sca_file_collector.py +0 -0
  146. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/walk_ignore.py +0 -0
  147. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/files_collector/zip_documents.py +0 -0
  148. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/logger.py +0 -0
  149. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/main.py +0 -0
  150. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/models.py +0 -0
  151. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/__init__.py +0 -0
  152. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/console_printer.py +0 -0
  153. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/json_printer.py +0 -0
  154. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/printer_base.py +0 -0
  155. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/rich_printer.py +0 -0
  156. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/__init__.py +0 -0
  157. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/sca_table_printer.py +0 -0
  158. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/table.py +0 -0
  159. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/table_models.py +0 -0
  160. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/table_printer.py +0 -0
  161. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/tables/table_printer_base.py +0 -0
  162. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/text_printer.py +0 -0
  163. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/__init__.py +0 -0
  164. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/code_snippet_syntax.py +0 -0
  165. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/detection_data.py +0 -0
  166. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/detection_ordering/__init__.py +0 -0
  167. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/detection_ordering/common_ordering.py +0 -0
  168. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/detection_ordering/sca_ordering.py +0 -0
  169. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/printers/utils/rich_helpers.py +0 -0
  170. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/__init__.py +0 -0
  171. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/base_file_manager.py +0 -0
  172. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/config_file_manager.py +0 -0
  173. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/configuration_manager.py +0 -0
  174. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/credentials_manager.py +0 -0
  175. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/user_settings/jwt_creator.py +0 -0
  176. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/__init__.py +0 -0
  177. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/binary_utils.py +0 -0
  178. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/enum_utils.py +0 -0
  179. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/get_api_client.py +0 -0
  180. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/git_proxy.py +0 -0
  181. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/ignore_utils.py +0 -0
  182. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/path_utils.py +0 -0
  183. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/progress_bar.py +0 -0
  184. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/scan_batch.py +0 -0
  185. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/scan_utils.py +0 -0
  186. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/shell_executor.py +0 -0
  187. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/string_utils.py +0 -0
  188. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/task_timer.py +0 -0
  189. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/url_utils.py +0 -0
  190. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/version_checker.py +0 -0
  191. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cli/utils/yaml_utils.py +0 -0
  192. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/config.py +0 -0
  193. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/__init__.py +0 -0
  194. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/ai_security_manager_client.py +0 -0
  195. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/ai_security_manager_service_config.py +0 -0
  196. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/auth_client.py +0 -0
  197. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/base_token_auth_client.py +0 -0
  198. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cli_activation_client.py +0 -0
  199. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/client_creator.py +0 -0
  200. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/config.py +0 -0
  201. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/config_dev.py +0 -0
  202. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cycode_client.py +0 -0
  203. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cycode_client_base.py +0 -0
  204. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cycode_dev_based_client.py +0 -0
  205. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cycode_oidc_based_client.py +0 -0
  206. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/cycode_token_based_client.py +0 -0
  207. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/headers.py +0 -0
  208. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/import_sbom_client.py +0 -0
  209. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/logger.py +0 -0
  210. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/models.py +0 -0
  211. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/report_client.py +0 -0
  212. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/scan_client.py +0 -0
  213. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/cyclient/scan_config_base.py +0 -0
  214. {cycode-3.15.4.dev1 → cycode-3.15.4.dev3}/cycode/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.15.4.dev1
3
+ Version: 3.15.4.dev3
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  License-Expression: MIT
6
6
  License-File: LICENCE
@@ -34,6 +34,8 @@ Requires-Dist: pyyaml (>=6.0,<7.0)
34
34
  Requires-Dist: requests (>=2.32.4,<3.0)
35
35
  Requires-Dist: rich (>=13.9.4,<14)
36
36
  Requires-Dist: tenacity (>=9.0.0,<9.1.0)
37
+ Requires-Dist: tomli (>=2.0.0,<3.0.0) ; python_version < "3.11"
38
+ Requires-Dist: tomli-w (>=1.0.0,<2.0.0)
37
39
  Requires-Dist: typer (>=0.15.3,<0.16.0)
38
40
  Requires-Dist: urllib3 (>=2.4.0,<3.0.0)
39
41
  Project-URL: Repository, https://github.com/cycodehq/cycode-cli
@@ -0,0 +1 @@
1
+ __version__ = '3.15.4.dev3' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -28,7 +28,7 @@ def _is_cycode_command(command: str) -> bool:
28
28
 
29
29
 
30
30
  def is_cycode_hook_entry(entry: dict) -> bool:
31
- """Detect Cycode hook entries in both Cursor (flat) and Claude Code (nested) shapes."""
31
+ """True if any hook inside ``entry`` is owned by Cycode."""
32
32
  command = entry.get('command', '')
33
33
  if _is_cycode_command(command):
34
34
  return True
@@ -40,6 +40,31 @@ def is_cycode_hook_entry(entry: dict) -> bool:
40
40
  return False
41
41
 
42
42
 
43
+ def _strip_cycode_from_entry(entry: dict) -> Optional[dict]:
44
+ """Remove Cycode hooks from ``entry`` and return the remainder.
45
+
46
+ Returns ``None`` when nothing useful remains (Cursor-flat Cycode entry, or
47
+ every nested hook was Cycode). Non-Cycode hooks co-located in the same
48
+ entry are preserved.
49
+ """
50
+ # Cursor format: the entry itself IS a single hook command.
51
+ if 'command' in entry and 'hooks' not in entry:
52
+ return None if _is_cycode_command(entry.get('command', '')) else entry
53
+
54
+ # Claude Code / Codex format: nested `hooks` list inside the entry.
55
+ nested = entry.get('hooks')
56
+ if isinstance(nested, list):
57
+ kept = [h for h in nested if not (isinstance(h, dict) and _is_cycode_command(h.get('command', '')))]
58
+ if not kept:
59
+ return None
60
+ if len(kept) == len(nested):
61
+ return entry # nothing Cycode-shaped inside; preserve identity
62
+ return {**entry, 'hooks': kept}
63
+
64
+ # Entry has neither shape we recognize — leave it alone defensively.
65
+ return entry
66
+
67
+
43
68
  def _load_hooks_file(hooks_path: Path) -> Optional[dict]:
44
69
  if not hooks_path.exists():
45
70
  return None
@@ -108,50 +133,83 @@ def install_hooks(
108
133
 
109
134
  for event, entries in rendered['hooks'].items():
110
135
  existing['hooks'].setdefault(event, [])
111
-
112
- # Remove any existing Cycode entries for this event
113
- existing['hooks'][event] = [e for e in existing['hooks'][event] if not is_cycode_hook_entry(e)]
114
-
115
- # Add new Cycode entries
136
+ existing['hooks'][event] = [
137
+ stripped for e in existing['hooks'][event] if (stripped := _strip_cycode_from_entry(e)) is not None
138
+ ]
116
139
  for entry in entries:
117
140
  existing['hooks'][event].append(entry)
118
141
 
119
- if _save_hooks_file(hooks_path, existing):
120
- return True, f'AI guardrails hooks installed: {hooks_path}'
121
- return False, f'Failed to install hooks to {hooks_path}'
142
+ if not _save_hooks_file(hooks_path, existing):
143
+ return False, f'Failed to install hooks to {hooks_path}'
122
144
 
145
+ message = f'AI guardrails hooks installed: {hooks_path}'
123
146
 
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)
147
+ # IDE-specific extras (e.g. Codex enables a TOML feature flag).
148
+ extra_ok, extra_message = ide.post_install(scope, repo_path)
149
+ if not extra_ok:
150
+ return False, extra_message
151
+ if extra_message:
152
+ message = f'{message}\n {extra_message}'
127
153
 
128
- existing = _load_hooks_file(hooks_path)
129
- if existing is None:
130
- return True, f'No hooks file found at {hooks_path}'
154
+ return True, message
131
155
 
156
+
157
+ def _strip_cycode_entries(existing: dict) -> bool:
158
+ """Mutate ``existing`` to drop Cycode hooks (surgically). Return True if anything changed."""
132
159
  modified = False
133
160
  for event in list(existing.get('hooks', {}).keys()):
134
- original_count = len(existing['hooks'][event])
135
- existing['hooks'][event] = [e for e in existing['hooks'][event] if not is_cycode_hook_entry(e)]
136
- if len(existing['hooks'][event]) != original_count:
137
- modified = True
138
- if not existing['hooks'][event]:
161
+ before = existing['hooks'][event]
162
+ after: list = []
163
+ for e in before:
164
+ stripped = _strip_cycode_from_entry(e)
165
+ if stripped is None:
166
+ modified = True
167
+ continue
168
+ if stripped is not e:
169
+ modified = True
170
+ after.append(stripped)
171
+ if not after:
139
172
  del existing['hooks'][event]
173
+ else:
174
+ existing['hooks'][event] = after
175
+ return modified
140
176
 
177
+
178
+ def _persist_uninstall(hooks_path: Path, existing: dict, modified: bool) -> tuple[bool, str]:
179
+ """Apply the uninstall result to disk and return ``(success, message)``."""
141
180
  if not modified:
142
181
  return True, 'No Cycode hooks found to remove'
143
-
144
182
  if not existing.get('hooks'):
145
183
  try:
146
184
  hooks_path.unlink()
147
- return True, f'Removed hooks file: {hooks_path}'
148
185
  except Exception as e:
149
186
  logger.debug('Failed to delete hooks file', exc_info=e)
150
187
  return False, f'Failed to remove hooks file: {hooks_path}'
188
+ return True, f'Removed hooks file: {hooks_path}'
189
+ if not _save_hooks_file(hooks_path, existing):
190
+ return False, f'Failed to update hooks file: {hooks_path}'
191
+ return True, f'Cycode hooks removed from: {hooks_path}'
192
+
193
+
194
+ def uninstall_hooks(ide: IDE, scope: str = 'user', repo_path: Optional[Path] = None) -> tuple[bool, str]:
195
+ """Remove Cycode AI guardrails hooks for ``ide``."""
196
+ hooks_path = ide.settings_path(scope, repo_path)
197
+
198
+ existing = _load_hooks_file(hooks_path)
199
+ if existing is None:
200
+ return True, f'No hooks file found at {hooks_path}'
151
201
 
152
- if _save_hooks_file(hooks_path, existing):
153
- return True, f'Cycode hooks removed from: {hooks_path}'
154
- return False, f'Failed to update hooks file: {hooks_path}'
202
+ modified = _strip_cycode_entries(existing)
203
+ file_ok, message = _persist_uninstall(hooks_path, existing, modified)
204
+ if not file_ok:
205
+ return False, message
206
+
207
+ extra_ok, extra_message = ide.post_uninstall(scope, repo_path)
208
+ if not extra_ok:
209
+ return False, extra_message
210
+ if extra_message:
211
+ message = f'{message}\n {extra_message}'
212
+ return True, message
155
213
 
156
214
 
157
215
  def get_hooks_status(ide: IDE, scope: str = 'user', repo_path: Optional[Path] = None) -> dict:
@@ -9,11 +9,12 @@ import typer
9
9
 
10
10
  from cycode.cli.apps.ai_guardrails.ides.base import IDE
11
11
  from cycode.cli.apps.ai_guardrails.ides.claude_code import ClaudeCode
12
+ from cycode.cli.apps.ai_guardrails.ides.codex import Codex
12
13
  from cycode.cli.apps.ai_guardrails.ides.cursor import Cursor
13
14
 
14
15
  # Single source of truth: name → singleton instance.
15
16
  # `--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
+ IDES: dict[str, IDE] = {ide.name: ide for ide in (Cursor(), ClaudeCode(), Codex())}
17
18
 
18
19
  # Default IDE used when `--ide` is omitted. Kept here so the value is colocated
19
20
  # with the registry; no module outside `ides/` needs to know which IDE wins.
@@ -0,0 +1,73 @@
1
+ """Shared plugin-resolution helpers for IDE integrations.
2
+
3
+ Both Claude Code and Codex use the same ``<plugin>@<marketplace>`` key convention
4
+ and emit the same telemetry shape — only the marketplace layout and manifest
5
+ location differ. ``walk_enabled_plugins`` is the IDE-agnostic loop; each IDE
6
+ supplies the two callables that vary (``locate_dir`` + ``read_plugin``).
7
+ """
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any, Callable, Optional
12
+
13
+ from cycode.logger import get_logger
14
+
15
+ logger = get_logger('AI Guardrails Plugins')
16
+
17
+
18
+ def load_plugin_json(path: Path) -> Optional[dict]:
19
+ """Load a JSON file inside a plugin directory; None if missing or invalid."""
20
+ if not path.exists():
21
+ return None
22
+ try:
23
+ return json.loads(path.read_text(encoding='utf-8'))
24
+ except Exception as e:
25
+ logger.debug('Failed to load plugin file, %s', {'path': str(path)}, exc_info=e)
26
+ return None
27
+
28
+
29
+ def walk_enabled_plugins(
30
+ plugin_entries: dict[str, Any],
31
+ is_enabled: Callable[[Any], bool],
32
+ locate_dir: Callable[[str, str], Optional[Path]],
33
+ read_plugin: Callable[[Path], tuple[dict, dict]],
34
+ ) -> tuple[dict, dict]:
35
+ """Iterate enabled plugins; merge their MCP servers and metadata.
36
+
37
+ Args:
38
+ plugin_entries: ``{<plugin>@<marketplace>: settings}`` map from the IDE config.
39
+ is_enabled: returns True if ``settings`` indicates the plugin is on
40
+ (e.g. ``bool(settings)`` for Claude, ``settings.get('enabled')`` for Codex).
41
+ locate_dir: given ``(plugin_name, marketplace)``, returns the plugin's
42
+ filesystem path or None if it can't be resolved.
43
+ read_plugin: given the plugin path, returns ``(entry_fields, servers)``:
44
+ ``entry_fields`` are extra metadata to attach to the inventory entry
45
+ (name/version/description/...), ``servers`` are MCP servers contributed.
46
+
47
+ Returns ``(merged_mcp_servers, enriched_plugins)``. Plugin keys without
48
+ ``@`` (or that fail to resolve to a directory) still appear in the
49
+ inventory with just ``{'enabled': True}`` so we don't silently drop them.
50
+ """
51
+ merged_mcp: dict = {}
52
+ enriched: dict = {}
53
+
54
+ for plugin_key, settings in plugin_entries.items():
55
+ if not is_enabled(settings):
56
+ continue
57
+
58
+ entry: dict = {'enabled': True}
59
+ enriched[plugin_key] = entry
60
+
61
+ if '@' not in plugin_key:
62
+ continue
63
+ plugin_name, marketplace = plugin_key.split('@', 1)
64
+
65
+ plugin_dir = locate_dir(plugin_name, marketplace)
66
+ if plugin_dir is None:
67
+ continue
68
+
69
+ plugin_fields, servers = read_plugin(plugin_dir)
70
+ entry.update(plugin_fields)
71
+ merged_mcp.update(servers)
72
+
73
+ return merged_mcp, enriched
@@ -108,6 +108,26 @@ class IDE(ABC):
108
108
  ``hooks_manager`` can treat them uniformly.
109
109
  """
110
110
 
111
+ def post_install(self, scope: str, repo_path: Optional[Path] = None) -> tuple[bool, str]:
112
+ """Run IDE-specific actions after the hooks file is written.
113
+
114
+ Default: no-op success. Override to perform extra setup that doesn't
115
+ belong in the hooks file itself — e.g. Codex enables a
116
+ ``[features] codex_hooks = true`` flag in its TOML config.
117
+
118
+ Returns ``(success, message)``. If ``success`` is False, the overall
119
+ install is considered failed.
120
+ """
121
+ return True, ''
122
+
123
+ def post_uninstall(self, scope: str, repo_path: Optional[Path] = None) -> tuple[bool, str]:
124
+ """Run IDE-specific cleanup after the hooks file is removed.
125
+
126
+ Default: no-op success. Override to undo whatever ``post_install``
127
+ wrote outside the hooks file.
128
+ """
129
+ return True, ''
130
+
111
131
  # --- runtime scan ---
112
132
 
113
133
  @abstractmethod
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from typing import ClassVar, Optional
8
8
 
9
9
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
10
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import load_plugin_json, walk_enabled_plugins
10
11
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
11
12
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
12
13
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -127,7 +128,7 @@ def load_claude_config(config_path: Optional[Path] = None) -> Optional[dict]:
127
128
  """Load and parse `~/.claude.json`. Returns None if missing/invalid."""
128
129
  path = config_path or _CLAUDE_CONFIG_PATH
129
130
  if not path.exists():
130
- logger.debug('Claude config file not found', extra={'path': str(path)})
131
+ logger.debug('Claude config file not found, %s', {'path': str(path)})
131
132
  return None
132
133
  try:
133
134
  return json.loads(path.read_text(encoding='utf-8'))
@@ -150,7 +151,7 @@ def load_claude_settings(settings_path: Optional[Path] = None) -> Optional[dict]
150
151
  """Load and parse `~/.claude/settings.json`. Returns None if missing/invalid."""
151
152
  path = settings_path or _CLAUDE_SETTINGS_PATH
152
153
  if not path.exists():
153
- logger.debug('Claude settings file not found', extra={'path': str(path)})
154
+ logger.debug('Claude settings file not found, %s', {'path': str(path)})
154
155
  return None
155
156
  try:
156
157
  return json.loads(path.read_text(encoding='utf-8'))
@@ -171,69 +172,47 @@ def _resolve_marketplace_path(marketplace: dict) -> Optional[Path]:
171
172
  return path if path.is_dir() else None
172
173
 
173
174
 
174
- def _load_plugin_json_file(plugin_path: Path, relative_path: str) -> Optional[dict]:
175
- """Load and parse a JSON file inside a plugin directory.
175
+ def _read_claude_plugin(plugin_dir: Path) -> tuple[dict, dict]:
176
+ """Read one Claude Code plugin's manifest + MCP servers.
176
177
 
177
- Returns None if the file is missing, unreadable, or has invalid JSON.
178
+ Claude hardcodes the MCP file at ``<plugin_dir>/.mcp.json`` and always
179
+ wraps it as ``{"mcpServers": {...}}``.
178
180
  """
179
- target = plugin_path / relative_path
180
- if not target.exists():
181
- return None
182
- try:
183
- return json.loads(target.read_text(encoding='utf-8'))
184
- except Exception as e:
185
- logger.debug('Failed to load plugin file', extra={'path': str(target)}, exc_info=e)
186
- return None
181
+ manifest = load_plugin_json(plugin_dir / '.claude-plugin' / 'plugin.json') or {}
182
+ entry: dict = {}
183
+ for field in ('name', 'version', 'description'):
184
+ if field in manifest:
185
+ entry[field] = manifest[field]
187
186
 
187
+ mcp_config = load_plugin_json(plugin_dir / '.mcp.json') or {}
188
+ servers: dict = mcp_config.get('mcpServers') or {}
189
+ if servers:
190
+ entry['mcp_server_names'] = list(servers.keys())
191
+ return entry, servers
188
192
 
189
- def resolve_plugins(settings: dict) -> tuple[dict, dict]:
190
- """Resolve enabled plugins to their MCP servers and metadata.
191
193
 
192
- Walks ``enabledPlugins`` from claude settings, resolves each plugin's
193
- marketplace directory via ``extraKnownMarketplaces``, and reads:
194
- - ``<path>/.mcp.json`` for MCP servers (merged into a flat dict)
195
- - ``<path>/.claude-plugin/plugin.json`` for metadata (name, version, description)
194
+ def resolve_plugins(settings: dict) -> tuple[dict, dict]:
195
+ """Walk Claude Code's ``enabledPlugins`` via the shared plugin walker.
196
196
 
197
- Returns ``(merged_mcp_servers, enriched_plugins)``.
197
+ Each enabled plugin's marketplace is resolved through
198
+ ``extraKnownMarketplaces`` to a directory; the rest of the work
199
+ (manifest + ``.mcp.json``) is the shared ``_read_claude_plugin``.
198
200
  """
199
201
  enabled = settings.get('enabledPlugins') or {}
200
202
  marketplaces = settings.get('extraKnownMarketplaces') or {}
201
- merged_mcp: dict = {}
202
- enriched: dict = {}
203
203
 
204
- for plugin_key, is_enabled in enabled.items():
205
- if not is_enabled:
206
- continue
207
-
208
- entry: dict = {'enabled': True}
209
- enriched[plugin_key] = entry
210
-
211
- if '@' not in plugin_key:
212
- continue
213
-
214
- _plugin_name, marketplace_name = plugin_key.split('@', 1)
204
+ def _locate(_plugin_name: str, marketplace_name: str) -> Optional[Path]:
215
205
  marketplace = marketplaces.get(marketplace_name)
216
206
  if not marketplace:
217
- continue
218
-
219
- plugin_path = _resolve_marketplace_path(marketplace)
220
- if plugin_path is None:
221
- continue
222
-
223
- metadata = _load_plugin_json_file(plugin_path, '.claude-plugin/plugin.json') or {}
224
- for field in ('name', 'version', 'description'):
225
- if field in metadata:
226
- entry[field] = metadata[field]
227
-
228
- mcp_config = _load_plugin_json_file(plugin_path, '.mcp.json') or {}
229
- plugin_server_names = []
230
- for server_name, server_cfg in (mcp_config.get('mcpServers') or {}).items():
231
- merged_mcp[server_name] = server_cfg
232
- plugin_server_names.append(server_name)
233
- if plugin_server_names:
234
- entry['mcp_server_names'] = plugin_server_names
207
+ return None
208
+ return _resolve_marketplace_path(marketplace)
235
209
 
236
- return merged_mcp, enriched
210
+ return walk_enabled_plugins(
211
+ plugin_entries=enabled,
212
+ is_enabled=bool,
213
+ locate_dir=_locate,
214
+ read_plugin=_read_claude_plugin,
215
+ )
237
216
 
238
217
 
239
218
  # --- IDE integration ----------------------------------------------------------
@@ -260,6 +239,7 @@ class ClaudeCode(IDE):
260
239
  'hooks': {
261
240
  'SessionStart': [
262
241
  {
242
+ 'matcher': 'startup|clear',
263
243
  'hooks': [{'type': 'command', 'command': _SESSION_START_COMMAND}],
264
244
  }
265
245
  ],