voidaccess 1.4.0__tar.gz → 1.4.2__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 (178) hide show
  1. {voidaccess-1.4.0 → voidaccess-1.4.2}/PKG-INFO +1 -1
  2. {voidaccess-1.4.0 → voidaccess-1.4.2}/pyproject.toml +1 -1
  3. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/PKG-INFO +1 -1
  4. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/__init__.py +1 -1
  5. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/export.py +7 -3
  6. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/investigate.py +20 -0
  7. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/show.py +22 -0
  8. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/config.py +6 -0
  9. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/main.py +22 -10
  10. {voidaccess-1.4.0 → voidaccess-1.4.2}/LICENSE +0 -0
  11. {voidaccess-1.4.0 → voidaccess-1.4.2}/README.md +0 -0
  12. {voidaccess-1.4.0 → voidaccess-1.4.2}/analysis/__init__.py +0 -0
  13. {voidaccess-1.4.0 → voidaccess-1.4.2}/analysis/opsec.py +0 -0
  14. {voidaccess-1.4.0 → voidaccess-1.4.2}/analysis/patterns.py +0 -0
  15. {voidaccess-1.4.0 → voidaccess-1.4.2}/analysis/temporal.py +0 -0
  16. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/__init__.py +0 -0
  17. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/auth.py +0 -0
  18. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/main.py +0 -0
  19. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/__init__.py +0 -0
  20. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/admin.py +0 -0
  21. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/auth.py +0 -0
  22. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/entities.py +0 -0
  23. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/export.py +0 -0
  24. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/investigations.py +0 -0
  25. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/monitors.py +0 -0
  26. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/search.py +0 -0
  27. {voidaccess-1.4.0 → voidaccess-1.4.2}/api/routes/settings.py +0 -0
  28. {voidaccess-1.4.0 → voidaccess-1.4.2}/auth/__init__.py +0 -0
  29. {voidaccess-1.4.0 → voidaccess-1.4.2}/auth/token_blacklist.py +0 -0
  30. {voidaccess-1.4.0 → voidaccess-1.4.2}/config.py +0 -0
  31. {voidaccess-1.4.0 → voidaccess-1.4.2}/crawler/__init__.py +0 -0
  32. {voidaccess-1.4.0 → voidaccess-1.4.2}/crawler/dedup.py +0 -0
  33. {voidaccess-1.4.0 → voidaccess-1.4.2}/crawler/frontier.py +0 -0
  34. {voidaccess-1.4.0 → voidaccess-1.4.2}/crawler/spider.py +0 -0
  35. {voidaccess-1.4.0 → voidaccess-1.4.2}/crawler/utils.py +0 -0
  36. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/__init__.py +0 -0
  37. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/__init__.py +0 -0
  38. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/env.py +0 -0
  39. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0001_initial_schema.py +0 -0
  40. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0002_add_investigation_status_column.py +0 -0
  41. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0002_add_missing_tables.py +0 -0
  42. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0003_add_canonical_value_and_entity_links.py +0 -0
  43. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0004_add_page_posted_at.py +0 -0
  44. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0005_add_extraction_method.py +0 -0
  45. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0006_add_monitor_alerts.py +0 -0
  46. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0007_add_actor_style_profiles.py +0 -0
  47. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0008_add_users_table.py +0 -0
  48. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0009_add_investigation_id_to_relationships.py +0 -0
  49. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0010_add_composite_index_entity_relationships.py +0 -0
  50. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0011_add_page_extraction_cache.py +0 -0
  51. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0013_add_graph_status.py +0 -0
  52. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0015_add_progress_fields.py +0 -0
  53. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0016_backfill_graph_status.py +0 -0
  54. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0017_add_user_api_keys.py +0 -0
  55. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0018_add_user_id_to_investigations.py +0 -0
  56. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0019_add_content_safety_log.py +0 -0
  57. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/migrations/versions/0020_add_entity_source_tracking.py +0 -0
  58. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/models.py +0 -0
  59. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/queries.py +0 -0
  60. {voidaccess-1.4.0 → voidaccess-1.4.2}/db/session.py +0 -0
  61. {voidaccess-1.4.0 → voidaccess-1.4.2}/export/__init__.py +0 -0
  62. {voidaccess-1.4.0 → voidaccess-1.4.2}/export/misp.py +0 -0
  63. {voidaccess-1.4.0 → voidaccess-1.4.2}/export/sigma.py +0 -0
  64. {voidaccess-1.4.0 → voidaccess-1.4.2}/export/stix.py +0 -0
  65. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/__init__.py +0 -0
  66. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/llm_extract.py +0 -0
  67. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/ner.py +0 -0
  68. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/normalizer.py +0 -0
  69. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/pipeline.py +0 -0
  70. {voidaccess-1.4.0 → voidaccess-1.4.2}/extractor/regex_patterns.py +0 -0
  71. {voidaccess-1.4.0 → voidaccess-1.4.2}/fingerprint/__init__.py +0 -0
  72. {voidaccess-1.4.0 → voidaccess-1.4.2}/fingerprint/profiler.py +0 -0
  73. {voidaccess-1.4.0 → voidaccess-1.4.2}/fingerprint/stylometry.py +0 -0
  74. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/__init__.py +0 -0
  75. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/builder.py +0 -0
  76. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/export.py +0 -0
  77. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/model.py +0 -0
  78. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/queries.py +0 -0
  79. {voidaccess-1.4.0 → voidaccess-1.4.2}/graph/visualize.py +0 -0
  80. {voidaccess-1.4.0 → voidaccess-1.4.2}/i18n/__init__.py +0 -0
  81. {voidaccess-1.4.0 → voidaccess-1.4.2}/i18n/detect.py +0 -0
  82. {voidaccess-1.4.0 → voidaccess-1.4.2}/i18n/query_expand.py +0 -0
  83. {voidaccess-1.4.0 → voidaccess-1.4.2}/i18n/translate.py +0 -0
  84. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/__init__.py +0 -0
  85. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/_db.py +0 -0
  86. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/alerts.py +0 -0
  87. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/config.py +0 -0
  88. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/diff.py +0 -0
  89. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/jobs.py +0 -0
  90. {voidaccess-1.4.0 → voidaccess-1.4.2}/monitor/scheduler.py +0 -0
  91. {voidaccess-1.4.0 → voidaccess-1.4.2}/scraper/__init__.py +0 -0
  92. {voidaccess-1.4.0 → voidaccess-1.4.2}/scraper/scrape.py +0 -0
  93. {voidaccess-1.4.0 → voidaccess-1.4.2}/scraper/scrape_js.py +0 -0
  94. {voidaccess-1.4.0 → voidaccess-1.4.2}/search/__init__.py +0 -0
  95. {voidaccess-1.4.0 → voidaccess-1.4.2}/search/circuit_breaker.py +0 -0
  96. {voidaccess-1.4.0 → voidaccess-1.4.2}/search/search.py +0 -0
  97. {voidaccess-1.4.0 → voidaccess-1.4.2}/setup.cfg +0 -0
  98. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/__init__.py +0 -0
  99. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/blockchain.py +0 -0
  100. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/cache.py +0 -0
  101. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/cisa.py +0 -0
  102. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/dns_enrichment.py +0 -0
  103. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/domain_reputation.py +0 -0
  104. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/email_reputation.py +0 -0
  105. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/engines.py +0 -0
  106. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/enrichment.py +0 -0
  107. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/github_scraper.py +0 -0
  108. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/gitlab_scraper.py +0 -0
  109. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/hash_reputation.py +0 -0
  110. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/historical_intel.py +0 -0
  111. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/ip_reputation.py +0 -0
  112. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/paste_scraper.py +0 -0
  113. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/pastes.py +0 -0
  114. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/rss_scraper.py +0 -0
  115. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/seed_manager.py +0 -0
  116. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/seeds.py +0 -0
  117. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/shodan.py +0 -0
  118. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/telegram.py +0 -0
  119. {voidaccess-1.4.0 → voidaccess-1.4.2}/sources/virustotal.py +0 -0
  120. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_analysis_opsec.py +0 -0
  121. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_analysis_stylometry.py +0 -0
  122. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_analysis_temporal.py +0 -0
  123. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_api.py +0 -0
  124. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_api_monitors.py +0 -0
  125. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_blockchain.py +0 -0
  126. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_config.py +0 -0
  127. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_crawler.py +0 -0
  128. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_db.py +0 -0
  129. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_dns_enrichment.py +0 -0
  130. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_domain_reputation.py +0 -0
  131. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_email_reputation.py +0 -0
  132. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_fingerprint.py +0 -0
  133. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_github_scraper.py +0 -0
  134. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_gitlab_scraper.py +0 -0
  135. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_graph.py +0 -0
  136. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_hash_reputation.py +0 -0
  137. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_i18n.py +0 -0
  138. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_ip_reputation.py +0 -0
  139. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_llm.py +0 -0
  140. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_llm_utils.py +0 -0
  141. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_model_singleton.py +0 -0
  142. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_monitor.py +0 -0
  143. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_pagination.py +0 -0
  144. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_paste_scraper.py +0 -0
  145. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_rss_scraper.py +0 -0
  146. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_scrape_js.py +0 -0
  147. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_settings.py +0 -0
  148. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_sources.py +0 -0
  149. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_sources_enrichment_new.py +0 -0
  150. {voidaccess-1.4.0 → voidaccess-1.4.2}/tests/test_vector.py +0 -0
  151. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/__init__.py +0 -0
  152. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/async_utils.py +0 -0
  153. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/content_safety.py +0 -0
  154. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/defang.py +0 -0
  155. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/encryption.py +0 -0
  156. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/ioc_freshness.py +0 -0
  157. {voidaccess-1.4.0 → voidaccess-1.4.2}/utils/user_keys.py +0 -0
  158. {voidaccess-1.4.0 → voidaccess-1.4.2}/vector/__init__.py +0 -0
  159. {voidaccess-1.4.0 → voidaccess-1.4.2}/vector/embedder.py +0 -0
  160. {voidaccess-1.4.0 → voidaccess-1.4.2}/vector/model_singleton.py +0 -0
  161. {voidaccess-1.4.0 → voidaccess-1.4.2}/vector/search.py +0 -0
  162. {voidaccess-1.4.0 → voidaccess-1.4.2}/vector/store.py +0 -0
  163. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess/__init__.py +0 -0
  164. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess/llm.py +0 -0
  165. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess/llm_utils.py +0 -0
  166. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/SOURCES.txt +0 -0
  167. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/dependency_links.txt +0 -0
  168. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/entry_points.txt +0 -0
  169. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/requires.txt +0 -0
  170. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess.egg-info/top_level.txt +0 -0
  171. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/adapters/__init__.py +0 -0
  172. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/adapters/sqlite.py +0 -0
  173. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/browser.py +0 -0
  174. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/__init__.py +0 -0
  175. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/configure.py +0 -0
  176. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/commands/enrich.py +0 -0
  177. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/display.py +0 -0
  178. {voidaccess-1.4.0 → voidaccess-1.4.2}/voidaccess_cli/tor_detect.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidaccess
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Dark web OSINT CLI — automated threat intelligence from query to report
5
5
  Author: VoidAccess
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "voidaccess"
7
- version = "1.4.0"
7
+ version = "1.4.2"
8
8
  description = "Dark web OSINT CLI — automated threat intelligence from query to report"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voidaccess
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Dark web OSINT CLI — automated threat intelligence from query to report
5
5
  Author: VoidAccess
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """voidaccess CLI — dark-web OSINT command-line interface."""
2
2
 
3
- __version__ = "1.3.0"
3
+ __version__ = "1.4.1"
@@ -39,7 +39,7 @@ def run(
39
39
  raise typer.Exit(code=1)
40
40
 
41
41
  payload, suffix = _render(fmt, inv_id, data)
42
- out_path = output or _default_out_path(target, suffix)
42
+ out_path = output or _default_out_path(target, suffix, fmt=fmt)
43
43
  out_path = Path(out_path).expanduser()
44
44
  out_path.parent.mkdir(parents=True, exist_ok=True)
45
45
  if isinstance(payload, bytes):
@@ -150,9 +150,13 @@ def _flatten_for_md(data: dict) -> dict:
150
150
  return data
151
151
 
152
152
 
153
- def _default_out_path(target: str, suffix: str) -> Path:
153
+ def _default_out_path(target: str, suffix: str, fmt: str = "") -> Path:
154
154
  p = Path(target).expanduser()
155
155
  if p.exists():
156
- return p.with_suffix(suffix)
156
+ candidate = p.with_suffix(suffix)
157
+ # Avoid overwriting input when suffix is the same (e.g. stix/misp .json)
158
+ if candidate == p and fmt and fmt not in ("json",):
159
+ return p.parent / f"{p.stem}-{fmt}{suffix}"
160
+ return candidate
157
161
  from voidaccess_cli import config as cli_config
158
162
  return cli_config.get_output_dir() / f"{target}{suffix}"
@@ -49,9 +49,28 @@ def run(
49
49
  from voidaccess_cli import config as cli_config
50
50
 
51
51
  cli_config.apply_env()
52
+
53
+ try:
54
+ import spacy
55
+ spacy.load("en_core_web_sm")
56
+ except Exception:
57
+ import subprocess, sys
58
+ from rich.console import Console
59
+ Console().print(" [dim]→[/dim] Installing spaCy NER model (one-time)...")
60
+ subprocess.run(
61
+ [sys.executable, "-m", "spacy", "download", "en_core_web_sm"],
62
+ capture_output=True,
63
+ )
64
+
52
65
  if quiet:
53
66
  logging.getLogger().setLevel(logging.ERROR)
54
67
 
68
+ from utils.content_safety import is_blocked_query
69
+ blocked, reason = is_blocked_query(query)
70
+ if blocked:
71
+ console.print(f"[red]Query blocked:[/red] {reason}")
72
+ raise typer.Exit(code=1)
73
+
55
74
  if not cli_config.is_configured() and not no_llm:
56
75
  console.print("[yellow]No LLM configured.[/yellow] Run [bold]voidaccess configure[/bold] first, or pass --no-llm.")
57
76
  raise typer.Exit(code=2)
@@ -382,6 +401,7 @@ async def _run_investigation(
382
401
  "query": query,
383
402
  "refined_query": refined,
384
403
  "model_used": chosen_model if llm is not None else None,
404
+ "status": "completed" if final_entities or scraped_pages else "completed_no_results",
385
405
  "created_at": datetime.now(timezone.utc).isoformat(),
386
406
  "summary": summary_text,
387
407
  "sources_used": sources_used,
@@ -24,6 +24,7 @@ def run(
24
24
  target: Optional[str] = typer.Argument(
25
25
  None, help="Investigation id or path to a .json export"
26
26
  ),
27
+ no_tui: bool = typer.Option(False, "--no-tui", help="Print summary table without launching TUI (for scripted use)."),
27
28
  ) -> None:
28
29
  """Open the entity browser TUI."""
29
30
  from voidaccess_cli import config as cli_config
@@ -32,6 +33,9 @@ def run(
32
33
  data: Optional[dict] = None
33
34
 
34
35
  if target is None:
36
+ if no_tui:
37
+ console.print("[yellow]No target specified.[/yellow]")
38
+ raise typer.Exit(code=1)
35
39
  target = _pick_recent()
36
40
  if target is None:
37
41
  console.print("[yellow]No investigations found. Run `voidaccess investigate` first.[/yellow]")
@@ -49,11 +53,29 @@ def run(
49
53
  console.print(f"[red]Unknown investigation:[/red] {target}")
50
54
  raise typer.Exit(code=1)
51
55
 
56
+ if no_tui:
57
+ _print_summary(data)
58
+ return
59
+
52
60
  from voidaccess_cli.browser import EntityBrowserApp
53
61
  app = EntityBrowserApp(data=data)
54
62
  app.run()
55
63
 
56
64
 
65
+ def _print_summary(data: dict) -> None:
66
+ inv = data.get("investigation") or data
67
+ entities = data.get("entities", [])
68
+ table = Table(title="Investigation summary")
69
+ table.add_column("Field", style="bold")
70
+ table.add_column("Value")
71
+ table.add_row("Query", str(inv.get("query") or ""))
72
+ table.add_row("Status", str(inv.get("status") or ""))
73
+ table.add_row("Entities", str(len(entities)))
74
+ table.add_row("Created", str(inv.get("created_at") or "")[:19])
75
+ table.add_row("Summary", (str(inv.get("summary") or "—"))[:120])
76
+ console.print(table)
77
+
78
+
57
79
  def _pick_recent() -> Optional[str]:
58
80
  from voidaccess_cli.adapters import sqlite as sqlite_adapter
59
81
  sqlite_adapter.init_db()
@@ -178,3 +178,9 @@ def apply_env(config: Optional[dict[str, Any]] = None) -> None:
178
178
  # Enrichment keys
179
179
  for k, v in (cfg.get("enrichment_keys") or {}).items():
180
180
  _set_env_if_present(k, v, clear_if_empty=True)
181
+
182
+ # Keyless APIs (ThreatFox/URLhaus/MalwareBazaar/abuse.ch) must never
183
+ # receive an empty auth header — clear any empty env remnant.
184
+ for key in ("ABUSECH_API_KEY", "VT_API_KEY", "OTX_API_KEY"):
185
+ if not os.environ.get(key):
186
+ os.environ.pop(key, None)
@@ -14,6 +14,7 @@ import sys
14
14
  # Force UTF-8 on Windows consoles so rich glyphs render reliably
15
15
  if sys.platform == "win32":
16
16
  os.environ.setdefault("PYTHONIOENCODING", "utf-8")
17
+ os.environ.setdefault("PYTHONUTF8", "1")
17
18
  try:
18
19
  sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
19
20
  sys.stderr.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
@@ -21,6 +22,7 @@ if sys.platform == "win32":
21
22
  pass
22
23
 
23
24
  import typer
25
+ from rich.align import Align
24
26
  from rich.console import Console
25
27
  from rich.table import Table
26
28
 
@@ -30,13 +32,17 @@ from voidaccess_cli.commands import configure, enrich, export, investigate, show
30
32
 
31
33
  console = Console()
32
34
  BANNER = """\
33
- [color(183)] ░░[color(141)]████████[color(183)]░░[/]
34
- [color(183)] ░░[color(141)]████████████[color(183)]░░[/]
35
- [color(183)] ░░[color(141)]██████████████[color(183)]░░[/]
36
- [color(183)] ░░[color(141)]████[/] [bright_white]void[/] [color(141)]████[color(183)]░░[/]
37
- [color(183)] ░░[color(141)]██████████████[color(183)]░░[/]
38
- [color(183)] ░░[color(141)]████████████[color(183)]░░[/]
39
- [color(183)] ░░[color(141)]████████[color(183)]░░[/]
35
+ [color(183)] ░░░░░[color(141)][color(183)]░░░░░[/]
36
+ [color(183)] ░░[color(141)]█████████████[color(183)]░░[/]
37
+ [color(183)][color(141)]█████████████████[color(183)][/]
38
+ [color(183)][color(141)]███████████████████[color(183)][/]
39
+ [color(183)][color(141)]███████████████████[color(183)][/]
40
+ [color(141)]██████[/] [bright_white]void[/] [color(141)]███████[/]
41
+ [color(183)][color(141)]███████████████████[color(183)][/]
42
+ [color(183)]░[color(141)]███████████████████[color(183)]░[/]
43
+ [color(183)] ░[color(141)]█████████████████[color(183)]░[/]
44
+ [color(183)] ░░[color(141)]█████████████[color(183)]░░[/]
45
+ [color(183)] ░░░░░[color(141)]█[color(183)]░░░░░[/]
40
46
  [dim white] dark web osint intelligence[/dim white]"""
41
47
 
42
48
  app = typer.Typer(
@@ -154,10 +160,16 @@ def version() -> None:
154
160
 
155
161
 
156
162
  def show_banner(console: Console) -> None:
157
- if not sys.stdout.isatty():
163
+ import shutil
164
+ if os.environ.get("TERM") == "dumb":
165
+ return
166
+ if not sys.stdout.isatty() and "PS1" not in os.environ and os.name != "nt":
158
167
  return
159
168
  console.print()
160
- console.print(BANNER, justify="center")
169
+ raw_line = " oooooXooooo " # widest line, 21 chars
170
+ pad = max(0, (console.width - len(raw_line)) // 2)
171
+ for line in BANNER.split("\n"):
172
+ console.print(" " * pad + line)
161
173
  console.print()
162
174
 
163
175
 
@@ -171,7 +183,7 @@ def main(
171
183
  ) -> None:
172
184
  """Set env vars and render banner before command execution."""
173
185
  cli_config.apply_env()
174
- if not no_banner and not ctx.invoked_subcommand:
186
+ if not no_banner and ctx.invoked_subcommand:
175
187
  show_banner(console)
176
188
 
177
189
 
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
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