diff --git a/README.md b/README.md
index 6e63e1b..6eaee26 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,20 @@
-# sonacli
+
+
+
-`sonacli` is a CLI for consuming SonarQube analysis reports.
+sonacli
+
+A CLI for consuming SonarQube analysis reports.
+
+
+
+
+
+
+
+
+
+
> SonarQube is an open-source platform for continuous inspection of code quality and security. It ships in several editions: **Community Build** (free, open-source, single-node), **Server** (commercial), **Data Center** (high-availability), and **Cloud** (SaaS, hosted by Sonar). `sonacli` targets the Community Build only.
diff --git a/docs/_demo-source/sonar-project.properties b/docs/_demo-source/sonar-project.properties
new file mode 100644
index 0000000..eb99103
--- /dev/null
+++ b/docs/_demo-source/sonar-project.properties
@@ -0,0 +1,4 @@
+sonar.projectKey=payments-api
+sonar.projectName=Payments API
+sonar.sources=src
+sonar.sourceEncoding=UTF-8
diff --git a/docs/_demo-source/src/auth.go b/docs/_demo-source/src/auth.go
new file mode 100644
index 0000000..d8c1d02
--- /dev/null
+++ b/docs/_demo-source/src/auth.go
@@ -0,0 +1,26 @@
+// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
+// Not compiled by the sonacli module — the parent directory is Go-ignored.
+package payments
+
+import (
+ "fmt"
+ "net/http"
+)
+
+const adminPassword = "replace-me-in-real-deployments"
+
+const stripeSecretKey = "replace-me-in-real-deployments"
+
+func Authenticate(user, pass string) bool {
+ if user == "admin" && pass == adminPassword {
+ return true
+ }
+
+ return false
+}
+
+func CallStripe() {
+ req, _ := http.NewRequest("GET", "https://api.stripe.com/v1/charges", nil)
+ req.Header.Set("Authorization", "Bearer "+stripeSecretKey)
+ fmt.Println(req)
+}
diff --git a/docs/_demo-source/src/invoice.go b/docs/_demo-source/src/invoice.go
new file mode 100644
index 0000000..6c49b11
--- /dev/null
+++ b/docs/_demo-source/src/invoice.go
@@ -0,0 +1,25 @@
+// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
+// Not compiled by the sonacli module — the parent directory is Go-ignored.
+package payments
+
+import "fmt"
+
+// TODO: migrate to the new billing engine before Q3
+// FIXME: handle partial refunds
+// TODO: add idempotency keys once the gateway supports them
+
+func SendInvoice(id string) {
+ total := 0
+ total = 100
+ total = 200
+
+ fmt.Println("invoice", id, total)
+}
+
+func duplicate() {
+ fmt.Println("dup")
+}
+
+func duplicate2() {
+ fmt.Println("dup")
+}
diff --git a/docs/_demo-source/src/pricing.go b/docs/_demo-source/src/pricing.go
new file mode 100644
index 0000000..553e336
--- /dev/null
+++ b/docs/_demo-source/src/pricing.go
@@ -0,0 +1,59 @@
+// Deliberately problematic demo fixture used to regenerate docs/demo.gif.
+// Not compiled by the sonacli module — the parent directory is Go-ignored.
+package payments
+
+import "fmt"
+
+func ComputeDiscount(tier string, quantity int, seasonal bool, vip bool, region string) float64 {
+ discount := 0.0
+
+ if tier == "gold" {
+ if quantity > 100 {
+ if seasonal {
+ if vip {
+ discount = 0.35
+ } else {
+ discount = 0.25
+ }
+ } else {
+ if region == "EU" {
+ discount = 0.20
+ } else {
+ discount = 0.15
+ }
+ }
+ } else {
+ if vip {
+ discount = 0.15
+ } else {
+ discount = 0.10
+ }
+ }
+ } else if tier == "silver" {
+ if quantity > 50 {
+ if seasonal {
+ discount = 0.15
+ } else {
+ discount = 0.10
+ }
+ } else {
+ if vip {
+ discount = 0.08
+ } else {
+ discount = 0.05
+ }
+ }
+ } else {
+ if quantity > 20 {
+ discount = 0.05
+ } else {
+ discount = 0.0
+ }
+ }
+
+ return discount
+}
+
+func LegacyPricing() {
+ fmt.Println("legacy path")
+}
diff --git a/docs/demo.gif b/docs/demo.gif
new file mode 100644
index 0000000..c1ff825
Binary files /dev/null and b/docs/demo.gif differ
diff --git a/docs/demo.tape b/docs/demo.tape
new file mode 100644
index 0000000..ab8977e
--- /dev/null
+++ b/docs/demo.tape
@@ -0,0 +1,80 @@
+# sonacli demo tape — regenerates docs/demo.gif
+#
+# Prerequisites: make, Go 1.26+, podman, vhs, jq, curl, a cached
+# SonarScanner CLI (see scripts/seed-sample-project.sh for one).
+#
+# Regenerate:
+# 1. make build
+# 2. ./scripts/local-sonarqube.sh start
+# 3. Create and analyze the demo project (once per fresh SonarQube):
+# curl -fsS -u admin:SonacliAdmin1@ -X POST \
+# "http://127.0.0.1:9000/api/projects/create" \
+# --data-urlencode "project=payments-api" \
+# --data-urlencode "name=Payments API"
+# SCANNER=tests/bin/sample-project-tools/sonar-scanner-*/bin/sonar-scanner
+# SCAN_TOKEN=$(curl -fsS -u admin:SonacliAdmin1@ -X POST \
+# "http://127.0.0.1:9000/api/user_tokens/generate" \
+# --data-urlencode "name=demo-scan-$(date +%s)" | jq -r .token)
+# ( cd docs/_demo-source && \
+# SONAR_HOST_URL=http://127.0.0.1:9000 SONAR_TOKEN=$SCAN_TOKEN \
+# "$PWD/../../$SCANNER" )
+# 4. export TOKEN=$(curl -fsS -u admin:SonacliAdmin1@ -X POST \
+# "http://127.0.0.1:9000/api/user_tokens/generate" \
+# --data-urlencode "name=demo-$(date +%s)" | jq -r .token)
+# 5. export ISSUE_KEY=$(curl -fsS -u admin:SonacliAdmin1@ \
+# "http://127.0.0.1:9000/api/issues/search?componentKeys=payments-api&impactSeverities=HIGH" \
+# | jq -r '.issues[0].key')
+# 6. vhs docs/demo.tape
+
+Output docs/demo.gif
+
+Set Shell "bash"
+Set FontSize 14
+Set Width 1200
+Set Height 760
+Set Theme "Dracula"
+Set TypingSpeed 30ms
+Set PlaybackSpeed 1.0
+Set Padding 25
+Set WindowBar Colorful
+
+Hide
+Type 'export PATH="$PWD:$PATH" HOME=/tmp/sonacli-demo-home PS1="$ "'
+Enter
+Type 'mkdir -p $HOME && rm -rf $HOME/.sonacli && cd /tmp && clear'
+Enter
+Show
+
+Sleep 600ms
+
+# Scene 1 — Connect sonacli to a SonarQube instance.
+Type "sonacli auth setup -s http://127.0.0.1:9000 -t $TOKEN"
+Sleep 300ms
+Enter
+Sleep 1500ms
+
+# Scene 2 — Browse projects as pretty JSON.
+Type "sonacli project list --pretty"
+Sleep 300ms
+Enter
+Sleep 1800ms
+
+# Scene 3 — sonacli speaks JSON: compose with jq to reshape issues.
+Type "sonacli issue list payments-api | jq '.issues[] | {rule, sev: .impacts[0].severity, file: .component, line, message}'"
+Sleep 300ms
+Enter
+Sleep 2200ms
+
+# Scene 4 — Filter by impact severity.
+Type "sonacli issue list payments-api --severity HIGH | jq '.issues[] | {rule, file: .component, line, message}'"
+Sleep 300ms
+Enter
+Sleep 2200ms
+
+# Scene 5 — Paste a SonarQube web URL; sonacli extracts the issue key for you.
+Type@6ms 'sonacli issue show "http://127.0.0.1:9000/project/issues?id=payments-api&issues=$ISSUE_KEY" | jq "{rule, file: .component, line, impact: .impacts[0], message}"'
+Sleep 300ms
+Enter
+Sleep 2500ms
+
+Sleep 500ms
diff --git a/sonacli-logo.png b/sonacli-logo.png
new file mode 100644
index 0000000..053b7c3
Binary files /dev/null and b/sonacli-logo.png differ