feat(demo): add tui-demo command + fix demo-full recipe

cre tui-demo (8 seconds default) renders the live TUI with synthetic
events: 4 fake credentials going through rotation states, a policy
violation, drift detection, an apply-step failure, and a critical alert.
Lets you see what watch mode looks like without seeding any real data.

demo-full: dropped the bogus --config=... flag (cre run never
implemented config-file loading; uses env vars + --db). Now exports
the four service URLs (DATABASE_URL, AWS_ENDPOINT, VAULT_ADDR,
GITHUB_API_BASE) at the user's manually-set ports (6022/45666/18201/7115)
and runs the daemon against the real Postgres. Added demo-full-status
to peek at container health.
This commit is contained in:
CarterPerez-dev 2026-04-29 01:34:25 -04:00
parent 73e3488edc
commit 7835aa4dc5
5 changed files with 169 additions and 10 deletions

View File

@ -87,12 +87,25 @@ export framework="soc2" out="evidence.zip" db=db_default:
demo:
shards build cre && {{binary}} demo
[group('demo')]
tui-demo seconds="8":
shards build cre && {{binary}} tui-demo --seconds={{seconds}}
[group('demo')]
demo-full:
docker compose -f docker/docker-compose.yml up -d
@echo "Waiting for services..."
@echo "Waiting for services to be healthy..."
@sleep 8
shards build cre && {{binary}} run --config=config/demo-full.cr
shards build cre
@echo ""
@echo "Stack up. Daemon will run against Postgres on port 6022."
@echo "Press Ctrl+C to stop the daemon, then 'just demo-full-down' to tear down the stack."
@echo ""
DATABASE_URL=postgres://cre:cre@localhost:6022/cre \
AWS_ENDPOINT=http://localhost:45666 \
VAULT_ADDR=http://localhost:18201 \
GITHUB_API_BASE=http://localhost:7115 \
{{binary}} run --db=postgres://cre:cre@localhost:6022/cre
[group('demo')]
demo-full-down:
@ -102,6 +115,10 @@ demo-full-down:
demo-full-logs:
docker compose -f docker/docker-compose.yml logs -f --tail=50
[group('demo')]
demo-full-status:
docker compose -f docker/docker-compose.yml ps
# =============================================================================
# Test
# =============================================================================

View File

@ -24,6 +24,7 @@ module CRE::Cli
export --framework=<name> generate signed compliance evidence bundle
audit verify verify hash chain + HMAC ratchet + Merkle batches
demo tier-1 zero-deps demo (SQLite + .env rotator)
tui-demo 8-second TUI preview with synthetic events
version print version
help this message
@ -43,14 +44,15 @@ module CRE::Cli
when "version"
io.puts CRE::VERSION
0
when "run" then Commands::Run.new.execute(argv, io)
when "watch" then Commands::Watch.new.execute(argv, io)
when "check" then Commands::Check.new.execute(argv, io)
when "rotate" then Commands::Rotate.new.execute(argv, io)
when "policy" then Commands::Policy.new.execute(argv, io)
when "export" then Commands::Export.new.execute(argv, io)
when "audit" then Commands::Audit.new.execute(argv, io)
when "demo" then Commands::Demo.new.execute(argv, io)
when "run" then Commands::Run.new.execute(argv, io)
when "watch" then Commands::Watch.new.execute(argv, io)
when "check" then Commands::Check.new.execute(argv, io)
when "rotate" then Commands::Rotate.new.execute(argv, io)
when "policy" then Commands::Policy.new.execute(argv, io)
when "export" then Commands::Export.new.execute(argv, io)
when "audit" then Commands::Audit.new.execute(argv, io)
when "demo" then Commands::Demo.new.execute(argv, io)
when "tui-demo" then Commands::TuiDemo.new.execute(argv, io)
else
io.puts "unknown subcommand: #{subcommand}"
io.puts USAGE

View File

@ -11,4 +11,5 @@ require "./commands/policy"
require "./commands/export"
require "./commands/audit"
require "./commands/demo"
require "./commands/tui_demo"
require "./commands/version"

View File

@ -0,0 +1,23 @@
# ===================
# ©AngelaMos | 2026
# tui_demo.cr
# ===================
require "../../demo/tui_demo"
module CRE::Cli::Commands
class TuiDemo
def execute(argv : Array(String), io : IO) : Int32
_help_requested = false
seconds = 8
OptionParser.parse(argv) do |parser|
parser.banner = "Usage: cre tui-demo [--seconds=N]"
parser.on("--seconds=N", "duration in seconds (default 8)") { |s| seconds = s.to_i }
parser.on("-h", "--help") { _help_requested = true; io.puts parser }
end
return 0 if _help_requested
CRE::Demo::TuiDemo.run(io, seconds)
end
end
end

View File

