Skip to content

Commit aaa567b

Browse files
shivasuryaclaude
andcommitted
feat: Add banner system and TTY detection for CLI output (PR-01)
Implements Phase 1 of CLI Output Enhancement with TTY-aware banner display. New files: - output/banner.go: ASCII art banner with version/license display - output/banner_test.go: 100% test coverage for banner functionality - output/tty.go: TTY detection using golang.org/x/term - output/tty_test.go: 100% test coverage for TTY detection Modified files: - output/logger.go: Add isTTY field and IsTTY()/GetWriter() methods - output/logger_test.go: Tests for new logger methods - cmd/root.go: Add --no-banner persistent flag - cmd/scan.go: Display banner on scan command startup - cmd/ci.go: Display banner on ci command startup - main_test.go: Update help output test with --no-banner flag Dependencies added: - github.com/common-nighthawk/go-figure: ASCII art generation - golang.org/x/term: TTY detection and terminal capabilities Features: - Smart banner display (full ASCII art in TTY, hidden in pipes/CI) - --no-banner flag to disable banner display - Automatic TTY detection for all logger instances - 97% test coverage for output package Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent e7ee09b commit aaa567b

File tree

13 files changed

+552
-2
lines changed

13 files changed

+552
-2
lines changed

sast-engine/cmd/ci.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ Examples:
5050
}
5151
logger := output.NewLogger(verbosity)
5252

