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:
parent
73e3488edc
commit
7835aa4dc5
|
|
@ -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
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ require "./commands/policy"
|
|||
require "./commands/export"
|
||||
require "./commands/audit"
|
||||
require "./commands/demo"
|
||||
require "./commands/tui_demo"
|
||||
require "./commands/version"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue