classifyre-cli 0.4.71__tar.gz → 0.4.72__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 (264) hide show
  1. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/.turbo/turbo-build.log +1 -1
  2. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/PKG-INFO +1 -1
  3. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/package.json +1 -1
  4. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/pyproject.toml +4 -1
  5. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/pii/detector.py +126 -2
  6. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/models/generated_detectors.py +2 -2
  7. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/models/generated_input.py +91 -70
  8. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/notion/source.py +11 -0
  9. classifyre_cli-0.4.72/src/sources/slack/source.py +953 -0
  10. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/dependency_groups.py +1 -0
  11. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/file_parser.py +70 -1
  12. classifyre_cli-0.4.72/tests/detectors/pii/test_pii_regional_entities.py +70 -0
  13. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_dependency_groups.py +7 -1
  14. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_notion_source.py +77 -0
  15. classifyre_cli-0.4.72/tests/test_slack_source.py +690 -0
  16. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_source_strategy_conformance.py +1 -10
  17. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_file_parser.py +93 -8
  18. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/uv.lock +15 -2
  19. classifyre_cli-0.4.71/src/sources/slack/source.py +0 -574
  20. classifyre_cli-0.4.71/tests/test_slack_source.py +0 -208
  21. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/.gitignore +0 -0
  22. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/.python-version +0 -0
  23. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/README.md +0 -0
  24. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/main.py +0 -0
  25. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/scripts/generate_models.py +0 -0
  26. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/__init__.py +0 -0
  27. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/config.py +0 -0
  28. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/__init__.py +0 -0
  29. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/base.py +0 -0
  30. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/broken_links/__init__.py +0 -0
  31. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/broken_links/detector.py +0 -0
  32. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/config.py +0 -0
  33. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/content/__init__.py +0 -0
  34. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/__init__.py +0 -0
  35. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/detector.py +0 -0
  36. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/extractor.py +0 -0
  37. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/__init__.py +0 -0
  38. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_base.py +0 -0
  39. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_factory.py +0 -0
  40. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_gliner2.py +0 -0
  41. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_image_classification.py +0 -0
  42. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_llm.py +0 -0
  43. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_object_detection.py +0 -0
  44. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_regex.py +0 -0
  45. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/runners/_text_classification.py +0 -0
  46. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/custom/trainer.py +0 -0
  47. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/dependencies.py +0 -0
  48. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/pii/__init__.py +0 -0
  49. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/secrets/__init__.py +0 -0
  50. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/secrets/detector.py +0 -0
  51. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/threat/__init__.py +0 -0
  52. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/threat/code_security_detector.py +0 -0
  53. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/detectors/threat/yara_detector.py +0 -0
  54. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/file_evaluation/__init__.py +0 -0
  55. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/file_evaluation/runner.py +0 -0
  56. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/main.py +0 -0
  57. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/models/generated_single_asset_scan_results.py +0 -0
  58. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/__init__.py +0 -0
  59. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/base.py +0 -0
  60. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/console.py +0 -0
  61. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/factory.py +0 -0
  62. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/file.py +0 -0
  63. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/outputs/rest.py +0 -0
  64. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/__init__.py +0 -0
  65. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/content_provider.py +0 -0
  66. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/detector_pipeline.py +0 -0
  67. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/parsed_content_provider.py +0 -0
  68. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/text_artifact.py +0 -0
  69. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/pipeline/worker_pool.py +0 -0
  70. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/__init__.py +0 -0
  71. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/asset_metadata.py +0 -0
  72. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/atlassian_common.py +0 -0
  73. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/azure_blob_storage/__init__.py +0 -0
  74. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/azure_blob_storage/source.py +0 -0
  75. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/base.py +0 -0
  76. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/confluence/__init__.py +0 -0
  77. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/confluence/source.py +0 -0
  78. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/databricks/__init__.py +0 -0
  79. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/databricks/source.py +0 -0
  80. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/delta_lake/__init__.py +0 -0
  81. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/delta_lake/source.py +0 -0
  82. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/dependencies.py +0 -0
  83. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/dropbox/__init__.py +0 -0
  84. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/dropbox/auth.py +0 -0
  85. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/dropbox/source.py +0 -0
  86. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/elasticsearch/__init__.py +0 -0
  87. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/elasticsearch/source.py +0 -0
  88. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/email/__init__.py +0 -0
  89. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/email/source.py +0 -0
  90. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/google_cloud_storage/__init__.py +0 -0
  91. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/google_cloud_storage/source.py +0 -0
  92. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/google_workspace/__init__.py +0 -0
  93. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/google_workspace/source.py +0 -0
  94. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/hive/__init__.py +0 -0
  95. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/hive/source.py +0 -0
  96. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/iceberg/__init__.py +0 -0
  97. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/iceberg/source.py +0 -0
  98. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/jira/__init__.py +0 -0
  99. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/jira/source.py +0 -0
  100. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/kafka/__init__.py +0 -0
  101. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/kafka/rest.py +0 -0
  102. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/kafka/source.py +0 -0
  103. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/lakehouse_base.py +0 -0
  104. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/local_folder/__init__.py +0 -0
  105. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/local_folder/source.py +0 -0
  106. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/meilisearch/__init__.py +0 -0
  107. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/meilisearch/source.py +0 -0
  108. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/microsoft_365/__init__.py +0 -0
  109. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/microsoft_365/source.py +0 -0
  110. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mongodb/__init__.py +0 -0
  111. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mongodb/source.py +0 -0
  112. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mssql/__init__.py +0 -0
  113. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mssql/source.py +0 -0
  114. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mysql/__init__.py +0 -0
  115. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/mysql/source.py +0 -0
  116. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/neo4j/__init__.py +0 -0
  117. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/neo4j/source.py +0 -0
  118. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/notion/__init__.py +0 -0
  119. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/notion/client.py +0 -0
  120. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/object_storage/base.py +0 -0
  121. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/opensearch/__init__.py +0 -0
  122. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/opensearch/source.py +0 -0
  123. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/oracle/__init__.py +0 -0
  124. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/oracle/source.py +0 -0
  125. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/postgresql/__init__.py +0 -0
  126. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/postgresql/source.py +0 -0
  127. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/powerbi/__init__.py +0 -0
  128. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/powerbi/source.py +0 -0
  129. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/recipe_normalizer.py +0 -0
  130. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/s3_client.py +0 -0
  131. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/s3_compatible_storage/README.md +0 -0
  132. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/s3_compatible_storage/__init__.py +0 -0
  133. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/s3_compatible_storage/source.py +0 -0
  134. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/sandbox/__init__.py +0 -0
  135. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/sandbox/source.py +0 -0
  136. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/search_engine_base.py +0 -0
  137. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/servicedesk/__init__.py +0 -0
  138. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/servicedesk/source.py +0 -0
  139. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/slack/__init__.py +0 -0
  140. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/snowflake/__init__.py +0 -0
  141. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/snowflake/source.py +0 -0
  142. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/sqlite/__init__.py +0 -0
  143. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/sqlite/source.py +0 -0
  144. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/tableau/__init__.py +0 -0
  145. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/tableau/source.py +0 -0
  146. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/tabular_base.py +0 -0
  147. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/tabular_utils.py +0 -0
  148. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/wordpress/__init__.py +0 -0
  149. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/wordpress/source.py +0 -0
  150. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/youtube/__init__.py +0 -0
  151. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/sources/youtube/source.py +0 -0
  152. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/telemetry.py +0 -0
  153. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/__init__.py +0 -0
  154. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/archive_extraction.py +0 -0
  155. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/content_extraction.py +0 -0
  156. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/embedded_images.py +0 -0
  157. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/file_metadata.py +0 -0
  158. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/file_to_images.py +0 -0
  159. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/hashing.py +0 -0
  160. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/legacy_office.py +0 -0
  161. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/resources.py +0 -0
  162. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/transcription.py +0 -0
  163. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/uv_sync.py +0 -0
  164. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/validation.py +0 -0
  165. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/src/utils/video_processing.py +0 -0
  166. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/__init__.py +0 -0
  167. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/_lakehouse_fakes.py +0 -0
  168. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/conftest.py +0 -0
  169. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/__init__.py +0 -0
  170. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  171. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/conftest.py +0 -0
  172. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/content/__init__.py +0 -0
  173. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/__init__.py +0 -0
  174. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/conftest.py +0 -0
  175. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  176. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  177. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_llm_runner.py +0 -0
  178. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  179. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_regex_runner.py +0 -0
  180. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_runner_hardening.py +0 -0
  181. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/custom/test_transformer_runners.py +0 -0
  182. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/__init__.py +0 -0
  183. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/conftest.py +0 -0
  184. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/sample_invoice.pdf +0 -0
  185. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/test_pii_chunking.py +0 -0
  186. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/test_pii_detector.py +0 -0
  187. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  188. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/pii/test_pii_severity.py +0 -0
  189. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/secrets/__init__.py +0 -0
  190. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  191. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  192. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_base_detector.py +0 -0
  193. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  194. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  195. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_detector_pipeline_types.py +0 -0
  196. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_detector_schema_examples.py +0 -0
  197. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_detector_types.py +0 -0
  198. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_phase2_detectors.py +0 -0
  199. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/test_registry.py +0 -0
  200. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/threat/__init__.py +0 -0
  201. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/threat/test_code_security_detector.py +0 -0
  202. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/detectors/threat/test_yara_detector.py +0 -0
  203. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  204. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/integration/test_wordpress_links_assets.py +0 -0
  205. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/pipeline/test_detector_outcomes.py +0 -0
  206. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/pipeline/test_detector_pipeline.py +0 -0
  207. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/pipeline/test_worker_pool.py +0 -0
  208. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_assets_metadata_catalog.py +0 -0
  209. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_azure_blob_storage_source.py +0 -0
  210. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_base_source_attachment.py +0 -0
  211. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_base_source_sampling.py +0 -0
  212. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_config.py +0 -0
  213. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_confluence_source.py +0 -0
  214. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_custom_extractor.py +0 -0
  215. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_databricks_source.py +0 -0
  216. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_delta_lake_source.py +0 -0
  217. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_dropbox_auth_command.py +0 -0
  218. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_dropbox_source.py +0 -0
  219. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_elasticsearch_source.py +0 -0
  220. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_email_source.py +0 -0
  221. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_file_evaluation_runner.py +0 -0
  222. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_google_cloud_storage_source.py +0 -0
  223. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_google_workspace_source.py +0 -0
  224. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_hashing.py +0 -0
  225. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_hive_source.py +0 -0
  226. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_iceberg_source.py +0 -0
  227. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_jira_source.py +0 -0
  228. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_kafka_source.py +0 -0
  229. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_local_folder_source.py +0 -0
  230. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_main.py +0 -0
  231. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_meilisearch_source.py +0 -0
  232. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_microsoft_365_source.py +0 -0
  233. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_mongodb_source.py +0 -0
  234. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_mssql_source.py +0 -0
  235. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_mysql_source.py +0 -0
  236. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_neo4j_source.py +0 -0
  237. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_opensearch_source.py +0 -0
  238. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_oracle_source.py +0 -0
  239. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_outputs.py +0 -0
  240. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_postgresql_source.py +0 -0
  241. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_powerbi_source.py +0 -0
  242. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_recipe_normalizer.py +0 -0
  243. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_s3_compatible_storage_source.py +0 -0
  244. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_sampling_automatic.py +0 -0
  245. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_sandbox_source.py +0 -0
  246. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_servicedesk_source.py +0 -0
  247. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_snowflake_source.py +0 -0
  248. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_source_dependency_groups.py +0 -0
  249. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_sqlite_source.py +0 -0
  250. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_tableau_source.py +0 -0
  251. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_tabular_automatic_sampling.py +0 -0
  252. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_tabular_utils.py +0 -0
  253. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_uv_sync.py +0 -0
  254. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_wordpress_source.py +0 -0
  255. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_youtube_source.py +0 -0
  256. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/test_youtube_source_integration.py +0 -0
  257. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_archive_extraction.py +0 -0
  258. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_content_extraction.py +0 -0
  259. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_embedded_images.py +0 -0
  260. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_file_metadata.py +0 -0
  261. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_file_to_images.py +0 -0
  262. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_legacy_office.py +0 -0
  263. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_transcription.py +0 -0
  264. {classifyre_cli-0.4.71 → classifyre_cli-0.4.72}/tests/utils/test_video_processing.py +0 -0
