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.
- {footprinter_cli-1.0.0rc2/footprinter_cli.egg-info → footprinter_cli-1.0.0rc4}/PKG-INFO +27 -21
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/README.md +12 -13
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/access.py +27 -14
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/__init__.py +0 -7
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_common.py +5 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/ingest.py +30 -5
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/setup.py +1 -166
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/status.py +0 -18
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/orchestrator.py +6 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/pipe_runner.py +15 -3
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/db.py +2 -12
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/errors.py +1 -4
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/status.py +1 -5
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/paths.py +2 -28
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/permissions.py +16 -8
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/visibility.py +16 -8
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4/footprinter_cli.egg-info}/PKG-INFO +27 -21
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/SOURCES.txt +3 -13
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/pyproject.toml +17 -9
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_recalculate.py +237 -0
- footprinter_cli-1.0.0rc4/tests/test_bundled.py +108 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_e2e_install.py +4 -8
- footprinter_cli-1.0.0rc4/tests/test_examples.py +84 -0
- footprinter_cli-1.0.0rc4/tests/test_paths_no_test_marker.py +59 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_security_layer.py +116 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_security_permissions.py +148 -0
- footprinter_cli-1.0.0rc2/footprinter/bundled/samples/hidden-client-file-sample.txt +0 -2
- footprinter_cli-1.0.0rc2/footprinter/bundled/samples/opaque-project-file-sample.txt +0 -2
- footprinter_cli-1.0.0rc2/footprinter/bundled/samples/visible-file-sample.txt +0 -2
- footprinter_cli-1.0.0rc2/footprinter/cli/_sample_seed.py +0 -204
- footprinter_cli-1.0.0rc2/tests/test_bundled.py +0 -92
- footprinter_cli-1.0.0rc2/tests/test_dependency_stripping.py +0 -289
- footprinter_cli-1.0.0rc2/tests/test_doc_paths.py +0 -35
- footprinter_cli-1.0.0rc2/tests/test_old_api_cleanup.py +0 -132
- footprinter_cli-1.0.0rc2/tests/test_pyproject_metadata.py +0 -162
- footprinter_cli-1.0.0rc2/tests/test_release_governance.py +0 -1168
- footprinter_cli-1.0.0rc2/tests/test_snapshot_manifest.py +0 -560
- footprinter_cli-1.0.0rc2/tests/test_swe_839_cleanup.py +0 -19
- footprinter_cli-1.0.0rc2/tests/test_tool_scope_regressions.py +0 -264
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/LICENSE +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/db.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/entities.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/search.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/semantic.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/server.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/api/status.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/config.example.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/context_patterns.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/extensions.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/filename_patterns.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/mime_mappings.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/salesforce_rules.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/bundled/patterns/security_patterns.yaml +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/__main__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_policy_helpers.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/_prompt.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/api_cmd.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/connect.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/data.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/delete.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/mcp_cmd.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/mcp_setup.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/search.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/search_cmd.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/status_cmd.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/upsert.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/vectorize_cmd.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/cli/view.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/connectors/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/connectors/config_utils.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/browser.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/chats.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/clients.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/emails.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/files.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/folders.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/messages.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/policies.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/projects.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/search.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/sql_utils.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/status.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/db/uploads.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/browser.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/chat.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/ingest.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/local_files.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/local_folders.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/adapters/protocol.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/browser_indexer.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_dedup.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_indexer.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/chatgpt_parser.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/chat_parsers/claude_parser.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/cli.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/content_extractors.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/database.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/connector_schema.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/migration.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/schema.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/db/security.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/file_indexer.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/file_scanner.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/folder_indexer.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/full_content_extractor.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/processing.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/registry.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/run_record.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/ingest/status.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/__main__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/extraction.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/server.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/navigation.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/read.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/search.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/mcp/tools/semantic.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/chunking.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/embeddings.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/hybrid_search.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/semantic/vector_store.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/access_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/chat_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/client_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/content_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/email_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/file_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/folder_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/includes.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/ingest_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/project_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/roles.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/search_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/semantic_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/status_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/services/visit_service.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/source_registry.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/__init__.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/hash_utils.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/logging_config.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/mime.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/text.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter/utils/time.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/dependency_links.txt +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/entry_points.txt +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/requires.txt +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/footprinter_cli.egg-info/top_level.txt +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/setup.cfg +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_control_bypasses.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_access_control_docs.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_build_status_filter.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_e2e_pipeline.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_edit_recalculate.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_files_rename.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_files_surface.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_inherit_resolution.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_logging.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_no_project_root.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_package_init.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_pip_install_e2e.py +0 -0
- {footprinter_cli-1.0.0rc2 → footprinter_cli-1.0.0rc4}/tests/test_prompt_safety.py +0 -0
- {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.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
11
|
-
|
|
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:
|
|
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 ::
|
|
18
|
-
Classifier: Topic ::
|
|
19
|
-
Classifier:
|
|
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
|
[](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml)
|
|
49
56
|
|
|
50
|
-
**
|
|
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
|
-
|
|
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
|
[](https://github.com/swellcitygroup/footprinter/actions/workflows/test.yml)
|
|
4
4
|
|
|
5
|
-
**
|
|
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
|
-
|
|
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":
|
|
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":
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
#
|
|
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
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|