voidaccess 1.9.0__tar.gz → 1.9.1__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.
- {voidaccess-1.9.0/voidaccess.egg-info → voidaccess-1.9.1}/PKG-INFO +1 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/config.py +11 -18
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/session.py +17 -4
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/ioc_package.py +1 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/pyproject.toml +1 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_config.py +24 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_llm.py +13 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess/config.py +4 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess/llm.py +26 -5
- {voidaccess-1.9.0 → voidaccess-1.9.1/voidaccess.egg-info}/PKG-INFO +1 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/__init__.py +1 -1
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/investigate.py +58 -10
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/show.py +2 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/config.py +4 -4
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/main.py +5 -3
- {voidaccess-1.9.0 → voidaccess-1.9.1}/LICENSE +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/README.md +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/analysis/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/analysis/opsec.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/analysis/patterns.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/analysis/temporal.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/auth.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/errors.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/main.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/actors.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/admin.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/auth.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/entities.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/investigations.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/monitors.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/search.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/api/routes/settings.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/auth/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/auth/token_blacklist.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/crawler/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/crawler/dedup.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/crawler/frontier.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/crawler/spider.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/crawler/utils.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/env.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0001_initial_schema.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0002_add_investigation_status_column.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0002_add_missing_tables.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0003_add_canonical_value_and_entity_links.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0004_add_page_posted_at.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0005_add_extraction_method.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0006_add_monitor_alerts.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0007_add_actor_style_profiles.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0008_add_users_table.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0009_add_investigation_id_to_relationships.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0010_add_composite_index_entity_relationships.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0011_add_page_extraction_cache.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0013_add_graph_status.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0015_add_progress_fields.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0016_backfill_graph_status.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0017_add_user_api_keys.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0018_add_user_id_to_investigations.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0019_add_content_safety_log.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0020_add_entity_source_tracking.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0021_add_search_engine_stats.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0022_add_actor_profiles.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0023_add_investigation_metadata.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0024_add_investigation_step_metrics.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0025_add_cancellation_requested.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/models.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/queries.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/db/search_engine_stats.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/_entity_loading.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/misp.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/sigma.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/snort_export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/stix.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/export/yara_export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/confidence.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/entity_shape.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/gazetteer.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/identity.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/llm_extract.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/ner.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/normalizer.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/pipeline.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/regex_patterns.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/extractor/relationship_extract.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/fingerprint/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/fingerprint/calibration.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/fingerprint/profiler.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/fingerprint/stylometry.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/builder.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/model.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/queries.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/graph/visualize.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/i18n/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/i18n/detect.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/i18n/query_expand.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/i18n/translate.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/_db.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/alerts.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/config.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/diff.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/jobs.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/monitor/scheduler.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/scraper/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/scraper/scrape.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/scraper/scrape_js.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/search/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/search/circuit_breaker.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/search/engine_catalog.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/search/query_builder.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/search/search.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/setup.cfg +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/actor_profiles.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/blockchain.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/breach_lookup.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/cache.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/cisa.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/data/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/data/onion_seeds.json +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/dns_enrichment.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/domain_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/email_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/engines.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/enrichment.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/github_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/gitlab_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/hash_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/historical_intel.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/infostealer.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/ip_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/nvd.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/paste_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/pastes.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/proxy_client.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/rss_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/seed_manager.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/seeds.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/shodan.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/telegram.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/sources/virustotal.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_analysis_opsec.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_analysis_stylometry.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_analysis_temporal.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_api.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_api_monitors.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_blockchain.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_breach_lookup.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_cli_proxy_config.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_confidence_calibration.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_consolidated_contracts.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_crawler.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_db.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_dns_enrichment.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_domain_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_email_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_extractor_llm_pipeline.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_filter_e2e_manual.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_filter_parser_manual.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_fingerprint.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_github_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_gitlab_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_graph.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_graph_integration.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_hash_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_i18n.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_infostealer.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_ip_reputation.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_llm_utils.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_model_singleton.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_monitor.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_monitor_integration.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_nvd.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_pagination.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_paste_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_proxy_client.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_ransomlook.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_regex_patterns.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_relationship_extract.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_rss_scraper.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_scrape_js.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_settings.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_snort_export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_sources.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_sources_enrichment_new.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_stix_export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_vector.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/tests/test_yara_export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/async_utils.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/content_dedup.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/content_safety.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/defang.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/encryption.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/enrichment_cache.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/investigation_metrics.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/ioc_freshness.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/utils/user_keys.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/vector/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/vector/embedder.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/vector/model_singleton.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/vector/search.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/vector/store.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess/llm_utils.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess.egg-info/SOURCES.txt +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess.egg-info/dependency_links.txt +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess.egg-info/entry_points.txt +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess.egg-info/requires.txt +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess.egg-info/top_level.txt +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/adapters/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/adapters/sqlite.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/browser.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/__init__.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/actors.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/configure.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/enrich.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/commands/export.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/display.py +0 -0
- {voidaccess-1.9.0 → voidaccess-1.9.1}/voidaccess_cli/tor_detect.py +0 -0
|
@@ -5,7 +5,7 @@ from dotenv import load_dotenv
|
|
|
5
5
|
load_dotenv()
|
|
6
6
|
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
8
|
+
_OPTIONAL_CONFIG_WARNING_STATE = "_voidaccess_optional_config_warning_shown"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _clean_env(name, default=None):
|
|
@@ -151,16 +151,12 @@ except ValueError:
|
|
|
151
151
|
BLOCKCYPHER_TOKEN = _clean_env("BLOCKCYPHER_TOKEN", "")
|
|
152
152
|
ETHERSCAN_API_KEY = _clean_env("ETHERSCAN_API_KEY", "")
|
|
153
153
|
|
|
154
|
-
# Auth — REQUIRED
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"and set it as JWT_SECRET in your .env file. "
|
|
161
|
-
"Do NOT use a random secret — it will change on restart and invalidate all issued tokens."
|
|
162
|
-
)
|
|
163
|
-
JWT_SECRET = _jwt_secret
|
|
154
|
+
# Auth — REQUIRED when the API/auth stack is actually used. Do not validate
|
|
155
|
+
# this at import time: the CLI injects its runtime environment in its entry
|
|
156
|
+
# point before importing dependent modules, and `--help`/`--version` must work
|
|
157
|
+
# on a fresh install. `validate_config()` and the API startup path enforce it
|
|
158
|
+
# at the point where authentication is needed.
|
|
159
|
+
JWT_SECRET = _clean_env("JWT_SECRET")
|
|
164
160
|
|
|
165
161
|
# Token blacklist Redis (optional — omit to disable blacklist checks)
|
|
166
162
|
REDIS_URL = _clean_env("REDIS_URL")
|
|
@@ -210,7 +206,6 @@ OPTIONAL_KEYS = [
|
|
|
210
206
|
|
|
211
207
|
|
|
212
208
|
def validate_config():
|
|
213
|
-
global _OPTIONAL_CONFIG_WARNING_SHOWN
|
|
214
209
|
missing_required = []
|
|
215
210
|
for key in REQUIRED_KEYS:
|
|
216
211
|
if _clean_env(key) is None:
|
|
@@ -221,16 +216,14 @@ def validate_config():
|
|
|
221
216
|
key for key in OPTIONAL_KEYS
|
|
222
217
|
if (_clean_env(key) is None or _clean_env(key) == "")
|
|
223
218
|
]
|
|
224
|
-
if missing_optional and not
|
|
219
|
+
if missing_optional and not getattr(logging, _OPTIONAL_CONFIG_WARNING_STATE, False):
|
|
225
220
|
logger.warning(
|
|
226
221
|
"Optional configuration keys not set (%d): %s - related features will be disabled",
|
|
227
222
|
len(missing_optional),
|
|
228
223
|
", ".join(missing_optional),
|
|
229
224
|
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
validate_config()
|
|
234
|
-
|
|
225
|
+
# Store the guard on the process-wide logging module so compatibility
|
|
226
|
+
# wrappers/reloads cannot reset it and reintroduce warning spam.
|
|
227
|
+
setattr(logging, _OPTIONAL_CONFIG_WARNING_STATE, True)
|
|
235
228
|
|
|
236
229
|
|
|
@@ -38,6 +38,7 @@ Usage (testing — pass an explicit URL to avoid needing DATABASE_URL in env)
|
|
|
38
38
|
from __future__ import annotations
|
|
39
39
|
|
|
40
40
|
from functools import lru_cache
|
|
41
|
+
import os
|
|
41
42
|
|
|
42
43
|
from contextlib import asynccontextmanager, contextmanager
|
|
43
44
|
from typing import AsyncGenerator, Generator, Optional
|
|
@@ -48,7 +49,19 @@ from sqlalchemy.orm import sessionmaker, Session
|
|
|
48
49
|
|
|
49
50
|
import config
|
|
50
51
|
|
|
52
|
+
# Kept as a compatibility/testing hook. Production lookups below use the
|
|
53
|
+
# current environment so apply_env() can run before the first DB operation.
|
|
51
54
|
DATABASE_URL = config.DATABASE_URL
|
|
55
|
+
_INITIAL_DATABASE_URL = DATABASE_URL
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _database_url() -> Optional[str]:
|
|
59
|
+
# Preserve the long-standing test/embedding hook of monkeypatching this
|
|
60
|
+
# module's DATABASE_URL. Otherwise, read the live environment so a caller
|
|
61
|
+
# can inject DATABASE_URL after db.session has been imported.
|
|
62
|
+
if DATABASE_URL != _INITIAL_DATABASE_URL:
|
|
63
|
+
return DATABASE_URL
|
|
64
|
+
return os.getenv("DATABASE_URL") or DATABASE_URL
|
|
52
65
|
_async_engine_cache: dict[str, "AsyncEngine"] = {}
|
|
53
66
|
|
|
54
67
|
|
|
@@ -104,7 +117,7 @@ def get_engine(url: Optional[str] = None) -> Engine:
|
|
|
104
117
|
PostgreSQL gets a connection pool tuned for the scraping workload.
|
|
105
118
|
SQLite skips pool parameters that only apply to QueuePool.
|
|
106
119
|
"""
|
|
107
|
-
target_url = url or
|
|
120
|
+
target_url = url or _database_url()
|
|
108
121
|
if not target_url:
|
|
109
122
|
raise RuntimeError(
|
|
110
123
|
"DATABASE_URL is not configured.\n"
|
|
@@ -122,7 +135,7 @@ def release_engine(url: Optional[str] = None) -> None:
|
|
|
122
135
|
Calls engine.dispose() to release connection pool resources and file handles,
|
|
123
136
|
then clears the cache. Use this in test teardown to prevent leaks.
|
|
124
137
|
"""
|
|
125
|
-
target_url = url or
|
|
138
|
+
target_url = url or _database_url()
|
|
126
139
|
if target_url:
|
|
127
140
|
try:
|
|
128
141
|
engine = get_engine(target_url)
|
|
@@ -139,7 +152,7 @@ def get_async_engine(url: Optional[str] = None) -> "AsyncEngine":
|
|
|
139
152
|
Converts postgresql:// to postgresql+asyncpg:// and sqlite:// to sqlite+aiosqlite://.
|
|
140
153
|
"""
|
|
141
154
|
|
|
142
|
-
target_url = url or
|
|
155
|
+
target_url = url or _database_url()
|
|
143
156
|
if not target_url:
|
|
144
157
|
raise RuntimeError(
|
|
145
158
|
"DATABASE_URL is not configured.\n"
|
|
@@ -185,7 +198,7 @@ def release_async_engine(url: Optional[str] = None) -> None:
|
|
|
185
198
|
|
|
186
199
|
Calls engine.dispose() to release connection pool resources and file handles.
|
|
187
200
|
"""
|
|
188
|
-
target_url = url or
|
|
201
|
+
target_url = url or _database_url()
|
|
189
202
|
if target_url in _async_engine_cache:
|
|
190
203
|
_async_engine_cache[target_url].dispose()
|
|
191
204
|
del _async_engine_cache[target_url]
|
|
@@ -57,7 +57,7 @@ logger = logging.getLogger(__name__)
|
|
|
57
57
|
# ---------------------------------------------------------------------------
|
|
58
58
|
|
|
59
59
|
PACKAGE_FORMAT = "voidaccess-ioc-v1"
|
|
60
|
-
PACKAGE_VERSION = "1.9.
|
|
60
|
+
PACKAGE_VERSION = "1.9.1"
|
|
61
61
|
SNORT_SID_BASE = 9000001 # 9xxxxxx reserved for VoidAccess-generated rules
|
|
62
62
|
|
|
63
63
|
# Map extractor upper-case entity types to the per-file bucket they belong to.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import unittest
|
|
6
|
+
import logging
|
|
6
7
|
from unittest.mock import patch, MagicMock
|
|
7
8
|
|
|
8
9
|
|
|
@@ -52,6 +53,28 @@ class TestConfigValidation(unittest.TestCase):
|
|
|
52
53
|
warning_calls = str(mock_logger.warning.call_args_list)
|
|
53
54
|
self.assertIn("OPENAI_API_KEY", warning_calls)
|
|
54
55
|
|
|
56
|
+
def test_optional_config_warning_is_once_per_process(self):
|
|
57
|
+
"""Repeated validation must not spam the same process with warnings."""
|
|
58
|
+
import config
|
|
59
|
+
|
|
60
|
+
with patch.dict(os.environ, {"JWT_SECRET": "test-secret-key-123"}, clear=True):
|
|
61
|
+
# The guard intentionally lives on logging so compatibility-module
|
|
62
|
+
# reloads cannot reset it; reset it explicitly for this isolated
|
|
63
|
+
# regression test.
|
|
64
|
+
setattr(logging, "_voidaccess_optional_config_warning_shown", False)
|
|
65
|
+
try:
|
|
66
|
+
with patch.object(config, "logger") as mock_logger:
|
|
67
|
+
config.validate_config()
|
|
68
|
+
config.validate_config()
|
|
69
|
+
|
|
70
|
+
warning_calls = [
|
|
71
|
+
call for call in mock_logger.warning.call_args_list
|
|
72
|
+
if call.args and "Optional configuration keys not set" in str(call.args[0])
|
|
73
|
+
]
|
|
74
|
+
self.assertEqual(len(warning_calls), 1)
|
|
75
|
+
finally:
|
|
76
|
+
setattr(logging, "_voidaccess_optional_config_warning_shown", False)
|
|
77
|
+
|
|
55
78
|
|
|
56
79
|
if __name__ == "__main__":
|
|
57
|
-
unittest.main()
|
|
80
|
+
unittest.main()
|
|
@@ -19,6 +19,19 @@ def test_select_relevant_pages_empty():
|
|
|
19
19
|
assert result == []
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def test_get_llm_uses_runtime_provider_key_when_api_keys_omitted(monkeypatch):
|
|
23
|
+
"""A key injected after module import must reach the LangChain client."""
|
|
24
|
+
from unittest.mock import patch
|
|
25
|
+
from langchain_openai import ChatOpenAI
|
|
26
|
+
from voidaccess.llm import get_llm
|
|
27
|
+
|
|
28
|
+
monkeypatch.setenv("OPENROUTER_API_KEY", "runtime-openrouter-key")
|
|
29
|
+
with patch.object(ChatOpenAI, "__init__", return_value=None) as init:
|
|
30
|
+
get_llm("openrouter/test-model")
|
|
31
|
+
|
|
32
|
+
assert init.call_args.kwargs["api_key"] == "runtime-openrouter-key"
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
def test_select_relevant_pages_fits_budget():
|
|
23
36
|
"""Pages that fit in budget are all returned."""
|
|
24
37
|
pages = [make_page("a" * 100, f"http://{i}.onion") for i in range(5)]
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib import import_module, reload
|
|
4
4
|
|
|
5
|
+
# Reload the compatibility target so explicit `importlib.reload` calls keep
|
|
6
|
+
# reflecting a test/application environment change. The warning guard itself
|
|
7
|
+
# lives on the process-wide logging module, so this reload cannot reintroduce
|
|
8
|
+
# warning spam.
|
|
5
9
|
_config = reload(import_module("config"))
|
|
6
10
|
|
|
7
11
|
for _name in dir(_config):
|
|
@@ -256,11 +256,32 @@ def get_llm(model_choice, api_keys: dict | None = None):
|
|
|
256
256
|
"GOOGLE_API_KEY": "google_api_key",
|
|
257
257
|
"GROQ_API_KEY": "groq_api_key",
|
|
258
258
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
259
|
+
# Prefer explicitly supplied per-user keys, but also resolve the current
|
|
260
|
+
# process environment when callers omit api_keys. The CLI injects its
|
|
261
|
+
# saved config at runtime, so relying only on import-time llm_utils values
|
|
262
|
+
# silently drops a valid OPENROUTER_API_KEY.
|
|
263
|
+
runtime_keys = dict(api_keys or {})
|
|
264
|
+
base_url = str(model_specific_params.get("base_url") or "").lower()
|
|
265
|
+
if "openrouter" in base_url:
|
|
266
|
+
runtime_keys.setdefault("OPENROUTER_API_KEY", os.getenv("OPENROUTER_API_KEY", ""))
|
|
267
|
+
elif "groq.com" in base_url:
|
|
268
|
+
runtime_keys.setdefault("GROQ_API_KEY", os.getenv("GROQ_API_KEY", ""))
|
|
269
|
+
elif "ChatAnthropic" in getattr(llm_class, "__name__", ""):
|
|
270
|
+
runtime_keys.setdefault("ANTHROPIC_API_KEY", os.getenv("ANTHROPIC_API_KEY", ""))
|
|
271
|
+
elif "ChatGoogle" in getattr(llm_class, "__name__", ""):
|
|
272
|
+
runtime_keys.setdefault("GOOGLE_API_KEY", os.getenv("GOOGLE_API_KEY", ""))
|
|
273
|
+
elif "ChatOpenAI" in getattr(llm_class, "__name__", ""):
|
|
274
|
+
runtime_keys.setdefault("OPENAI_API_KEY", os.getenv("OPENAI_API_KEY", ""))
|
|
275
|
+
|
|
276
|
+
for key_name, key_value in runtime_keys.items():
|
|
277
|
+
if key_value and key_name in _ENV_TO_LANGCHAIN:
|
|
278
|
+
param_name = _ENV_TO_LANGCHAIN[key_name]
|
|
279
|
+
model_specific_params[param_name] = key_value
|
|
280
|
+
# ChatOpenAI uses `api_key` as the canonical constructor field;
|
|
281
|
+
# keep it synchronized with the mapped alias when a model config
|
|
282
|
+
# was created from stale import-time defaults.
|
|
283
|
+
if param_name == "openai_api_key":
|
|
284
|
+
model_specific_params["api_key"] = key_value
|
|
264
285
|
|
|
265
286
|
# Combine common parameters with model-specific parameters
|
|
266
287
|
# Model-specific parameters will override common ones if there are any conflicts
|
|
@@ -26,13 +26,6 @@ from typing import Any, Callable, Optional
|
|
|
26
26
|
import typer
|
|
27
27
|
from rich.console import Console
|
|
28
28
|
|
|
29
|
-
from extractor.identity import entity_display_id
|
|
30
|
-
|
|
31
|
-
# Import reputation enrichment sources (used in Step 6.2–6.4)
|
|
32
|
-
from sources.domain_reputation import enrich_domain_entities
|
|
33
|
-
from sources.email_reputation import enrich_email_entities
|
|
34
|
-
from sources.hash_reputation import enrich_hash_entities
|
|
35
|
-
|
|
36
29
|
console = Console()
|
|
37
30
|
logger = logging.getLogger(__name__)
|
|
38
31
|
|
|
@@ -368,7 +361,11 @@ async def _run_investigation(
|
|
|
368
361
|
if not no_llm:
|
|
369
362
|
try:
|
|
370
363
|
from voidaccess.llm import get_llm
|
|
371
|
-
|
|
364
|
+
provider = cli_config.get_llm_provider(cfg)
|
|
365
|
+
provider_env = cli_config.PROVIDER_ENV.get(provider)
|
|
366
|
+
api_key = cli_config.get_llm_key(cfg)
|
|
367
|
+
api_keys = {provider_env: api_key} if provider_env and api_key else {}
|
|
368
|
+
llm = get_llm(chosen_model, api_keys=api_keys)
|
|
372
369
|
except Exception as exc:
|
|
373
370
|
display.update_step("Refining query", "fail", f"LLM init failed: {exc}")
|
|
374
371
|
llm = None
|
|
@@ -716,6 +713,7 @@ async def _run_investigation(
|
|
|
716
713
|
# whole cluster further down.
|
|
717
714
|
display.update_step("Enriching domains", "active")
|
|
718
715
|
try:
|
|
716
|
+
from sources.domain_reputation import enrich_domain_entities
|
|
719
717
|
# enrich_*_entities return (extraction_results, stats) — unpack so the
|
|
720
718
|
# threaded list isn't clobbered into a tuple for the next step.
|
|
721
719
|
extraction_results, _ = await asyncio.wait_for(
|
|
@@ -738,6 +736,7 @@ async def _run_investigation(
|
|
|
738
736
|
|
|
739
737
|
display.update_step("Enriching hashes", "active")
|
|
740
738
|
try:
|
|
739
|
+
from sources.hash_reputation import enrich_hash_entities
|
|
741
740
|
extraction_results, _ = await asyncio.wait_for(
|
|
742
741
|
enrich_hash_entities(extraction_results, inv_uuid),
|
|
743
742
|
timeout=45,
|
|
@@ -752,6 +751,7 @@ async def _run_investigation(
|
|
|
752
751
|
|
|
753
752
|
display.update_step("Enriching emails", "active")
|
|
754
753
|
try:
|
|
754
|
+
from sources.email_reputation import enrich_email_entities
|
|
755
755
|
extraction_results, _ = await asyncio.wait_for(
|
|
756
756
|
enrich_email_entities(extraction_results, inv_uuid),
|
|
757
757
|
timeout=30,
|
|
@@ -869,9 +869,22 @@ async def _run_investigation(
|
|
|
869
869
|
# onto the event-loop thread so Rich's live renderer remains safe.
|
|
870
870
|
loop.call_soon_threadsafe(display.update_step, "Building graph", "active", label)
|
|
871
871
|
|
|
872
|
+
def _graph_metrics(metrics: dict[str, int]) -> None:
|
|
873
|
+
detail = (
|
|
874
|
+
f"entities: {metrics.get('entity_rows_loaded', 0)} | "
|
|
875
|
+
f"intra-page edges: {metrics.get('intra_page_edges', 0)} | "
|
|
876
|
+
f"cross-page edges: {metrics.get('cross_page_edges', 0)}"
|
|
877
|
+
)
|
|
878
|
+
loop.call_soon_threadsafe(display.update_step, "Building graph", "active", detail)
|
|
879
|
+
|
|
872
880
|
try:
|
|
873
881
|
edges_written = await asyncio.wait_for(
|
|
874
|
-
asyncio.to_thread(
|
|
882
|
+
asyncio.to_thread(
|
|
883
|
+
_build_cooccurrence_edges,
|
|
884
|
+
investigation_id,
|
|
885
|
+
_graph_progress,
|
|
886
|
+
_graph_metrics,
|
|
887
|
+
),
|
|
875
888
|
timeout=CLI_PHASE_TIMEOUTS["graph_build"],
|
|
876
889
|
)
|
|
877
890
|
if no_llm:
|
|
@@ -1272,12 +1285,19 @@ def _lookup_page_ids(urls: list[str]) -> dict[str, uuid.UUID]:
|
|
|
1272
1285
|
def _build_cooccurrence_edges(
|
|
1273
1286
|
investigation_id: str,
|
|
1274
1287
|
progress_callback: Optional[Callable[[str], None]] = None,
|
|
1288
|
+
metrics_callback: Optional[Callable[[dict[str, int]], None]] = None,
|
|
1275
1289
|
) -> int:
|
|
1276
1290
|
"""Generate CO_APPEARED_ON edges for entities sharing a page."""
|
|
1277
1291
|
from db.models import Entity
|
|
1278
1292
|
from db.session import get_session
|
|
1279
1293
|
from sqlalchemy.orm import joinedload
|
|
1280
|
-
from graph.builder import
|
|
1294
|
+
from graph.builder import (
|
|
1295
|
+
_ENTITY_TYPE_TO_NODE_TYPE,
|
|
1296
|
+
_iter_semantic_cooccurrence_pairs,
|
|
1297
|
+
_link_cross_page_entities,
|
|
1298
|
+
_make_node_id,
|
|
1299
|
+
)
|
|
1300
|
+
import networkx as nx
|
|
1281
1301
|
from voidaccess_cli.adapters.sqlite import save_relationships
|
|
1282
1302
|
|
|
1283
1303
|
edges: list[dict] = []
|
|
@@ -1292,6 +1312,7 @@ def _build_cooccurrence_edges(
|
|
|
1292
1312
|
.filter(Entity.investigation_id == inv_uuid)
|
|
1293
1313
|
.all()
|
|
1294
1314
|
)
|
|
1315
|
+
entity_rows_loaded = len(rows)
|
|
1295
1316
|
by_page: dict[uuid.UUID, list] = {}
|
|
1296
1317
|
for ent in rows:
|
|
1297
1318
|
page_id = getattr(ent, "page_id", None)
|
|
@@ -1316,6 +1337,31 @@ def _build_cooccurrence_edges(
|
|
|
1316
1337
|
"confidence": 0.8,
|
|
1317
1338
|
}
|
|
1318
1339
|
)
|
|
1340
|
+
intra_page_edges = len(edges)
|
|
1341
|
+
|
|
1342
|
+
# Calculate the same cross-page bridge metric used by the graph
|
|
1343
|
+
# builder, while the ORM rows are still attached to their pages. The
|
|
1344
|
+
# CLI continues to persist its bounded co-occurrence set below, but
|
|
1345
|
+
# the live UI now exposes all three already-computed graph metrics.
|
|
1346
|
+
graph = nx.MultiDiGraph()
|
|
1347
|
+
for ent in rows:
|
|
1348
|
+
page_url = ent.page.url if ent.page else ""
|
|
1349
|
+
node_id = _make_node_id(ent.entity_type, ent.value, page_url)
|
|
1350
|
+
graph.add_node(
|
|
1351
|
+
node_id,
|
|
1352
|
+
node_type=_ENTITY_TYPE_TO_NODE_TYPE.get(ent.entity_type, ""),
|
|
1353
|
+
confidence=float(ent.confidence or 0.0),
|
|
1354
|
+
metadata={"confidence": float(ent.confidence or 0.0)},
|
|
1355
|
+
)
|
|
1356
|
+
cross_page_edges = _link_cross_page_entities(graph, rows, inv_uuid)
|
|
1357
|
+
if metrics_callback:
|
|
1358
|
+
metrics_callback(
|
|
1359
|
+
{
|
|
1360
|
+
"entity_rows_loaded": entity_rows_loaded,
|
|
1361
|
+
"intra_page_edges": intra_page_edges,
|
|
1362
|
+
"cross_page_edges": cross_page_edges,
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1319
1365
|
if progress_callback:
|
|
1320
1366
|
progress_callback("loading persisted relationships")
|
|
1321
1367
|
written = save_relationships(investigation_id, edges)
|
|
@@ -1408,6 +1454,8 @@ def _slugify(s: str) -> str:
|
|
|
1408
1454
|
|
|
1409
1455
|
|
|
1410
1456
|
def _render_markdown(payload: dict[str, Any]) -> str:
|
|
1457
|
+
from extractor.identity import entity_display_id
|
|
1458
|
+
|
|
1411
1459
|
lines: list[str] = []
|
|
1412
1460
|
lines.append(f"# Investigation: {payload['query']}")
|
|
1413
1461
|
lines.append(
|
|
@@ -184,12 +184,14 @@ def _print_path(data: dict, from_value: str, to_value: str) -> None:
|
|
|
184
184
|
def _print_summary(data: dict) -> None:
|
|
185
185
|
inv = data.get("investigation") or data
|
|
186
186
|
entities = data.get("entities", [])
|
|
187
|
+
relationships = data.get("relationships", []) or []
|
|
187
188
|
table = Table(title="Investigation summary")
|
|
188
189
|
table.add_column("Field", style="bold")
|
|
189
190
|
table.add_column("Value")
|
|
190
191
|
table.add_row("Query", str(inv.get("query") or ""))
|
|
191
192
|
table.add_row("Status", str(inv.get("status") or ""))
|
|
192
193
|
table.add_row("Entities", str(len(entities)))
|
|
194
|
+
table.add_row("Relationships", str(len(relationships)))
|
|
193
195
|
table.add_row("Created", str(inv.get("created_at") or "")[:19])
|
|
194
196
|
table.add_row("Summary", (str(inv.get("summary") or "—"))[:120])
|
|
195
197
|
console.print(table)
|
|
@@ -317,10 +317,11 @@ def ensure_spacy_model(model_name: str = "en_core_web_sm") -> bool:
|
|
|
317
317
|
|
|
318
318
|
def apply_env(config: Optional[dict[str, Any]] = None) -> None:
|
|
319
319
|
"""
|
|
320
|
-
Push saved config into os.environ
|
|
321
|
-
modules (config.py, llm.py, sources/*) pick up the values at import.
|
|
320
|
+
Push saved config into os.environ before importing runtime modules.
|
|
322
321
|
|
|
323
|
-
|
|
322
|
+
This remains the single environment-injection boundary for the CLI;
|
|
323
|
+
runtime modules must resolve environment-dependent values when they are
|
|
324
|
+
used rather than freezing them during import.
|
|
324
325
|
"""
|
|
325
326
|
cfg = config or load_config()
|
|
326
327
|
|
|
@@ -390,4 +391,3 @@ def apply_env(config: Optional[dict[str, Any]] = None) -> None:
|
|
|
390
391
|
os.environ.pop(key, None)
|
|
391
392
|
|
|
392
393
|
|
|
393
|
-
|
|
@@ -521,12 +521,14 @@ def main(
|
|
|
521
521
|
console.print(f"voidaccess {__version__}")
|
|
522
522
|
raise typer.Exit()
|
|
523
523
|
cli_config.apply_env()
|
|
524
|
+
# Validate after the CLI has injected its runtime environment. This keeps
|
|
525
|
+
# --help/--version cold-start safe while preserving the required-key and
|
|
526
|
+
# once-per-process optional-key checks for real command invocations.
|
|
527
|
+
import config as runtime_config
|
|
528
|
+
runtime_config.validate_config()
|
|
524
529
|
if not no_banner and ctx.invoked_subcommand:
|
|
525
530
|
show_banner(console)
|
|
526
531
|
|
|
527
532
|
|
|
528
533
|
if __name__ == "__main__":
|
|
529
534
|
app()
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0002_add_investigation_status_column.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0007_add_actor_style_profiles.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0011_add_page_extraction_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0018_add_user_id_to_investigations.py
RENAMED
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0020_add_entity_source_tracking.py
RENAMED
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0021_add_search_engine_stats.py
RENAMED
|
File without changes
|
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0023_add_investigation_metadata.py
RENAMED
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0024_add_investigation_step_metrics.py
RENAMED
|
File without changes
|
{voidaccess-1.9.0 → voidaccess-1.9.1}/db/migrations/versions/0025_add_cancellation_requested.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|