@@ -1,3 +1,3 @@
1
1
  $ uv sync
2
- Resolved 302 packages in 216ms
2
+ Resolved 303 packages in 187ms
3
3
  Checked 51 packages in 1ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: classifyre-cli
3
- Version: 0.4.71
3
+ Version: 0.4.72
4
4
  Summary: Classifyre CLI — scan and classify unstructured data sources
5
5
  License: MIT
6
6
  Keywords: data,ingestion,metadata,pii,secrets,unstructured
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classifyre/cli",
3
- "version": "0.4.71",
3
+ "version": "0.4.72",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "uv sync",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "classifyre-cli"
3
- version = "0.4.71"
3
+ version = "0.4.72"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -230,6 +230,9 @@ google-cloud-storage = [
230
230
  dropbox = [
231
231
  "dropbox>=12.2.1,<13.0.0",
232
232
  ]
233
+ slack = [
234
+ "slack-sdk>=3.43.0,<4.0.0",
235
+ ]
233
236
  email = [
234
237
  "imap-tools>=1.10.0,<2.0.0",
235
238
  ]
@@ -107,7 +107,7 @@ class PIIDetector(BaseDetector):
107
107
  # Italy
108
108
  "IT_FISCAL_CODE",
109
109
  "IT_DRIVER_LICENSE",
110
- "IT_VAR_CODE",
110
+ "IT_VAT_CODE",
111
111
  "IT_PASSPORT",
112
112
  "IT_IDENTITY_CARD",
113
113
  # Singapore
@@ -314,6 +314,7 @@ class PIIDetector(BaseDetector):
314
314
  else:
315
315
  self.analyzer = AnalyzerEngine()
316
316
 
317
+ self._register_regional_recognizers(presidio_module)
317
318
  self._register_custom_recognizers(presidio_module)
318
319
  self._probe_phone_recognizer()
319
320
 
@@ -396,6 +397,129 @@ class PIIDetector(BaseDetector):
396
397
  logger.debug("Loaded spaCy model '%s'", cfg_model)
397
398
  return nlp_engine
398
399
 
400
+ # Regional recognizers Presidio ships but scopes to their own language, so an
401
+ # `supported_languages=["en"]` registry silently drops them (the "Recognizer
402
+ # not added to registry" warning is filtered out by _PresidioNoiseFilter).
403
+ # Every entity advertised in _ALL_SUPPORTED_ENTITIES has to be re-registered
404
+ # against "en" or it can never produce a finding, no matter what the preset
405
+ # asks for. Analysis always runs with language="en".
406
+ _REGIONAL_RECOGNIZER_CLASS_NAMES: ClassVar[tuple[str, ...]] = (
407
+ # Spain
408
+ "EsNifRecognizer",
409
+ "EsNieRecognizer",
410
+ # Italy
411
+ "ItFiscalCodeRecognizer",
412
+ "ItDriverLicenseRecognizer",
413
+ "ItVatCodeRecognizer",
414
+ "ItPassportRecognizer",
415
+ "ItIdentityCardRecognizer",
416
+ # Singapore
417
+ "SgFinRecognizer",
418
+ "SgUenRecognizer",
419
+ # Australia
420
+ "AuAbnRecognizer",
421
+ "AuAcnRecognizer",
422
+ "AuTfnRecognizer",
423
+ "AuMedicareRecognizer",
424
+ # India
425
+ "InPanRecognizer",
426
+ "InAadhaarRecognizer",
427
+ "InVehicleRegistrationRecognizer",
428
+ "InVoterRecognizer",
429
+ # Finland
430
+ "FiPersonalIdentityCodeRecognizer",
431
+ # Poland
432
+ "PlPeselRecognizer",
433
+ )
434
+
435
+ # DACH and EU identifiers Presidio has no recognizer for at all. Each pattern
436
+ # is deliberately anchored on a checkable structure and paired with context
437
+ # words, because a bare run of digits is far too common to flag on its own.
438
+ #
439
+ # Scores are chosen against Presidio's LemmaContextAwareEnhancer, which adds
440
+ # 0.35 when a context word sits near the match, and the detector's default
441
+ # confidence_threshold of 0.7. A structurally weak pattern therefore scores
442
+ # 0.4: on its own it stays below the threshold and is suppressed, and only a
443
+ # nearby context word lifts it to 0.75 and reports it. Anything lower could
444
+ # never surface at all; anything higher would fire on bare digit runs.
445
+ _DACH_PATTERN_SPECS: ClassVar[
446
+ tuple[tuple[str, str, tuple[tuple[str, str, float], ...], tuple[str, ...]], ...]
447
+ ] = (
448
+ (
449
+ "AT_SVNR",
450
+ "Austrian social insurance number (SVNR)",
451
+ # NNNN DDMMYY — a 4-digit serial (last digit is a check digit)
452
+ # followed by the holder's date of birth.
453
+ (("svnr (needs context)", r"\b\d{4}[ /]?\d{2}[01]\d{3}\b", 0.4),),
454
+ ("svnr", "sozialversicherung", "sozialversicherungsnummer", "versicherungsnummer"),
455
+ ),
456
+ (
457
+ "CH_AHV",
458
+ "Swiss social security number (AHV/AVS)",
459
+ # Always begins with the 756 country prefix, so this one is safe to
460
+ # score highly on structure alone.
461
+ (("ahv (strong)", r"\b756[.\s]?\d{4}[.\s]?\d{4}[.\s]?\d{2}\b", 0.7),),
462
+ ("ahv", "avs", "ahv-nummer", "sozialversicherung", "versichertennummer"),
463
+ ),
464
+ (
465
+ "DE_TAX_ID",
466
+ "German tax identification number (Steuer-IdNr)",
467
+ # 11 digits, never leading zero.
468
+ (("steuer-idnr (needs context)", r"\b[1-9]\d{2}[ /]?\d{4}[ /]?\d{4}\b", 0.4),),
469
+ ("steuer", "steuernummer", "steuer-idnr", "steueridentifikationsnummer", "tax id"),
470
+ ),
471
+ (
472
+ "EU_NATIONAL_ID",
473
+ "Generic EU national identity number",
474
+ (("eu national id (needs context)", r"\b[A-Z]{2}[ -]?\d{6,12}\b", 0.4),),
475
+ ("national id", "identity card", "personalausweis", "ausweisnummer", "id number"),
476
+ ),
477
+ )
478
+
479
+ def _register_regional_recognizers(self, presidio_module: Any) -> None:
480
+ """Re-register language-scoped and missing regional recognizers under "en"."""
481
+ if self.analyzer is None:
482
+ return
483
+
484
+ predefined = importlib.import_module("presidio_analyzer.predefined_recognizers")
485
+ already_supported = set(self.analyzer.get_supported_entities())
486
+
487
+ for class_name in self._REGIONAL_RECOGNIZER_CLASS_NAMES:
488
+ recognizer_class = getattr(predefined, class_name, None)
489
+ if recognizer_class is None:
490
+ logger.debug("Presidio recognizer %s not available; skipping", class_name)
491
+ continue
492
+ try:
493
+ recognizer = recognizer_class(supported_language="en")
494
+ except Exception as exc:
495
+ logger.debug("Could not instantiate %s: %s", class_name, exc)
496
+ continue
497
+ # Never double-register: a duplicate recognizer reports every match twice.
498
+ if already_supported.issuperset(recognizer.supported_entities):
499
+ continue
500
+ self.analyzer.registry.add_recognizer(recognizer)
501
+ already_supported.update(recognizer.supported_entities)
502
+
503
+ PatternRecognizer = presidio_module.PatternRecognizer # noqa: N806
504
+ Pattern = presidio_module.Pattern # noqa: N806
505
+
506
+ for entity, name, pattern_specs, context in self._DACH_PATTERN_SPECS:
507
+ if entity in already_supported:
508
+ continue
509
+ self.analyzer.registry.add_recognizer(
510
+ PatternRecognizer(
511
+ supported_entity=entity,
512
+ name=name,
513
+ supported_language="en",
514
+ patterns=[
515
+ Pattern(name=pattern_name, regex=regex, score=score)
516
+ for pattern_name, regex, score in pattern_specs
517
+ ],
518
+ context=list(context),
519
+ )
520
+ )
521
+ already_supported.add(entity)
522
+
399
523
  def _register_custom_recognizers(self, presidio_module: Any) -> None:
400
524
  """Add ad-hoc recognizers from config to the analyzer registry."""
401
525
  custom_recognizers = getattr(self.config, "custom_recognizers", None) or []
@@ -826,7 +950,7 @@ class PIIDetector(BaseDetector):
826
950
  "AU_ABN",
827
951
  "AU_ACN",
828
952
  "SG_UEN",
829
- "IT_VAR_CODE",
953
+ "IT_VAT_CODE",
830
954
  "IN_VOTER",
831
955
  "IN_VEHICLE_REGISTRATION",
832
956
  }:
