HackTUI is a terminal-native purple team security operations platform built on Elixir and the BEAM runtime.
It combines:
- realtime telemetry ingestion
- alert and investigation lifecycle management
- terminal-first SOC operations
- bounded agent-assisted workflows using Jido
- AI interoperability through MCP (Model Context Protocol)
HackTUI is designed as a research and experimentation platform for autonomous and human-guided security operations.
This repository includes:
- the HackTUI platform
- the built-in MCP server
- Jido-based bounded agent components inside HackTUI
This repository does not include my external Hermes agent. Hermes was used as a development partner and can connect to HackTUI through MCP, but the private agent itself is not part of this codebase.
Most security platforms today are built around:
- large web dashboards
- centralized service layers
- conventional SIEM pipelines
- analyst workflows designed for browsers first
HackTUI explores a different direction:
- terminal-first operations
- actor-style concurrency on the BEAM
- bounded AI assistance
- structured security workflows
- agent-operable infrastructure
The goal is not to replace mature security platforms. The goal is to explore what a realtime, terminal-native, AI-accessible security system looks like when built from first principles in Elixir.
HackTUI is an Elixir umbrella application.
apps/
hacktui_agent/
hacktui_collab/
hacktui_core/
hacktui_hub/
hacktui_sensor/
hacktui_store/
hacktui_tui/
Shared domain logic and durable system concepts.
Contains:
- commands
- events
- aggregates
- investigation lifecycle logic
- alert lifecycle logic
- correlation and reporting primitives
Persistence boundary.
Responsible for storing and projecting:
- alerts
- cases
- observations
- approvals
- audit events
- query projections
Backed by Ecto and PostgreSQL.
System control plane and runtime coordination layer.
Responsible for:
- health reporting
- query services
- alert promotion
- case creation
- investigation orchestration
- privacy masking
- replay support
- runtime workflows
Telemetry ingestion boundary.
Responsible for collecting and forwarding:
- journald security events
- network flow telemetry
- process/runtime signals
Terminal SOC interface.
Responsible for rendering:
- alert queue
- case board
- approvals
- observations
- workflow views
- live dashboard views
Bounded agent integration.
Uses the real Jido runtime for advisory investigation workflows and includes the MCP server implementation that external agents can use to interact with HackTUI.
Experimental collaboration boundary for external integrations.
HackTUI currently supports:
- live host telemetry ingestion
- network flow ingestion through
tshark/dumpcap - journald security event ingestion
- process and BEAM runtime telemetry
- alert lifecycle management
- investigation case creation
- terminal-native SOC workflows
- MCP access for external AI agents
- Jido-backed agent runtime integration
- privacy masking for safer screenshots and demos
In practice, this means HackTUI can collect observations, promote suspicious activity into alerts, open cases, expose system state through MCP, and allow a bounded agent to assist with investigation while keeping a human operator in control.
HackTUI is built to work over SSH, in remote shells, and in environments where browser-first tooling is undesirable.
Alerts, cases, approvals, and observations are modeled explicitly and can be persisted and queried.
Agents help analyze, summarize, and investigate. Humans remain in control of final decisions.
The platform is intended to support replay, validation, and purple-team experimentation.
State transitions should leave inspectable records and remain understandable to operators.
- Elixir
- Erlang/OTP
- BEAM
- PostgreSQL
- Ecto
- Jido
- MCP
- tshark / dumpcap
- terminal UI workflows
Recommended versions:
- Elixir 1.19+
- Erlang/OTP 28+
- PostgreSQL 14+
tsharkdumpcap
Useful optional tools:
journalctl- Observer GUI support
git
HackTUI is currently best supported on Linux.
git clone https://github.com/HackTuah/HackTUI-Hermes-Jido.git
cd HackTUI-Hermes-Jidomix deps.getmix compilesudo apt update
sudo apt install -y erlang elixir postgresql postgresql-contrib wireshark tsharkFor Observer GUI support, you may also need:
sudo apt install -y libwxgtk3.2-dev libwxgtk-webview3.2-devsudo dnf install -y erlang elixir postgresql-server wireshark-cli wiresharksudo pacman -S elixir erlang postgresql wireshark-cli wireshark-qtwhich tshark
which dumpcapExpected output should be valid paths such as:
/usr/bin/tshark
/usr/bin/dumpcapsudo systemctl start postgresql
sudo systemctl enable postgresqlsudo -u postgres psqlInside psql:
CREATE USER hacktui WITH PASSWORD 'hacktui';
CREATE DATABASE hacktui_dev OWNER hacktui;
\qexport HACKTUI_START_REPO=true
export HACKTUI_DB_NAME=hacktui_dev
export HACKTUI_DB_USER=hacktui
export HACKTUI_DB_PASS=hacktui
export HACKTUI_DB_HOST=localhost
export HACKTUI_DB_PORT=5432mix ecto.create
mix ecto.migrateIf your environment uses app-specific migration tasks, run the equivalent store migration workflow for your setup.
iex -S mixApplication.ensure_all_started(:hacktui_store)
Application.ensure_all_started(:hacktui_hub)
Application.ensure_all_started(:hacktui_agent)
Application.ensure_all_started(:hacktui_sensor)
Application.ensure_all_started(:hacktui_tui)HacktuiHub.Health.status()
HacktuiAgent.Health.status()
HacktuiHub.QueryService.system_diagnostic()You should see the hub, store, and agent layers report healthy startup state.
Start the TUI:
mix hacktui.tuiDepending on your current implementation and workflow, you may also have an equivalent task exposed under the hub app. If so, this command should also work:
mix hacktui.liveThe terminal UI is intended to display:
- alert queue
- case board
- approval inbox
- recent observations
- system health
- workflow-specific views
Observer is extremely useful for inspecting BEAM processes, supervision trees, memory, and message flow.
Start the project:
iex -S mixThen launch Observer:
:observer.start()If your system requires explicit wx startup:
:wx.new()
:observer.start()Observer is useful for inspecting:
- supervision trees
- process state
- message queues
- ETS tables
- scheduler load
- application memory usage
HackTUI uses the real Jido runtime, not a local stub.
Run the following in IEx:
Code.ensure_loaded?(Jido)
Code.ensure_loaded?(Jido.Agent)
Code.ensure_loaded?(Jido.Action)All should return true.
Verify the application starts:
Application.ensure_all_started(:jido)
Application.spec(:jido)Locate the compiled modules:
:code.which(Jido)
:code.which(Jido.Agent)
:code.which(Jido.Action)Expected result: paths under _build/dev/lib/jido/ and _build/dev/lib/jido_action/.
HackTUI includes a built-in MCP server.
HACKTUI_MCP_STDIO=1 ./bin/hacktui-mcpbody='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"demo","version":"0.1"}}}'
printf '%s\n' "$body" | ./bin/hacktui-mcpYou should receive a valid JSON-RPC initialize response.
If present in your checkout:
./bin/hacktui-mcp-smokeStart IEx:
iex -S mixThen use the following commands.
HacktuiHub.Health.status()
HacktuiAgent.Health.status()
HacktuiHub.QueryService.system_diagnostic()HacktuiHub.QueryService.__info__(:functions)Process.whereis(HacktuiSensor.Forwarder)
Process.whereis(HacktuiHub.IngestService)DynamicSupervisor.which_children(HacktuiSensor.CollectorsSupervisor)HacktuiHub.QueryService.check_network_health()System.find_executable("tshark")
System.find_executable("dumpcap")To inspect the running network collector in detail:
network_pid =
(
DynamicSupervisor.which_children(HacktuiSensor.CollectorsSupervisor)
|> Enum.find_value(fn {_, pid, _, mods} ->
if HacktuiSensor.Collectors.Network in mods, do: pid, else: nil
end)
)Show the PID:
network_pidInspect collector state:
:sys.get_state(network_pid)This is useful for verifying:
- selected interface
- active port
- collector enablement
- collector error state
- startup timestamp
- resolved tshark path
HacktuiHub.QueryService.clear_buffer()ping -c 2 8.8.8.8
curl -I https://google.com
curl -I https://cloudflare.comHacktuiHub.QueryService.latest_observations()snapshot = HacktuiHub.QueryService.live_dashboard_snapshot(HacktuiStore.Repo, live_only: true)%{
alerts: length(snapshot.alerts),
cases: length(snapshot.cases),
approvals: length(snapshot.approvals),
observations: length(snapshot.observations)
}snapshot.observations
|> Enum.filter(&(Map.get(&1, :kind) == "network.flow"))
|> Enum.take(10)snapshot.observations
|> Enum.filter(&(Map.get(&1, :kind) in ["system.error", "journald.security", "network.flow"]))
|> Enum.take(20)Load the command struct:
alias HacktuiCore.Commands.CreateAlertCreate a high-severity alert through the runtime path:
HacktuiHub.Runtime.create_alert(
%CreateAlert{
alert_id: "hermes-demo-" <> Integer.to_string(System.unique_integer([:positive])),
title: "Beacon Detection",
severity: :high,
observation_refs: ["manual-net-1"],
actor: "hermes"
},
repo: HacktuiStore.Repo,
occurred_at: DateTime.utc_now(),
event_id: "evt-hermes-demo-" <> Integer.to_string(System.unique_integer([:positive]))
)Inspect the alert queue:
HacktuiHub.QueryService.alert_queue(HacktuiStore.Repo)Inspect the case board:
HacktuiHub.QueryService.case_board(HacktuiStore.Repo)Run the same pattern again with a different alert id:
HacktuiHub.Runtime.create_alert(
%CreateAlert{
alert_id: "hermes-demo-repeat-" <> Integer.to_string(System.unique_integer([:positive])),
title: "Beacon Detection",
severity: :high,
observation_refs: ["manual-net-1"],
actor: "hermes"
},
repo: HacktuiStore.Repo,
occurred_at: DateTime.utc_now(),
event_id: "evt-hermes-demo-repeat-" <> Integer.to_string(System.unique_integer([:positive]))
)Then inspect again:
HacktuiHub.QueryService.alert_queue(HacktuiStore.Repo)
HacktuiHub.QueryService.case_board(HacktuiStore.Repo)If you want to inject a test observation directly:
alias HacktuiCore.Commands.AcceptObservation
now = DateTime.utc_now() |> DateTime.truncate(:second)
obs = %AcceptObservation{
observation_id: "manual-net-1",
fingerprint: "manual-net-1",
source: "iex.manual",
kind: "network.flow",
summary: "simulated attacker beacon",
raw_message: "simulated attacker beacon",
severity: "high",
confidence: 0.95,
payload: %{
"src" => "10.0.0.99",
"dst" => "185.199.108.153",
"src_port" => 51515,
"dst_port" => 443,
"proto" => "TCP",
"service" => "HTTPS",
"site" => "malicious.test",
"summary" => "simulated attacker beacon",
"severity" => "high"
},
metadata: %{
collector: :manual_test,
severity: "high",
occurred_at: DateTime.to_iso8601(now),
observed_at: DateTime.to_iso8601(now),
category: "network",
tags: ["manual", "test", "beacon"]
},
observed_at: now,
received_at: now,
actor: "iex",
envelope_version: 1
}
HacktuiSensor.Forwarder.accept_observation(obs)Then inspect observations:
HacktuiHub.QueryService.latest_observations(HacktuiStore.Repo, limit: 10)HacktuiHub.QueryService.clear_buffer()HacktuiStore.Repo.delete_all(HacktuiStore.Schema.CaseRecord)
HacktuiStore.Repo.delete_all(HacktuiStore.Schema.Alert)HacktuiStore.Repo.delete_all(HacktuiStore.Schema.Observation)
HacktuiHub.QueryService.clear_buffer()HacktuiHub.QueryService.alert_queue(HacktuiStore.Repo)
HacktuiHub.QueryService.case_board(HacktuiStore.Repo)
HacktuiHub.QueryService.live_dashboard_snapshot(HacktuiStore.Repo, live_only: true)Start the TUI:
mix hacktui.tuiStart IEx:
iex -S mixThen check state:
HacktuiHub.Health.status()
HacktuiAgent.Health.status()
HacktuiHub.QueryService.alert_queue(HacktuiStore.Repo)
HacktuiHub.QueryService.case_board(HacktuiStore.Repo)
HacktuiHub.QueryService.latest_observations()Trigger a live alert while the TUI is open:
alias HacktuiCore.Commands.CreateAlert
HacktuiHub.Runtime.create_alert(
%CreateAlert{
alert_id: "live-demo-" <> Integer.to_string(System.unique_integer([:positive])),
title: "Hermes Beacon Detection",
severity: :high,
observation_refs: ["manual-net-1"],
actor: "hermes"
},
repo: HacktuiStore.Repo,
occurred_at: DateTime.utc_now(),
event_id: "evt-live-demo-" <> Integer.to_string(System.unique_integer([:positive]))
)mix testmix test apps/hacktui_core/test
mix test apps/hacktui_hub/test
mix test apps/hacktui_agent/test
mix test apps/hacktui_sensor/test
mix test apps/hacktui_store/test
mix test apps/hacktui_tui/testmix test apps/hacktui_hub/test/hacktui_hub_runtime_test.exsmix test apps/hacktui_hub/test/hacktui_hub_runtime_test.exs:42mix test --tracemix test --failedFormat everything:
mix formatCheck formatting only:
mix format --check-formattedCompile with warnings as errors:
mix compile --warnings-as-errorsBefore publishing screenshots or logs:
- review environment variables
- verify no secrets are committed
- sanitize host identifiers when needed
- use privacy masking when sharing screenshots publicly
- review captured telemetry before publishing
Before pushing to GitHub, a quick sanity check:
git ls-files | grep env
git ls-files | grep key
git statusExpected result: no private .env files, no secret key files, and only intentional project files tracked.
This repository does not include my private Hermes runtime or personal agent configuration.
It does include:
- the built-in MCP server
- Jido integration inside HackTUI
- bounded agent components used by the system itself
HackTUI is a research prototype exploring:
- terminal-native SOC workflows
- purple-team telemetry and replay
- bounded agent assistance
- AI-operable security infrastructure
- BEAM-native security system design
It is not production ready, but it already demonstrates a cohesive architecture for telemetry ingestion, alerting, case management, AI interoperability, and operator-driven security workflows.
If you want the shortest possible path to a working system:
mix deps.get
mix compile
iex -S mixThen in IEx:
Application.ensure_all_started(:hacktui_store)
Application.ensure_all_started(:hacktui_hub)
Application.ensure_all_started(:hacktui_agent)
Application.ensure_all_started(:hacktui_sensor)
Application.ensure_all_started(:hacktui_tui)
HacktuiHub.Health.status()
HacktuiAgent.Health.status()
HacktuiHub.QueryService.check_network_health()Then in another terminal:
mix hacktui.tuiAnd if you want to test MCP:
body='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"demo","version":"0.1"}}}'
printf '%s\n' "$body" | ./bin/hacktui-mcpHackTUI is licensed under the Apache License, Version 2.0. See LICENSE
for the full terms and NOTICE for attribution requirements.
Apache-2.0 was chosen over a simpler permissive license because it includes an explicit patent grant and contribution terms, which matter for security research software that may be evaluated, extended, or redistributed by external parties.
Contributions are accepted under the same license; see CONTRIBUTING.md.