53+
// Display banner if appropriate
54+
noBanner, _ := cmd.Flags().GetBool("no-banner")
55+
if output.ShouldShowBanner(logger.IsTTY(), noBanner) {
56+
output.PrintBanner(logger.GetWriter(), Version, output.DefaultBannerOptions())
57+
} else if logger.IsTTY() && !noBanner {
58+
fmt.Fprintln(logger.GetWriter(), output.GetCompactBanner(Version))
59+
}
60+
5361
// Parse and validate --fail-on severities
5462
failOn := output.ParseFailOn(failOnStr)
5563
if len(failOn) > 0 {

sast-engine/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ func Execute() error {
3030
func init() {
3131
rootCmd.PersistentFlags().Bool("disable-metrics", false, "Disable metrics collection")
3232
rootCmd.PersistentFlags().Bool("verbose", false, "Verbose output")
33+
rootCmd.PersistentFlags().Bool("no-banner", false, "Disable startup banner")
3334
}

sast-engine/cmd/scan.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ Examples:
8282
}
8383
logger := output.NewLogger(verbosity)
8484

85+
// Display banner if appropriate
86+
noBanner, _ := cmd.Flags().GetBool("no-banner")
87+
if output.ShouldShowBanner(logger.IsTTY(), noBanner) {
88+
output.PrintBanner(logger.GetWriter(), Version, output.DefaultBannerOptions())
89+
} else if logger.IsTTY() && !noBanner {
90+
fmt.Fprintln(logger.GetWriter(), output.GetCompactBanner(Version))
91+
}
92+
8593
// Parse and validate --fail-on severities
8694
failOn := output.ParseFailOn(failOnStr)
8795
if len(failOn) > 0 {

sast-engine/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ require (
1818
)
1919

2020
require (
21+
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
2122
github.com/davecgh/go-spew v1.1.1 // indirect
2223
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
2324
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2425
github.com/pmezard/go-difflib v1.0.0 // indirect
2526
github.com/spf13/pflag v1.0.10 // indirect
27+
golang.org/x/sys v0.40.0 // indirect
28+
golang.org/x/term v0.39.0 // indirect
2629
)

sast-engine/go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
2+
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
3+
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
24
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
35
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
46
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -45,6 +47,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
4547
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
4648
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
4749
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
50+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
51+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
52+
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
53+
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
4854
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
4955
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
5056
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

sast-engine/go.work.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
22
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
3+
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
34
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
5+
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
46
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
57
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
68
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
@@ -27,7 +29,9 @@ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2729
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
2830
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2931
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
32+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
3033
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
34+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
3135
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
3236
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
3337
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=

sast-engine/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestExecute(t *testing.T) {
2323
{
2424
name: "Successful execution",
2525
mockExecuteErr: nil,
26-
expectedOutput: "Code Pathfinder is designed for identifying vulnerabilities in source code.\n\nUsage:\n pathfinder [command]\n\nAvailable Commands:\n ci CI mode with SARIF, JSON, or CSV output for CI/CD integration\n completion Generate the autocompletion script for the specified shell\n diagnose Validate intra-procedural taint analysis against LLM ground truth\n help Help about any command\n resolution-report Generate a diagnostic report on call resolution statistics\n scan Scan code for security vulnerabilities using Python DSL rules\n serve Start MCP server for AI coding assistants\n version Print the version and commit information\n\nFlags:\n --disable-metrics Disable metrics collection\n -h, --help help for pathfinder\n --verbose Verbose output\n\nUse \"pathfinder [command] --help\" for more information about a command.\n",
26+
expectedOutput: "Code Pathfinder is designed for identifying vulnerabilities in source code.\n\nUsage:\n pathfinder [command]\n\nAvailable Commands:\n ci CI mode with SARIF, JSON, or CSV output for CI/CD integration\n completion Generate the autocompletion script for the specified shell\n diagnose Validate intra-procedural taint analysis against LLM ground truth\n help Help about any command\n resolution-report Generate a diagnostic report on call resolution statistics\n scan Scan code for security vulnerabilities using Python DSL rules\n serve Start MCP server for AI coding assistants\n version Print the version and commit information\n\nFlags:\n --disable-metrics Disable metrics collection\n -h, --help help for pathfinder\n --no-banner Disable startup banner\n --verbose Verbose output\n\nUse \"pathfinder [command] --help\" for more information about a command.\n",
2727
expectedExit: 0,
2828
},
2929
}

sast-engine/output/banner.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package output
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/common-nighthawk/go-figure"
8+
)
9+
10+
// BannerOptions configures the startup banner display.
11+
type BannerOptions struct {
12+
ShowBanner bool // Show ASCII art logo
13+
ShowVersion bool // Show version information
14+
ShowLicense bool // Show license information
15+
}
16+
17+
// DefaultBannerOptions returns default banner configuration.
18+
func DefaultBannerOptions() BannerOptions {
19+
return BannerOptions{
20+
ShowBanner: true,
21+
ShowVersion: true,
22+
ShowLicense: true,
23+
}
24+
}
25+
26+
// PrintBanner displays the pathfinder logo and information.
27+
func PrintBanner(w io.Writer, version string, opts BannerOptions) {
28+
if w == nil {
29+
return
30+
}
31+
32+
if !opts.ShowBanner {
33+
// Simple text-only banner
34+
if opts.ShowVersion {
35+
fmt.Fprintf(w, "Code Pathfinder v%s\n", version)
36+
}
37+
if opts.ShowLicense {
38+
fmt.Fprintf(w, "AGPL-3.0 License | https://codepathfinder.dev\n")
39+
}
40+
fmt.Fprintln(w)
41+
return
42+
}
43+
44+
// Generate ASCII art using go-figure
45+
asciiArt := GetASCIILogo()
46+
fmt.Fprintln(w, asciiArt)
47+
48+
// Version and license info
49+
if opts.ShowVersion {
50+
fmt.Fprintf(w, "Code Pathfinder v%s\n", version)
51+
}
52+
53+
if opts.ShowLicense {
54+
fmt.Fprintln(w, "AGPL-3.0 License | https://codepathfinder.dev")
55+
}
56+
57+
// Empty line separator
58+
fmt.Fprintln(w)
59+
}
60+
61+
// GetASCIILogo generates the ASCII art logo for "Pathfinder".
62+
func GetASCIILogo() string {
63+
// Use "standard" font for compact output
64+
fig := figure.NewFigure("Pathfinder", "standard", true)
65+
return fig.String()
66+
}
67+
68+
// GetCompactBanner returns a single-line banner for non-TTY output.
69+
func GetCompactBanner(version string) string {
70+
return fmt.Sprintf("Code Pathfinder v%s | AGPL-3.0 | https://codepathfinder.dev", version)
71+
}
72+
73+
// ShouldShowBanner determines if banner should be displayed.
74+
func ShouldShowBanner(isTTY bool, noBannerFlag bool) bool {
75+
// Never show if --no-banner is set
76+
if noBannerFlag {
77+
return false
78+
}
79+
// Show full banner only in TTY
80+
return isTTY
81+
}

0 commit comments

Comments
 (0)