@@ -258,7 +258,7 @@ class SecretsEnabledPattern(StrEnum):
258
258
 
259
259
  class PIIEnabledPattern(StrEnum):
260
260
  """
261
- Presidio entity types for PII detection. Global: CREDIT_CARD, CRYPTO, DATE_TIME, EMAIL_ADDRESS, IBAN_CODE, IP_ADDRESS, NRP, LOCATION, PERSON, PHONE_NUMBER, MEDICAL_LICENSE, URL. USA: US_BANK_NUMBER, US_DRIVER_LICENSE, US_ITIN, US_PASSPORT, US_SSN. UK: UK_NHS. Spain: ES_NIF, ES_NIE. Italy: IT_FISCAL_CODE, IT_DRIVER_LICENSE, IT_VAR_CODE, IT_PASSPORT, IT_IDENTITY_CARD. Singapore: SG_NRIC_FIN, SG_UEN. Australia: AU_ABN, AU_ACN, AU_TFN, AU_MEDICARE. India: IN_PAN, IN_AADHAAR, IN_VEHICLE_REGISTRATION, IN_VOTER. Finland: FI_PERSONAL_IDENTITY_CODE. Poland: PL_PESEL. DACH: AT_SVNR, CH_AHV, DE_TAX_ID, EU_NATIONAL_ID.
261
+ Presidio entity types for PII detection. Global: CREDIT_CARD, CRYPTO, DATE_TIME, EMAIL_ADDRESS, IBAN_CODE, IP_ADDRESS, NRP, LOCATION, PERSON, PHONE_NUMBER, MEDICAL_LICENSE, URL. USA: US_BANK_NUMBER, US_DRIVER_LICENSE, US_ITIN, US_PASSPORT, US_SSN. UK: UK_NHS. Spain: ES_NIF, ES_NIE. Italy: IT_FISCAL_CODE, IT_DRIVER_LICENSE, IT_VAT_CODE, IT_PASSPORT, IT_IDENTITY_CARD. Singapore: SG_NRIC_FIN, SG_UEN. Australia: AU_ABN, AU_ACN, AU_TFN, AU_MEDICARE. India: IN_PAN, IN_AADHAAR, IN_VEHICLE_REGISTRATION, IN_VOTER. Finland: FI_PERSONAL_IDENTITY_CODE. Poland: PL_PESEL. DACH: AT_SVNR, CH_AHV, DE_TAX_ID, EU_NATIONAL_ID.
262
262
  """