@ -0,0 +1,116 @@
# ===================
# ©AngelaMos | 2026
# tui_demo.cr
# ===================
require "../engine/event_bus"
require "../tui/tui"
require "../events/credential_events"
require "../events/system_events"
module CRE::Demo
# TuiDemo synthesizes a stream of fake events so you can SEE what the
# live TUI looks like without running the full daemon. Eight seconds of
# narrated activity, then it shuts down cleanly.
module TuiDemo
def self.run(io : IO = STDOUT, seconds : Int32 = 8) : Int32
bus = CRE::Engine::EventBus.new
tui = CRE::Tui::Tui.new(bus, io: io, refresh_interval: 150.milliseconds)
bus.run
tui.start
script_fiber = spawn do
run_script(bus, seconds)
end
sleep seconds.seconds + 0.5.seconds
tui.stop
bus.stop
0
end
private def self.run_script(bus : CRE::Engine::EventBus, seconds : Int32) : Nil
cred_a = UUID.random
cred_b = UUID.random
cred_c = UUID.random
cred_d = UUID.random
# 0.5s: a policy violation arrives
sleep 0.5.seconds
bus.publish CRE::Events::PolicyViolation.new(cred_a, "production-databases", "credential exceeded max_age=30.days (last rotated 47 days ago)")
# 1.0s: rotation A starts
sleep 0.5.seconds
rot_a = UUID.random
bus.publish CRE::Events::RotationStarted.new(cred_a, rot_a, "aws_secretsmgr")
bus.publish CRE::Events::RotationStepStarted.new(cred_a, rot_a, :generate)
# 1.5s: drift detected on B
sleep 0.5.seconds
bus.publish CRE::Events::DriftDetected.new(cred_b, "abc123def456", "ffe098cba321")
# 2.0s: rotation A finishes generate, starts apply
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_a, rot_a, :generate)
bus.publish CRE::Events::RotationStepStarted.new(cred_a, rot_a, :apply)
# 2.5s: rotation A finishes apply, starts verify
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_a, rot_a, :apply)
bus.publish CRE::Events::RotationStepStarted.new(cred_a, rot_a, :verify)
# 3.0s: rotation C starts (parallel)
sleep 0.5.seconds
rot_c = UUID.random
bus.publish CRE::Events::RotationStarted.new(cred_c, rot_c, "github_pat")
bus.publish CRE::Events::RotationStepStarted.new(cred_c, rot_c, :generate)
# 3.5s: rotation A finishes verify, starts commit
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_a, rot_a, :verify)
bus.publish CRE::Events::RotationStepStarted.new(cred_a, rot_a, :commit)
# 4.0s: rotation A complete!
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_a, rot_a, :commit)
bus.publish CRE::Events::RotationCompleted.new(cred_a, rot_a)
# 4.5s: alert + a fourth rotation (vault) starts
sleep 0.5.seconds
bus.publish CRE::Events::AlertRaised.new(CRE::Events::Severity::Warn, "5 credentials approaching expiry within 7 days")
rot_d = UUID.random
bus.publish CRE::Events::RotationStarted.new(cred_d, rot_d, "vault_dynamic")
bus.publish CRE::Events::RotationStepStarted.new(cred_d, rot_d, :generate)
# 5.0s: rotation C finishes generate, then fails on apply
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_c, rot_c, :generate)
bus.publish CRE::Events::RotationStepStarted.new(cred_c, rot_c, :apply)
# 5.5s: rotation C apply fails!
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepFailed.new(cred_c, rot_c, :apply, "GitHub API 403 - PAT lacks admin:org scope")
bus.publish CRE::Events::RotationFailed.new(cred_c, rot_c, "GitHub API 403 - PAT lacks admin:org scope")
# 6.0s: rotation D progresses
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_d, rot_d, :generate)
bus.publish CRE::Events::RotationStepStarted.new(cred_d, rot_d, :apply)
bus.publish CRE::Events::RotationStepCompleted.new(cred_d, rot_d, :apply)
bus.publish CRE::Events::RotationStepStarted.new(cred_d, rot_d, :verify)
# 6.5s: critical alert
sleep 0.5.seconds
bus.publish CRE::Events::AlertRaised.new(CRE::Events::Severity::Critical, "1 critical: rotation_failure github_pat/deploy-bot needs manual intervention")
# 7.0s: rotation D finishes
sleep 0.5.seconds
bus.publish CRE::Events::RotationStepCompleted.new(cred_d, rot_d, :verify)
bus.publish CRE::Events::RotationStepStarted.new(cred_d, rot_d, :commit)
bus.publish CRE::Events::RotationStepCompleted.new(cred_d, rot_d, :commit)
bus.publish CRE::Events::RotationCompleted.new(cred_d, rot_d)
rescue ex
# silence: we may be torn down mid-script
end
end
end