footprinter-cli 1.0.0rc2__tar.gz → 1.0.0rc4__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 (173) hide show
  1. {footprinter_cli-1.0.0rc2/footprinter_cli.egg-info → footprinter_cli-1.0.0rc4}/PKG-INFO +27 -21
  2. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/README.md +12 -13
  3. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/access.py +27 -14
  4. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/__init__.py +0 -7
  5. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_common.py +5 -0
  6. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/ingest.py +30 -5
  7. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/setup.py +1 -166
  8. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/status.py +0 -18
  9. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/orchestrator.py +6 -0
  10. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/pipe_runner.py +15 -3
  11. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/db.py +2 -12
  12. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/errors.py +1 -4
  13. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/status.py +1 -5
  14. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/paths.py +2 -28
  15. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/permissions.py +16 -8
  16. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/visibility.py +16 -8
  17. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4/footprinter_cli.egg-info}/PKG-INFO +27 -21
  18. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/SOURCES.txt +3 -13
  19. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/pyproject.toml +17 -9
  20. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_recalculate.py +237 -0
  21. footprinter_cli-1.0.0rc4/tests/test_bundled.py +108 -0
  22. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_e2e_install.py +4 -8
  23. footprinter_cli-1.0.0rc4/tests/test_examples.py +84 -0
  24. footprinter_cli-1.0.0rc4/tests/test_paths_no_test_marker.py +59 -0
  25. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_security_layer.py +116 -0
  26. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_security_permissions.py +148 -0
  27. footprinter_cli-1.0.0rc2/footprinter/bundled/samples/hidden-client-file-sample.txt +0 -2
  28. footprinter_cli-1.0.0rc2/footprinter/bundled/samples/opaque-project-file-sample.txt +0 -2
  29. footprinter_cli-1.0.0rc2/footprinter/bundled/samples/visible-file-sample.txt +0 -2
  30. footprinter_cli-1.0.0rc2/footprinter/cli/_sample_seed.py +0 -204
  31. footprinter_cli-1.0.0rc2/tests/test_bundled.py +0 -92
  32. footprinter_cli-1.0.0rc2/tests/test_dependency_stripping.py +0 -289
  33. footprinter_cli-1.0.0rc2/tests/test_doc_paths.py +0 -35
  34. footprinter_cli-1.0.0rc2/tests/test_old_api_cleanup.py +0 -132
  35. footprinter_cli-1.0.0rc2/tests/test_pyproject_metadata.py +0 -162
  36. footprinter_cli-1.0.0rc2/tests/test_release_governance.py +0 -1168
  37. footprinter_cli-1.0.0rc2/tests/test_snapshot_manifest.py +0 -560
  38. footprinter_cli-1.0.0rc2/tests/test_swe_839_cleanup.py +0 -19
  39. footprinter_cli-1.0.0rc2/tests/test_tool_scope_regressions.py +0 -264
  40. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/LICENSE +0 -0
  41. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/__init__.py +0 -0
  42. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/__init__.py +0 -0
  43. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/db.py +0 -0
  44. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/entities.py +0 -0
  45. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/search.py +0 -0
  46. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/semantic.py +0 -0
  47. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/server.py +0 -0
  48. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/status.py +0 -0
  49. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/__init__.py +0 -0
  50. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/config.example.yaml +0 -0
  51. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/context_patterns.yaml +0 -0
  52. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/extensions.yaml +0 -0
  53. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/filename_patterns.yaml +0 -0
  54. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/mime_mappings.yaml +0 -0
  55. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/salesforce_rules.yaml +0 -0
  56. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/security_patterns.yaml +0 -0
  57. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/__main__.py +0 -0
  58. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_policy_helpers.py +0 -0
  59. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_prompt.py +0 -0
  60. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/api_cmd.py +0 -0
  61. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/connect.py +0 -0
  62. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/data.py +0 -0
  63. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/delete.py +0 -0
  64. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/mcp_cmd.py +0 -0
  65. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/mcp_setup.py +0 -0
  66. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/search.py +0 -0
  67. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/search_cmd.py +0 -0
  68. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/status_cmd.py +0 -0
  69. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/upsert.py +0 -0
  70. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/vectorize_cmd.py +0 -0
  71. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/view.py +0 -0
  72. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/connectors/__init__.py +0 -0
  73. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/connectors/config_utils.py +0 -0
  74. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/__init__.py +0 -0
  75. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/browser.py +0 -0
  76. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/chats.py +0 -0
  77. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/clients.py +0 -0
  78. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/emails.py +0 -0
  79. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/files.py +0 -0
  80. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/folders.py +0 -0
  81. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/messages.py +0 -0
  82. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/policies.py +0 -0
  83. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/projects.py +0 -0
  84. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/search.py +0 -0
  85. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/sql_utils.py +0 -0
  86. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/status.py +0 -0
  87. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/uploads.py +0 -0
  88. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/__init__.py +0 -0
  89. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/__init__.py +0 -0
  90. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/browser.py +0 -0
  91. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/chat.py +0 -0
  92. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/ingest.py +0 -0
  93. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/local_files.py +0 -0
  94. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/local_folders.py +0 -0
  95. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/protocol.py +0 -0
  96. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/browser_indexer.py +0 -0
  97. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_dedup.py +0 -0
  98. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_indexer.py +0 -0
  99. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/__init__.py +0 -0
  100. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/chatgpt_parser.py +0 -0
  101. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/claude_parser.py +0 -0
  102. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/cli.py +0 -0
  103. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/content_extractors.py +0 -0
  104. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/database.py +0 -0
  105. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/__init__.py +0 -0
  106. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/connector_schema.py +0 -0
  107. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/migration.py +0 -0
  108. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/schema.py +0 -0
  109. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/security.py +0 -0
  110. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/file_indexer.py +0 -0
  111. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/file_scanner.py +0 -0
  112. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/folder_indexer.py +0 -0
  113. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/full_content_extractor.py +0 -0
  114. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/processing.py +0 -0
  115. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/registry.py +0 -0
  116. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/run_record.py +0 -0
  117. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/status.py +0 -0
  118. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/__init__.py +0 -0
  119. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/__main__.py +0 -0
  120. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/extraction.py +0 -0
  121. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/server.py +0 -0
  122. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/__init__.py +0 -0
  123. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/navigation.py +0 -0
  124. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/read.py +0 -0
  125. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/search.py +0 -0
  126. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/semantic.py +0 -0
  127. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/__init__.py +0 -0
  128. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/chunking.py +0 -0
  129. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/embeddings.py +0 -0
  130. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/hybrid_search.py +0 -0
  131. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/vector_store.py +0 -0
  132. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/__init__.py +0 -0
  133. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/access_service.py +0 -0
  134. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/chat_service.py +0 -0
  135. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/client_service.py +0 -0
  136. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/content_service.py +0 -0
  137. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/email_service.py +0 -0
  138. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/file_service.py +0 -0
  139. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/folder_service.py +0 -0
  140. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/includes.py +0 -0
  141. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/ingest_service.py +0 -0
  142. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/project_service.py +0 -0
  143. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/roles.py +0 -0
  144. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/search_service.py +0 -0
  145. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/semantic_service.py +0 -0
  146. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/status_service.py +0 -0
  147. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/visit_service.py +0 -0
  148. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/source_registry.py +0 -0
  149. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/__init__.py +0 -0
  150. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/hash_utils.py +0 -0
  151. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/logging_config.py +0 -0
  152. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/mime.py +0 -0
  153. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/text.py +0 -0
  154. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/time.py +0 -0
  155. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/dependency_links.txt +0 -0
  156. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/entry_points.txt +0 -0
  157. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/requires.txt +0 -0
  158. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/top_level.txt +0 -0
  159. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/setup.cfg +0 -0
  160. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_control_bypasses.py +0 -0
  161. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_control_docs.py +0 -0
  162. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_build_status_filter.py +0 -0
  163. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_e2e_pipeline.py +0 -0
  164. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_edit_recalculate.py +0 -0
  165. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_files_rename.py +0 -0
  166. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_files_surface.py +0 -0
  167. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_inherit_resolution.py +0 -0
  168. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_logging.py +0 -0
  169. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_no_project_root.py +0 -0
  170. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_package_init.py +0 -0
  171. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_pip_install_e2e.py +0 -0
  172. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_prompt_safety.py +0 -0
  173. {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_resolver.py +0 -0
@@ -1,22 +1,29 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: footprinter-cli
3
- Version: 1.0.0rc2
4
- Summary: Index your files, emails, browser history, and chats locally. Give AI agents controlled access via MCP.
3
+ Version: 1.0.0rc4
4
+ Summary: A local context layer for your files, browser history, chats, and email searchable, user-owned, MCP-served.
5
5
  Author: SwellCity Group
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/swellcitygroup/footprinter
8
8
  Project-URL: Repository, https://github.com/swellcitygroup/footprinter
9
9
  Project-URL: Issues, https://github.com/swellcitygroup/footprinter/issues
10
- Keywords: indexer,mcp,metadata,model-context-protocol,file-indexing,sqlite
11
- Classifier: Development Status :: 5 - Production/Stable
10
+ Project-URL: Documentation, https://github.com/swellcitygroup/footprinter/blob/main/README.md
11
+ Project-URL: Changelog, https://github.com/swellcitygroup/footprinter/blob/main/CHANGELOG.md
12
+ Keywords: indexer,mcp,metadata,model-context-protocol,file-indexing,sqlite,context-engineering,personal-information-management
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
12
15
  Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Intended Audience :: Science/Research
13
18
  Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: POSIX :: Linux
15
21
  Classifier: Programming Language :: Python :: 3.11
16
22
  Classifier: Programming Language :: Python :: 3.12
17
- Classifier: Topic :: System :: Archiving
18
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
- Classifier: Operating System :: MacOS :: MacOS X
23
+ Classifier: Topic :: Database
24
+ Classifier: Topic :: Text Processing :: Indexing
25
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
26
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
27
  Requires-Python: >=3.11
21
28
  Description-Content-Type: text/markdown
22
29
  License-File: LICENSE
@@ -47,11 +54,9 @@ Requires-Dist: httpx<1.0,>=0.27.0; extra == "dev"
47
54
 
48
55
  [![Tests](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml/badge.svg)](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml)
49
56
 
50
- **Index your files, conversations, browser history, and more through connector plugins. Search across everything. Give AI assistants structured context via [MCP](https://modelcontextprotocol.io/).**
57
+ **A local context layer for your files, browser history, chats, and email searchable, user-owned, and served to AI agents through [MCP](https://modelcontextprotocol.io/).**
51
58
 
52
- Every conversation with an AI assistant starts from zero. It doesn't know what files you've been editing, what you were researching yesterday, or what conversations you had last week. You either paste context in manually or the AI operates blind.
53
-
54
- Footprinter fixes that. It scans your work into a local SQLite database, links related items across sources, and exposes the result through MCP — so Claude Desktop, or any MCP-compatible assistant, can search your files, find related conversations, and understand the shape of your work. No copy-pasting. No cloud. Everything stays on your machine.
59
+ Your work lives across a filesystem, a browser, an inbox, a chat history, and whatever other tools you reach for. Footprinter indexes those sources into a single local store, organizes them into the projects and groupings *you* define, and serves the result to AI agents through a governed access layer. You control what the agent can see. Everything stays on your machine.
55
60
 
56
61
  ## Install
57
62
 
@@ -95,6 +100,7 @@ Once configured, Claude can search your files, browse projects, and find related
95
100
  | **Local files** | Path, type, size, timestamps, content hash |
96
101
  | **Browser history** | Safari and Chrome — URLs, titles, visit times |
97
102
  | **Chat exports** | Claude and ChatGPT conversation exports |
103
+ | **Email** | Subject, sender, recipients, body, timestamps — ingested via [connector plugins](#connectors) |
98
104
  | **Documents** | PDF, Word, Excel, PowerPoint content (with `[parse]` extra) |
99
105
  | **Semantic embeddings** | Conceptual similarity across all sources (with `[semantic]` extra) |
100
106
 
@@ -160,10 +166,10 @@ Sources are scanned into SQLite with bidirectional links connecting local files
160
166
 
161
167
  ## Documentation
162
168
 
163
- - [Interfaces](reference/interfaces.md) — CLI commands, MCP tools, Python API
164
- - [Data Model](reference/data-model.md) — database schema
165
- - [Pipeline](reference/pipeline.md) — indexing stages and configuration
166
- - [Access Control](reference/mcp-access-control.md) — MCP security model
169
+ - [Interfaces](https://github.com/swellcitygroup/footprinter/blob/main/reference/interfaces.md) — CLI commands, MCP tools, Python API
170
+ - [Data Model](https://github.com/swellcitygroup/footprinter/blob/main/reference/data-model.md) — database schema
171
+ - [Pipeline](https://github.com/swellcitygroup/footprinter/blob/main/reference/pipeline.md) — indexing stages and configuration
172
+ - [Access Control](https://github.com/swellcitygroup/footprinter/blob/main/reference/mcp-access-control.md) — MCP security model
167
173
 
168
174
  ## Contributing
169
175
 
@@ -200,7 +206,7 @@ pytest tests/ -v --tb=short
200
206
  4. Run the test suite
201
207
  5. Submit a PR targeting `main`
202
208
 
203
- Never commit API keys, tokens, or credentials. Report security vulnerabilities privately — see [SECURITY.md](SECURITY.md).
209
+ Never commit API keys, tokens, or credentials. Report security vulnerabilities privately — see [SECURITY.md](https://github.com/swellcitygroup/footprinter/blob/main/SECURITY.md).
204
210
 
205
211
  ### Pull request expectations
206
212
 
@@ -211,13 +217,13 @@ Never commit API keys, tokens, or credentials. Report security vulnerabilities p
211
217
 
212
218
  All PRs are reviewed by the maintainer. Expect reviews within one week. CI must pass before review begins.
213
219
 
214
- No Contributor License Agreement required. By submitting a PR, you agree your contribution is licensed under the project's [MIT License](LICENSE).
220
+ No Contributor License Agreement required. By submitting a PR, you agree your contribution is licensed under the project's [MIT License](https://github.com/swellcitygroup/footprinter/blob/main/LICENSE).
215
221
 
216
222
  ## Community
217
223
 
218
- - [Code of Conduct](CODE_OF_CONDUCT.md)
219
- - [Security Policy](SECURITY.md)
224
+ - [Code of Conduct](https://github.com/swellcitygroup/footprinter/blob/main/CODE_OF_CONDUCT.md)
225
+ - [Security Policy](https://github.com/swellcitygroup/footprinter/blob/main/SECURITY.md)
220
226
 
221
227
  ## License
222
228
 
223
- MIT — see [LICENSE](LICENSE).
229
+ MIT — see [LICENSE](https://github.com/swellcitygroup/footprinter/blob/main/LICENSE).
@@ -2,11 +2,9 @@
2
2
 
3
3
  [![Tests](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml/badge.svg)](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml)
4
4
 
5
- **Index your files, conversations, browser history, and more through connector plugins. Search across everything. Give AI assistants structured context via [MCP](https://modelcontextprotocol.io/).**
5
+ **A local context layer for your files, browser history, chats, and email searchable, user-owned, and served to AI agents through [MCP](https://modelcontextprotocol.io/).**
6
6
 
7
- Every conversation with an AI assistant starts from zero. It doesn't know what files you've been editing, what you were researching yesterday, or what conversations you had last week. You either paste context in manually or the AI operates blind.
8
-
9
- Footprinter fixes that. It scans your work into a local SQLite database, links related items across sources, and exposes the result through MCP — so Claude Desktop, or any MCP-compatible assistant, can search your files, find related conversations, and understand the shape of your work. No copy-pasting. No cloud. Everything stays on your machine.
7
+ Your work lives across a filesystem, a browser, an inbox, a chat history, and whatever other tools you reach for. Footprinter indexes those sources into a single local store, organizes them into the projects and groupings *you* define, and serves the result to AI agents through a governed access layer. You control what the agent can see. Everything stays on your machine.
10
8
 
11
9
  ## Install
12
10
 
@@ -50,6 +48,7 @@ Once configured, Claude can search your files, browse projects, and find related
50
48
  | **Local files** | Path, type, size, timestamps, content hash |
51
49
  | **Browser history** | Safari and Chrome — URLs, titles, visit times |
52
50
  | **Chat exports** | Claude and ChatGPT conversation exports |
51
+ | **Email** | Subject, sender, recipients, body, timestamps — ingested via [connector plugins](#connectors) |
53
52
  | **Documents** | PDF, Word, Excel, PowerPoint content (with `[parse]` extra) |
54
53
  | **Semantic embeddings** | Conceptual similarity across all sources (with `[semantic]` extra) |
55
54
 
@@ -115,10 +114,10 @@ Sources are scanned into SQLite with bidirectional links connecting local files
115
114
 
116
115
  ## Documentation
117
116
 
118
- - [Interfaces](reference/interfaces.md) — CLI commands, MCP tools, Python API
119
- - [Data Model](reference/data-model.md) — database schema
120
- - [Pipeline](reference/pipeline.md) — indexing stages and configuration
121
- - [Access Control](reference/mcp-access-control.md) — MCP security model
117
+ - [Interfaces](https://github.com/swellcitygroup/footprinter/blob/main/reference/interfaces.md) — CLI commands, MCP tools, Python API
118
+ - [Data Model](https://github.com/swellcitygroup/footprinter/blob/main/reference/data-model.md) — database schema
119
+ - [Pipeline](https://github.com/swellcitygroup/footprinter/blob/main/reference/pipeline.md) — indexing stages and configuration
120
+ - [Access Control](https://github.com/swellcitygroup/footprinter/blob/main/reference/mcp-access-control.md) — MCP security model
122
121
 
123
122
  ## Contributing
124
123
 
@@ -155,7 +154,7 @@ pytest tests/ -v --tb=short
155
154
  4. Run the test suite
156
155
  5. Submit a PR targeting `main`
157
156
 
158
- Never commit API keys, tokens, or credentials. Report security vulnerabilities privately — see [SECURITY.md](SECURITY.md).
157
+ Never commit API keys, tokens, or credentials. Report security vulnerabilities privately — see [SECURITY.md](https://github.com/swellcitygroup/footprinter/blob/main/SECURITY.md).
159
158
 
160
159
  ### Pull request expectations
161
160
 
@@ -166,13 +165,13 @@ Never commit API keys, tokens, or credentials. Report security vulnerabilities p
166
165
 
167
166
  All PRs are reviewed by the maintainer. Expect reviews within one week. CI must pass before review begins.
168
167
 
169
- No Contributor License Agreement required. By submitting a PR, you agree your contribution is licensed under the project's [MIT License](LICENSE).
168
+ No Contributor License Agreement required. By submitting a PR, you agree your contribution is licensed under the project's [MIT License](https://github.com/swellcitygroup/footprinter/blob/main/LICENSE).
170
169
 
171
170
  ## Community
172
171
 
173
- - [Code of Conduct](CODE_OF_CONDUCT.md)
174
- - [Security Policy](SECURITY.md)
172
+ - [Code of Conduct](https://github.com/swellcitygroup/footprinter/blob/main/CODE_OF_CONDUCT.md)
173
+ - [Security Policy](https://github.com/swellcitygroup/footprinter/blob/main/SECURITY.md)
175
174
 
176
175
  ## License
177
176
 
178
- MIT — see [LICENSE](LICENSE).
177
+ MIT — see [LICENSE](https://github.com/swellcitygroup/footprinter/blob/main/LICENSE).
@@ -59,7 +59,7 @@ ENTITY_META: dict[str, dict[str, Any]] = {
59
59
  "has_permissions": True,
60
60
  "has_status": True,
61
61
  "has_project_id": True,
62
- "has_client_id": False,
62
+ "has_client_id": True,
63
63
  "has_account": True,
64
64
  "path_column": "path",
65
65
  },
@@ -89,7 +89,7 @@ ENTITY_META: dict[str, dict[str, Any]] = {
89
89
  "has_permissions": False,
90
90
  "has_status": False,
91
91
  "has_project_id": True,
92
- "has_client_id": False,
92
+ "has_client_id": True,
93
93
  "has_account": False,
94
94
  "path_column": "path",
95
95
  },
@@ -219,27 +219,40 @@ def _get_ids_for_scope(conn: sqlite3.Connection, scope: str) -> dict[str, list[i
219
219
 
220
220
  if prefix == "client":
221
221
  client_id = int(value)
222
- result = {}
222
+ # Gather ids per entity type as dicts (insertion-ordered sets) so we
223
+ # can union the project cascade with direct client_id matches without
224
+ # double-stamping entities reachable via both paths.
225
+ id_sets: dict[str, dict[int, None]] = {}
223
226
  # The client itself
224
227
  row = conn.execute("SELECT id FROM clients WHERE id = ?", (client_id,)).fetchone()
225
228
  if row:
226
- result["client"] = [row["id"]]
229
+ id_sets["client"] = {row["id"]: None}
227
230
  # Projects under this client
228
231
  proj_rows = conn.execute("SELECT id FROM projects WHERE client_id = ?", (client_id,)).fetchall()
229
232
  proj_ids = [r["id"] for r in proj_rows]
230
233
  if proj_ids:
231
- result["project"] = proj_ids
232
- # Recurse: gather children of each project
234
+ id_sets["project"] = {pid: None for pid in proj_ids}
235
+ # Cascade: children of each project
233
236
  for pid in proj_ids:
234
- proj_children = _get_ids_for_scope(conn, f"project:{pid}")
235
- for etype, ids in proj_children.items():
237
+ for etype, ids in _get_ids_for_scope(conn, f"project:{pid}").items():
236
238
  if etype in ("project", "client"):
237
- continue # already handled
238
- if etype in result:
239
- result[etype].extend(ids)
240
- else:
241
- result[etype] = list(ids)
242
- return result
239
+ continue
240
+ id_sets.setdefault(etype, {}).update({i: None for i in ids})
241
+ # Direct: entities with a client_id FK of their own (files, folders,
242
+ # emails, chats). Union with the cascade; dedup via the dict keys.
243
+ for etype, meta in ENTITY_META.items():
244
+ if etype in ("client", "project"):
245
+ continue
246
+ if not meta["has_client_id"]:
247
+ continue
248
+ table = meta["table"]
249
+ where = "client_id = ?"
250
+ if meta["has_status"]:
251
+ where += " AND status != 'removed'"
252
+ rows = conn.execute(f"SELECT id FROM {table} WHERE {where}", (client_id,)).fetchall()
253
+ if rows:
254
+ id_sets.setdefault(etype, {}).update({r["id"]: None for r in rows})
255
+ return {etype: list(ids) for etype, ids in id_sets.items()}
243
256
 
244
257
  # Single entity: file:42, email:10, etc.
245
258
  if prefix in ENTITY_META:
@@ -29,15 +29,8 @@ def main(argv=None) -> None:
29
29
 
30
30
  if argv is None:
31
31
  argv = _sys.argv[1:]
32
- from footprinter.paths import is_test_mode
33
32
  from footprinter.source_registry import ConfigError as _ConfigError
34
33
 
35
- if is_test_mode():
36
- print(
37
- "\033[33m⚠ Test mode active — data at ~/.footprinter/.test-env/\033[0m",
38
- file=_sys.stderr,
39
- )
40
-
41
34
  from footprinter import __version__
42
35
  from footprinter.cli._common import FORMATTER
43
36
 
@@ -32,6 +32,11 @@ console = Console()
32
32
 
33
33
  class FootprinterHelpFormatter(argparse.RawDescriptionHelpFormatter):
34
34
  def _format_usage(self, usage, actions, groups, prefix):
35
+ # argparse calls this with prefix="" when computing subparser prog
36
+ # prefixes; returning the Usage-wrapped string there would compound
37
+ # "Usage: Usage: fp …" at every nesting level.
38
+ if prefix == "":
39
+ return self._prog
35
40
  return f"\nUsage: {self._prog}\n\n"
36
41
 
37
42
 
@@ -244,6 +244,7 @@ def _run_with_logging(
244
244
  orchestrator,
245
245
  *,
246
246
  pipes=None,
247
+ refresh_source=None,
247
248
  mode,
248
249
  quiet=False,
249
250
  verbose=False,
@@ -253,7 +254,11 @@ def _run_with_logging(
253
254
  ):
254
255
  """Shared run helper: Rich Progress, file logging, run record, cleanup.
255
256
 
256
- If ``pipes`` is not provided, defaults to the ``"all"`` pipeline.
257
+ Dispatch target:
258
+ - ``refresh_source`` set → orchestrator.run_refresh(refresh_source)
259
+ - ``pipes`` set → orchestrator.run_pipes(pipes)
260
+ - neither → orchestrator.run_pipeline("all")
261
+
257
262
  Shows a stage counter ("Stage 2/5: local_files") and intra-stage
258
263
  progress counts for adapters that report them via on_progress.
259
264
  """
@@ -299,8 +304,11 @@ def _run_with_logging(
299
304
  )
300
305
 
301
306
  # Resolve stage list for counter display
302
- stage_list = pipes
303
- if stage_list is None:
307
+ if refresh_source is not None:
308
+ stage_list = orchestrator.refresh_pipes.get(refresh_source, [])
309
+ elif pipes is not None:
310
+ stage_list = pipes
311
+ else:
304
312
  stage_list = orchestrator.runner.pipelines.get("all", [])
305
313
  total_stages = len(stage_list)
306
314
  stage_index = [0] # mutable counter for closures
@@ -381,7 +389,14 @@ def _run_with_logging(
381
389
  detail_part = f" {details}" if details else ""
382
390
  console.print(f" {icon} {stage}{detail_part} [dim]({elapsed:.1f}s)[/dim]")
383
391
 
384
- if pipes:
392
+ if refresh_source is not None:
393
+ orchestrator.run_refresh(
394
+ refresh_source,
395
+ on_pipe_start=on_start,
396
+ on_pipe_end=on_end,
397
+ on_progress=on_progress,
398
+ )
399
+ elif pipes:
385
400
  orchestrator.run_pipes(
386
401
  pipes,
387
402
  on_pipe_start=on_start,
@@ -439,6 +454,13 @@ def _ingest_pipeline(args) -> None:
439
454
  verbose = getattr(args, "verbose", False)
440
455
  mode_str = "full" if orchestrator.full_mode else "incremental"
441
456
 
457
+ if pipes is not None:
458
+ try:
459
+ orchestrator.runner.validate_pipes(pipes)
460
+ except ValueError as e:
461
+ console.print(f"[red]Error:[/red] {e}")
462
+ sys.exit(1)
463
+
442
464
  try:
443
465
  _run_with_logging(
444
466
  orchestrator,
@@ -526,6 +548,7 @@ def _ingest_refresh(args) -> None:
526
548
  source = args.source
527
549
  valid_sources = list(refresh_pipes.keys())
528
550
 
551
+ # Early source validation (before lock/log setup) for a clean exit on bad input.
529
552
  if source not in refresh_pipes:
530
553
  console.print(f"[red]Error:[/red] Unknown refresh source: {source}")
531
554
  console.print(f"Valid sources: {', '.join(valid_sources)}")
@@ -538,9 +561,11 @@ def _ingest_refresh(args) -> None:
538
561
  mode_str = "full" if orchestrator.full_mode else "incremental"
539
562
 
540
563
  try:
564
+ # Route through orchestrator.run_refresh so access_resolution (a POST_PIPE)
565
+ # runs inline — run_pipes rejects POST_PIPES by design.
541
566
  _run_with_logging(
542
567
  orchestrator,
543
- pipes=stages,
568
+ refresh_source=source,
544
569
  mode=mode_str,
545
570
  quiet=quiet,
546
571
  verbose=verbose,
@@ -7,8 +7,6 @@ Usage:
7
7
  fp setup --check # Validate existing configuration
8
8
  fp setup --hooks # Install git hooks (sets core.hooksPath)
9
9
  fp setup --reset # Clear data and re-run wizard
10
- fp setup --seed-samples # Load sample data for exploration
11
- fp setup --clear-samples # Remove sample data
12
10
  """
13
11
 
14
12
  import argparse
@@ -50,15 +48,11 @@ from footprinter.cli._prompt import (
50
48
  from footprinter.cli.ingest import _run_with_logging
51
49
  from footprinter.ingest.orchestrator import DataPipelineOrchestrator
52
50
  from footprinter.paths import (
53
- _TEST_ENV_NAME,
54
- _TEST_MARKER_NAME,
55
51
  get_bundled_path,
56
52
  get_chroma_path,
57
53
  get_config_path,
58
54
  get_db_path,
59
55
  get_log_path,
60
- get_real_home,
61
- is_test_mode,
62
56
  )
63
57
  from footprinter.source_registry import ConfigError, get_config
64
58
 
@@ -196,10 +190,6 @@ def register(subparsers) -> None:
196
190
  "examples:\n"
197
191
  " fp setup Run the interactive wizard\n"
198
192
  " fp setup --check Validate existing configuration\n"
199
- " fp setup --seed-samples Load sample data for exploration\n"
200
- " fp setup --clear-samples Remove sample data\n"
201
- " fp setup --test Start throwaway test environment\n"
202
- " fp setup --endtest End test mode\n"
203
193
  " fp setup mcp --claude Configure MCP for Claude Desktop\n"
204
194
  " fp setup folders add ~/Work/newdir\n"
205
195
  "\n"
@@ -225,26 +215,6 @@ def register(subparsers) -> None:
225
215
  action="store_true",
226
216
  help="Clear database and vector store, then re-run setup wizard",
227
217
  )
228
- parser.add_argument(
229
- "--test",
230
- action="store_true",
231
- help="Start test mode — throwaway environment, zero risk to production data",
232
- )
233
- parser.add_argument(
234
- "--endtest",
235
- action="store_true",
236
- help="End test mode — remove test environment, return to production data",
237
- )
238
- parser.add_argument(
239
- "--seed-samples",
240
- action="store_true",
241
- help="Load sample data for exploration (tagged source='sample')",
242
- )
243
- parser.add_argument(
244
- "--clear-samples",
245
- action="store_true",
246
- help="Remove all sample data and policies",
247
- )
248
218
 
249
219
  subs = parser.add_subparsers(dest="setup_action", metavar="COMMAND", title="commands (one required)")
250
220
 
@@ -361,31 +331,10 @@ def _handle_setup_inner(args) -> None:
361
331
  console.print("[yellow]Usage: fp setup folders add|remove[/yellow]")
362
332
  return
363
333
 
364
- if getattr(args, "seed_samples", False):
365
- _do_seed_samples()
366
- return
367
-
368
- if getattr(args, "clear_samples", False):
369
- _do_clear_samples()
370
- return
371
-
372
- if getattr(args, "test", False):
373
- _start_test_mode()
374
- return
375
-
376
- if getattr(args, "endtest", False):
377
- _end_test_mode()
378
- return
379
-
380
334
  if getattr(args, "reset", False):
381
335
  db_path = get_db_path()
382
336
  chroma_path = get_chroma_path()
383
337
 
384
- if is_test_mode():
385
- console.print(
386
- "[yellow]Note: you are in test mode — this will reset the test environment, not production.[/yellow]"
387
- )
388
-
389
338
  console.print(
390
339
  "[bold yellow]This will delete all indexed data.[/bold yellow]\nConfig and credentials are preserved."
391
340
  )
@@ -417,101 +366,6 @@ def _handle_setup_inner(args) -> None:
417
366
  run_interactive_wizard()
418
367
 
419
368
 
420
- # ---------------------------------------------------------------------------
421
- # Sample data helpers
422
- # ---------------------------------------------------------------------------
423
-
424
-
425
- def _do_seed_samples() -> None:
426
- """Seed sample data for exploration."""
427
- from footprinter.cli._sample_seed import seed_samples
428
-
429
- conn = _get_db_connection()
430
- if conn is None:
431
- console.print("[red]No database found.[/red] Run 'fp setup' first.")
432
- return
433
-
434
- try:
435
- result = seed_samples(conn)
436
- console.print(
437
- f"[green]Sample data seeded:[/green] "
438
- f"{result['files_created']} files, "
439
- f"{result['policies_seeded']} policies"
440
- )
441
- console.print(" [dim]See: fp setup --clear-samples to remove[/dim]")
442
- except Exception as e: # Intentional broad catch: setup wizard step must not crash
443
- console.print(f"[yellow]Sample seeding failed: {e}[/yellow]")
444
- finally:
445
- conn.close()
446
-
447
-
448
- def _do_clear_samples() -> None:
449
- """Remove all sample data and policies."""
450
- from footprinter.cli._sample_seed import clear_samples
451
-
452
- conn = _get_db_connection()
453
- if conn is None:
454
- console.print("[red]No database found.[/red]")
455
- return
456
-
457
- try:
458
- result = clear_samples(conn)
459
- console.print(
460
- f"[green]Sample data cleared:[/green] "
461
- f"{result['files_removed']} files, "
462
- f"{result['folders_removed']} folders, "
463
- f"{result['policies_removed']} policies removed"
464
- )
465
- except Exception as e: # Intentional broad catch: setup wizard step must not crash
466
- console.print(f"[yellow]Sample clearing failed: {e}[/yellow]")
467
- finally:
468
- conn.close()
469
-
470
-
471
- # ---------------------------------------------------------------------------
472
- # Test mode helpers
473
- # ---------------------------------------------------------------------------
474
-
475
-
476
- def _start_test_mode() -> None:
477
- """Create a throwaway test environment with its own data + config."""
478
- real_home = get_real_home()
479
- marker = real_home / _TEST_MARKER_NAME
480
- test_env = real_home / _TEST_ENV_NAME
481
-
482
- if marker.is_file():
483
- console.print("[yellow]Test mode already active — wiping previous environment.[/yellow]")
484
- if test_env.exists():
485
- shutil.rmtree(test_env)
486
-
487
- test_env.mkdir(parents=True, exist_ok=True)
488
-
489
- # Copy production config so the test environment inherits settings.
490
- prod_config = real_home / "config.yaml"
491
- if prod_config.is_file():
492
- shutil.copy2(prod_config, test_env / "config.yaml")
493
-
494
- marker.write_text(str(test_env))
495
- console.print(f"[green]Test mode started.[/green] Data at {test_env}")
496
- run_interactive_wizard()
497
-
498
-
499
- def _end_test_mode() -> None:
500
- """Remove test environment and marker, returning to production data."""
501
- real_home = get_real_home()
502
- marker = real_home / _TEST_MARKER_NAME
503
-
504
- if not marker.is_file():
505
- console.print("[dim]Not in test mode — nothing to do.[/dim]")
506
- return
507
-
508
- test_env_path = Path(marker.read_text().strip())
509
- if test_env_path.exists():
510
- shutil.rmtree(test_env_path)
511
- marker.unlink()
512
- console.print("[green]Test mode ended.[/green] Back to production data.")
513
-
514
-
515
369
  # ---------------------------------------------------------------------------
516
370
  # Standalone entry point (fp setup)
517
371
  # ---------------------------------------------------------------------------
@@ -572,17 +426,6 @@ def main():
572
426
  remove_parser = folders_sub.add_parser("remove", help="Remove a directory from config")
573
427
  remove_parser.add_argument("path", help="Directory path to remove")
574
428
 
575
- parser.add_argument(
576
- "--seed-samples",
577
- action="store_true",
578
- help="Load sample data for exploration (tagged source='sample')",
579
- )
580
- parser.add_argument(
581
- "--clear-samples",
582
- action="store_true",
583
- help="Remove all sample data and policies",
584
- )
585
-
586
429
  args = parser.parse_args()
587
430
 
588
431
  if args.subcommand == "mcp":
@@ -599,11 +442,7 @@ def main():
599
442
  folders_parser.print_help()
600
443
  return
601
444
 
602
- if args.seed_samples:
603
- _do_seed_samples()
604
- elif args.clear_samples:
605
- _do_clear_samples()
606
- elif getattr(args, "hooks", False):
445
+ if getattr(args, "hooks", False):
607
446
  sys.exit(install_git_hooks())
608
447
  elif args.check:
609
448
  sys.exit(check_existing_config())
@@ -978,10 +817,6 @@ def run_interactive_wizard():
978
817
  seed_access_policies()
979
818
  mcp_configured = offer_setup_claude()
980
819
 
981
- # Optional: seed sample data
982
- if Confirm.ask("\nSeed sample data for exploration?", default=False):
983
- _do_seed_samples()
984
-
985
820
  # Phase 6: Summary
986
821
  _print_phase(6, 6, "Summary")
987
822
  print_summary(
@@ -233,24 +233,6 @@ def _query_all_counts(cursor, counts: dict) -> dict:
233
233
  except sqlite3.OperationalError:
234
234
  counts["last_run"] = None
235
235
 
236
- # Fallback: if no ingests, try MAX(indexed_at) from files
237
- if counts["last_run"] is None:
238
- try:
239
- cursor.execute("SELECT MAX(indexed_at) FROM files")
240
- row = cursor.fetchone()
241
- if row and row[0]:
242
- counts["last_run"] = {
243
- "mode": "unknown",
244
- "pipe": "unknown",
245
- "started_at": row[0],
246
- "completed_at": None,
247
- "items_processed": counts["files_total"],
248
- "errors": 0,
249
- "status": "unknown",
250
- "elapsed_seconds": None,
251
- }
252
- except sqlite3.OperationalError:
253
- pass
254
236
 
255
237
  return counts
256
238
 
@@ -95,6 +95,12 @@ class DataPipelineOrchestrator:
95
95
  )
96
96
  return self._dispatch_pipes(pipes, on_pipe_start, on_pipe_end, on_progress)
97
97
 
98
+ def run_refresh(self, source: str, on_pipe_start=None, on_pipe_end=None, on_progress=None) -> List[Dict]:
99
+ """Execute a refresh group. Shares _dispatch_pipes with run_pipeline so POST_PIPES run inline."""
100
+ if source not in self.refresh_pipes:
101
+ raise ValueError(f"Unknown refresh source: {source}. Available: {', '.join(self.refresh_pipes.keys())}")
102
+ return self._dispatch_pipes(self.refresh_pipes[source], on_pipe_start, on_pipe_end, on_progress)
103
+
98
104
  def _dispatch_pipes(self, pipes, on_pipe_start, on_pipe_end, on_progress) -> List[Dict]:
99
105
  self.runner.full_mode = self.full_mode
100
106
  mode = "full" if self.full_mode else "incremental"
@@ -159,6 +159,20 @@ class PipeRunner:
159
159
 
160
160
  return result
161
161
 
162
+ def validate_pipes(self, pipes: List[str]) -> None:
163
+ """Raise ValueError for unknown pipe names. Pure check, no side effects.
164
+
165
+ Exposed so callers that need to fail before starting UI output
166
+ (progress bars, headers) can pre-flight without duplicating the
167
+ unknown-pipe rule.
168
+ """
169
+ unknown = [s for s in pipes if s not in self.all_pipes]
170
+ if unknown:
171
+ raise ValueError(
172
+ f"Unknown pipe(s): {', '.join(unknown)}. "
173
+ f"Valid pipes: {', '.join(self.user_pipes)}"
174
+ )
175
+
162
176
  def run_pipes(
163
177
  self,
164
178
  pipes: List[str],
@@ -173,9 +187,7 @@ class PipeRunner:
173
187
  Raises ValueError for unknown pipe names. Stops on fatal errors
174
188
  (database/config error_type), continues on runtime errors.
175
189
  """
176
- unknown = [s for s in pipes if s not in self.all_pipes]
177
- if unknown:
178
- raise ValueError(f"Unknown pipe(s): {', '.join(unknown)}. Valid pipes: {', '.join(self.user_pipes)}")
190
+ self.validate_pipes(pipes)
179
191
 
180
192
  results = []
181
193