263
263
 
264
264
  CREDIT_CARD = 'CREDIT_CARD'
@@ -283,7 +283,7 @@ class PIIEnabledPattern(StrEnum):
283
283
  ES_NIE = 'ES_NIE'
284
284
  IT_FISCAL_CODE = 'IT_FISCAL_CODE'
285
285
  IT_DRIVER_LICENSE = 'IT_DRIVER_LICENSE'
286
- IT_VAR_CODE = 'IT_VAR_CODE'
286
+ IT_VAT_CODE = 'IT_VAT_CODE'
287
287
  IT_PASSPORT = 'IT_PASSPORT'
288
288
  IT_IDENTITY_CARD = 'IT_IDENTITY_CARD'
289
289
  SG_NRIC_FIN = 'SG_NRIC_FIN'
@@ -85,7 +85,7 @@ class PostStatus(StrEnum):
85
85
 
86
86
  class SlackChannelType(StrEnum):
87
87
  """
88
- Slack conversation types to include
88
+ Slack conversation types to include. public_channel and private_channel are named channels, mpim is a group direct message, im is a one-to-one direct message.
89
89
  """
90
90
 
91
91
  public_channel = 'public_channel'
@@ -439,104 +439,91 @@ class SlackRequired(BaseModel):
439
439
  extra='forbid',
440
440
  )
441
441
  workspace: str | None = Field(
442
- None, description='Slack workspace name or domain (for display and stable IDs)'
443
- )
444
-
445
-
446
- class SlackMaskedBotToken(BaseModel):
447
- model_config = ConfigDict(
448
- extra='forbid',
449
- )
450
- bot_token: str = Field(..., description='Slack bot token (starts with xoxb-)')
451
-
452
-
453
- class SlackMaskedUserToken(BaseModel):
454
- model_config = ConfigDict(
455
- extra='forbid',
442
+ None,
443
+ description='Slack workspace name or domain, used for permalinks and stable asset IDs (example: acme or acme.slack.com)',
456
444
  )
457
- user_token: str = Field(..., description='Slack user token (starts with xoxp-)')
458
445
 
459
446
 
460
- class TokenType(StrEnum):
447
+ class SlackMasked(BaseModel):
461
448
  """
462
- Token type hint
449
+ Bot-token authentication. Install the app into the workspace and copy its bot token; user tokens are not supported.
463
450
  """
464
451
 
465
- bot = 'bot'
466
- user = 'user'
467
-
468
-
469
- class SlackMaskedToken(BaseModel):
470
452
  model_config = ConfigDict(
471
453
  extra='forbid',
472
454
  )
473
- token: str = Field(..., description='Slack token (bot or user)')
474
- token_type: TokenType | None = Field(None, description='Token type hint')
455
+ bot_token: str = Field(
456
+ ...,
457
+ description='Slack bot token, starts with xoxb-. Needs the scopes channels:read, groups:read, im:read, mpim:read, channels:history, groups:history, im:history, mpim:history, users:read and files:read.',
458
+ )
475
459
 
476
460
 
477
461
  class SlackOptionalChannels(BaseModel):
478
462
  """
479
- Channel discovery and targeting controls.
463
+ Which conversations to discover and scan.
480
464
  """
481
465
 
482
466
  model_config = ConfigDict(
483
467
  extra='forbid',
484
468
  )
485
469
  channel_types: list[SlackChannelType] | None = Field(
486
- ['public_channel'],
487
- description='Slack conversation types to include when listing channels',
470
+ ['public_channel', 'private_channel', 'mpim', 'im'],
471
+ description='Conversation types to include when listing channels. The bot only sees private channels and group DMs it has been invited to.',
488
472
  )
489
473
  channel_ids: list[str] | None = Field(
490
474
  None,
491
- description='Explicit channel IDs to scan. If provided, channel_types is ignored.',
475
+ description='Explicit conversation IDs to scan (example: C0123456789). When set, channel_types is ignored.',
492
476
  )
493
477
  exclude_archived: bool | None = Field(
494
- True, description='Exclude archived channels when listing'
478
+ True, description='Skip archived channels when listing'
479
+ )
480
+ auto_join_public_channels: bool | None = Field(
481
+ False,
482
+ description='Join public channels the bot is not a member of before reading them. conversations.history returns not_in_channel otherwise. Requires the channels:join scope.',
495
483
  )
496
484
 
497
485
 
498
486
  class SlackOptionalTimeRange(BaseModel):
499
487
  """
500
- Time window filters for message ingestion.
488
+ Limit ingestion to messages inside a time window.
501
489
  """
502
490
 
503
491
  model_config = ConfigDict(
504
492
  extra='forbid',
505
493
  )
506
494
  oldest: str | None = Field(
507
- None, description='Start of date range (Slack timestamp or ISO 8601)'
495
+ None,
496
+ description='Start of the range, as a Slack timestamp or an ISO 8601 datetime',
508
497
  )
509
498
  latest: str | None = Field(
510
- None, description='End of date range (Slack timestamp or ISO 8601)'
499
+ None,
500
+ description='End of the range, as a Slack timestamp or an ISO 8601 datetime',
511
501
  )
512
502
 
513
503
 
514
504
  class SlackOptionalIngestion(BaseModel):
515
505
  """
516
- Throughput and payload controls for Slack ingestion.
506
+ Throughput and message-content controls.
517
507
  """
518
508
 
519
509
  model_config = ConfigDict(
520
510
  extra='forbid',
521
511
  )
522
512
  batch_size: int | None = Field(
523
- 200, description='Messages per API call (max 200)', ge=1, le=200
513
+ 200,
514
+ description='Messages requested per API call (Slack caps this at 200)',
515
+ ge=1,
516
+ le=200,
524
517
  )
525
518
  rate_limit_delay_seconds: float | None = Field(
526
- 1, description='Delay between API calls to avoid rate limits', ge=0.0
519
+ 1,
520
+ description="Extra pause between API calls. Slack's 429 responses are retried automatically; this only smooths out sustained traffic.",
521
+ ge=0.0,
527
522
  )
528
523
  include_thread_replies: bool | None = Field(
529
- False, description='Include thread replies in fetched content for detectors'
530
- )
531
-
532
-
533
- class SlackOptional(BaseModel):
534
- model_config = ConfigDict(
535
- extra='forbid',
524
+ True,
525
+ description="Fetch each thread's replies and scan them alongside the parent message. Costs one extra API call per threaded message.",
536
526
  )
537
- channels: SlackOptionalChannels | None = None
538
- time_range: SlackOptionalTimeRange | None = None
539
- ingestion: SlackOptionalIngestion | None = None
540
527
 
541
528
 
542
529
  class ObjectStorageOptionalScope(BaseModel):
@@ -2130,30 +2117,6 @@ class CoreInput(BaseModel):
2130
2117
  )
2131
2118
 
2132
2119
 
2133
- class SlackInput(CoreInput):
2134
- type: Literal['SLACK'] | None = Field(
2135
- None, description='Type of the asset or source'
2136
- )
2137
- required: SlackRequired
2138
- masked: SlackMaskedBotToken | SlackMaskedUserToken | SlackMaskedToken = Field(
2139
- ..., title='SlackMasked'
2140
- )
2141
- optional: SlackOptional | None = None
2142
- detectors: list[Detector] | None = Field(
2143
- None, description='Detectors to run on ingested content'
2144
- )
2145
- custom_detectors: list[CustomDetectorSelection] | None = Field(
2146
- None,
2147
- description='Reusable custom detector IDs selected from the custom detector catalog.',
2148
- )
2149
- sampling: SamplingConfig
2150
- resources: ResourceOverrides | None = None
2151
- cleanup_removed_detector_findings: bool | None = Field(
2152
- True,
2153
- description='When enabled (default), findings produced by detectors that are no longer configured on this source (removed or disabled) are automatically resolved at the start of the next run, keeping the findings list in step with the current detector set.',
2154
- )
2155
-
2156
-
2157
2120
  class EmailInput(CoreInput):
2158
2121
  type: Literal['EMAIL'] | None = Field(
2159
2122
  None, description='Type of the asset or source'
@@ -4437,6 +4400,32 @@ class DropboxInput(CoreInput):
4437
4400
  )
4438
4401
 
4439
4402
 
4403
+ class SlackOptionalAttachments(BaseModel):
4404
+ """
4405
+ Files shared in messages. Each file becomes its own asset, and its text is extracted with the same parser used for local files (PDF, Office, images via OCR, audio and video transcription).
4406
+ """
4407
+
4408
+ model_config = ConfigDict(
4409
+ extra='forbid',
4410
+ )
4411
+ include_attachments: bool | None = Field(
4412
+ True, description='Emit files shared in messages as scannable assets'
4413
+ )
4414
+ max_attachment_bytes: int | None = Field(
4415
+ 26214400,
4416
+ description='Skip files larger than this many bytes (default 25 MB)',
4417
+ ge=1024,
4418
+ le=524288000,
4419
+ )
4420
+ include_file_extensions: list[str] | None = Field(
4421
+ None,
4422
+ description='Only download files with these extensions (example: .pdf, .docx). Empty means all extensions.',
4423
+ )
4424
+ exclude_file_extensions: list[str] | None = Field(
4425
+ None, description='Skip files with these extensions (example: .mp4, .zip)'
4426
+ )
4427
+
4428
+
4440
4429
  class YouTubeInput(CoreInput):
4441
4430
  type: Literal['YOUTUBE'] | None = Field(
4442
4431
  None, description='Type of the asset or source'
@@ -4459,6 +4448,38 @@ class YouTubeInput(CoreInput):
4459
4448
  )
4460
4449
 
4461
4450
 
4451
+ class SlackOptional(BaseModel):
4452
+ model_config = ConfigDict(
4453
+ extra='forbid',
4454
+ )
4455
+ channels: SlackOptionalChannels | None = None
4456
+ time_range: SlackOptionalTimeRange | None = None
4457
+ ingestion: SlackOptionalIngestion | None = None
4458
+ attachments: SlackOptionalAttachments | None = None
4459
+
4460
+
4461
+ class SlackInput(CoreInput):
4462
+ type: Literal['SLACK'] | None = Field(
4463
+ None, description='Type of the asset or source'
4464
+ )
4465
+ required: SlackRequired
4466
+ masked: SlackMasked
4467
+ optional: SlackOptional | None = None
4468
+ detectors: list[Detector] | None = Field(
4469
+ None, description='Detectors to run on ingested content'
4470
+ )
4471
+ custom_detectors: list[CustomDetectorSelection] | None = Field(
4472
+ None,
4473
+ description='Reusable custom detector IDs selected from the custom detector catalog.',
4474
+ )
4475
+ sampling: SamplingConfig
4476
+ resources: ResourceOverrides | None = None
4477
+ cleanup_removed_detector_findings: bool | None = Field(
4478
+ True,
4479
+ description='When enabled (default), findings produced by detectors that are no longer configured on this source (removed or disabled) are automatically resolved at the start of the next run, keeping the findings list in step with the current detector set.',
4480
+ )
4481
+
4482
+
4462
4483
  class DeltaLakeOptional(BaseModel):
4463
4484
  model_config = ConfigDict(
4464
4485
  extra='forbid',
@@ -228,6 +228,12 @@ class NotionSource(BaseSource):
228
228
  self._external_file_url_by_hash: dict[str, str] = {}
229
229
  self._notion_signed_url_by_hash: dict[str, str] = {}
230
230
  self._file_block_id_by_hash: dict[str, str] = {}
231
+ # Asset IDs whose bytes have already been downloaded and put through the
232
+ # full extraction pipeline. Read by ParsedContentProvider and
233
+ # DetectorPipeline to suppress their fallback path — without it every
234
+ # asset that yields no text (an icon, a cover, a diagram) is downloaded
235
+ # and OCR'd a second time.
236
+ self._content_pages_processed: set[str] = set()
231
237
 
232
238
  # ------------------------------------------------------------------ config
233
239
  def _optional(self) -> NotionOptional:
@@ -297,6 +303,7 @@ class NotionSource(BaseSource):
297
303
  self._notion_signed_url_by_hash = {}
298
304
  self._file_block_id_by_hash = {}
299
305
  self._attachment_name_by_hash = {}
306
+ self._content_pages_processed = set()
300
307
 
301
308
  # --------------------------------------------------------------- discovery
302
309
  def _discover_refs(self) -> list[dict[str, Any]]:
@@ -826,6 +833,10 @@ class NotionSource(BaseSource):
826
833
  declared_mime_type=mime,
827
834
  file_name=self._attachment_file_name(asset_id, asset_id),
828
835
  )
836
+ # Record the asset as processed even when extraction produced nothing:
837
+ # the download and the extraction pass both already happened, and a
838
+ # second identical attempt costs exactly as much to return nothing again.
839
+ self._content_pages_processed.add(asset_id)
829
840
  if parsed.text_content:
830
841
  self._asset_content_cache[asset_id] = (parsed.raw_content, parsed.text_content)
831
842
  return parsed.raw_content, parsed.text_content