Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 27 additions & 42 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ For detailed help on any command, use:
console.PrintBanner()
}
},
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}

Expand Down Expand Up @@ -79,7 +79,7 @@ Examples:
` + constants.CLIExtensionPrefix + ` new my-workflow.md # Create template file (alternative format)
` + constants.CLIExtensionPrefix + ` new issue-handler --force`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
forceFlag, _ := cmd.Flags().GetBool("force")
verbose, _ := cmd.Flags().GetBool("verbose")
interactiveFlag, _ := cmd.Flags().GetBool("interactive")
Expand All @@ -88,8 +88,7 @@ Examples:
if len(args) == 0 || interactiveFlag {
// Check if running in CI environment
if cli.IsRunningInCI() {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Interactive mode cannot be used in CI environments. Please provide a workflow name."))
os.Exit(1)
return fmt.Errorf("interactive mode cannot be used in CI environments. Please provide a workflow name")
}

// Use default workflow name for interactive mode
Expand All @@ -98,19 +97,12 @@ Examples:
workflowName = args[0]
}

if err := cli.CreateWorkflowInteractively(workflowName, verbose, forceFlag); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return
return cli.CreateWorkflowInteractively(workflowName, verbose, forceFlag)
}

// Template mode with workflow name
workflowName := args[0]
if err := cli.NewWorkflow(workflowName, verbose, forceFlag); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return cli.NewWorkflow(workflowName, verbose, forceFlag)
},
}

Expand All @@ -125,16 +117,13 @@ You can provide a workflow-id prefix to remove multiple workflows, or a specific
Examples:
` + constants.CLIExtensionPrefix + ` remove my-workflow # Remove specific workflow
` + constants.CLIExtensionPrefix + ` remove test- # Remove all workflows starting with 'test-'`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
var pattern string
if len(args) > 0 {
pattern = args[0]
}
keepOrphans, _ := cmd.Flags().GetBool("keep-orphans")
if err := cli.RemoveWorkflows(pattern, keepOrphans); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return cli.RemoveWorkflows(pattern, keepOrphans)
},
}

Expand All @@ -152,12 +141,9 @@ Examples:
` + constants.CLIExtensionPrefix + ` enable ci-doctor.md # Enable specific workflow (alternative format)
` + constants.CLIExtensionPrefix + ` enable ci-doctor daily # Enable multiple workflows
` + constants.CLIExtensionPrefix + ` enable ci-doctor --repo owner/repo # Enable workflow in specific repository`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
repoOverride, _ := cmd.Flags().GetString("repo")
if err := cli.EnableWorkflowsByNames(args, repoOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return cli.EnableWorkflowsByNames(args, repoOverride)
},
}

Expand All @@ -175,12 +161,9 @@ Examples:
` + constants.CLIExtensionPrefix + ` disable ci-doctor.md # Disable specific workflow (alternative format)
` + constants.CLIExtensionPrefix + ` disable ci-doctor daily # Disable multiple workflows
` + constants.CLIExtensionPrefix + ` disable ci-doctor --repo owner/repo # Disable workflow in specific repository`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
repoOverride, _ := cmd.Flags().GetString("repo")
if err := cli.DisableWorkflowsByNames(args, repoOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return cli.DisableWorkflowsByNames(args, repoOverride)
},
}

Expand Down Expand Up @@ -210,7 +193,7 @@ Examples:
` + constants.CLIExtensionPrefix + ` compile --trial --logical-repo owner/repo # Compile for trial mode
` + constants.CLIExtensionPrefix + ` compile --dependabot # Generate Dependabot manifests
` + constants.CLIExtensionPrefix + ` compile --dependabot --force # Force overwrite existing dependabot.yml`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
engineOverride, _ := cmd.Flags().GetString("engine")
validate, _ := cmd.Flags().GetBool("validate")
watch, _ := cmd.Flags().GetBool("watch")
Expand All @@ -230,8 +213,7 @@ Examples:
jsonOutput, _ := cmd.Flags().GetBool("json")
verbose, _ := cmd.Flags().GetBool("verbose")
if err := validateEngine(engineOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
return err
}

// Handle --workflows-dir deprecation (mutual exclusion is enforced by Cobra)
Expand Down Expand Up @@ -264,12 +246,11 @@ Examples:
errMsg := err.Error()
// Check if error is already formatted (contains suggestions or starts with ✗)
if strings.Contains(errMsg, "Suggestions:") || strings.HasPrefix(errMsg, "✗") {
fmt.Fprintln(os.Stderr, errMsg)
} else {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(errMsg))
return fmt.Errorf("%s", errMsg)
}
os.Exit(1)
return err
}
return nil
},
}

Expand Down Expand Up @@ -317,9 +298,10 @@ Examples:
var versionCmd = &cobra.Command{
Use: "version",
Short: "Show version information",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println(console.FormatInfoMessage(fmt.Sprintf("%s version %s", constants.CLIExtensionPrefix, version)))
fmt.Println(console.FormatInfoMessage("GitHub Agentic Workflows CLI from GitHub Next"))
return nil
},
}

Expand Down Expand Up @@ -360,6 +342,10 @@ func init() {
// network timeouts). Users can still run --help for usage information.
rootCmd.SilenceUsage = true

// Silence errors - since we're using RunE and returning errors, Cobra will
// print errors automatically. We handle error formatting ourselves in main().
rootCmd.SilenceErrors = true

// Set version template to match the version subcommand format
rootCmd.SetVersionTemplate(fmt.Sprintf("%s\n%s\n",
console.FormatInfoMessage(fmt.Sprintf("%s version {{.Version}}", constants.CLIExtensionPrefix)),
Expand All @@ -385,7 +371,7 @@ func init() {
Simply type ` + constants.CLIExtensionPrefix + ` help [path to command] for full details.

Use "` + constants.CLIExtensionPrefix + ` help all" to show help for all commands.`,
Run: func(c *cobra.Command, args []string) {
RunE: func(c *cobra.Command, args []string) error {
// Check if the argument is "all"
if len(args) == 1 && args[0] == "all" {
// Print header
Expand All @@ -412,17 +398,16 @@ Use "` + constants.CLIExtensionPrefix + ` help all" to show help for all command
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("═══════════════════════════════════════════════════════════════"))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("For more information, visit: https://githubnext.github.io/gh-aw/"))
return
return nil
}

// Otherwise, use the default help behavior
cmd, _, e := rootCmd.Find(args)
if cmd == nil || e != nil {
fmt.Fprintf(os.Stderr, "Unknown help topic [%#q]\n", args)
_ = rootCmd.Usage()
return fmt.Errorf("unknown help topic [%#q]", args)
} else {
cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown
_ = cmd.Help()
return cmd.Help()
}
},
}
Expand Down
20 changes: 3 additions & 17 deletions cmd/gh-aw/main_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,31 +508,17 @@ func TestMCPCommand(t *testing.T) {

func TestCommandErrorHandling(t *testing.T) {
t.Run("invalid command produces error", func(t *testing.T) {
// Capture stderr
oldStderr := os.Stderr
r, w, _ := os.Pipe()
os.Stderr = w

// Test invalid command
rootCmd.SetArgs([]string{"invalid-command"})
err := rootCmd.Execute()

// Restore stderr
w.Close()
os.Stderr = oldStderr

// Read captured output
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
output := buf.String()

if err == nil {
t.Error("invalid command should produce an error")
}

if output == "" {
t.Error("invalid command should produce error output")
}
// With RunE and SilenceErrors, errors are returned but not automatically printed
// The main() function is responsible for formatting and printing errors
// This test verifies that Execute() returns an error for invalid commands

// Reset args for other tests
rootCmd.SetArgs([]string{})
Expand Down
15 changes: 4 additions & 11 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The --force flag overwrites existing workflow files.

Note: To create a new workflow from scratch, use the 'new' command instead.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
workflows := args
numberFlag, _ := cmd.Flags().GetInt("number")
engineOverride, _ := cmd.Flags().GetString("engine")
Expand All @@ -66,21 +66,14 @@ Note: To create a new workflow from scratch, use the 'new' command instead.`,
stopAfter, _ := cmd.Flags().GetString("stop-after")

if err := validateEngine(engineOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
return err
}

// Handle normal mode
if prFlag {
if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, true, noGitattributes, workflowDir, noStopAfter, stopAfter); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, true, noGitattributes, workflowDir, noStopAfter, stopAfter)
} else {
if err := AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, false, noGitattributes, workflowDir, noStopAfter, stopAfter); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return AddWorkflows(workflows, numberFlag, verbose, engineOverride, nameFlag, forceFlag, appendText, false, noGitattributes, workflowDir, noStopAfter, stopAfter)
}
},
}
Expand Down
7 changes: 2 additions & 5 deletions pkg/cli/completions_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -280,10 +279,8 @@ func TestCompletionDirectiveNoFileComp(t *testing.T) {

outputStr := string(output)

// The last line should contain ":4" which is ShellCompDirectiveNoFileComp
lines := strings.Split(strings.TrimSpace(outputStr), "\n")
lastLine := lines[len(lines)-1]
assert.Contains(t, lastLine, ":4", "Expected ShellCompDirectiveNoFileComp (:4) in output")
// The output should contain ":4" which is ShellCompDirectiveNoFileComp
assert.Contains(t, outputStr, ":4", "Expected ShellCompDirectiveNoFileComp (:4) in output")
}

// setupIntegrationTestForCompletions is a local version of setupIntegrationTest for this file
Expand Down
10 changes: 3 additions & 7 deletions pkg/cli/init_command.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package cli

import (
"fmt"
"os"

"github.com/githubnext/gh-aw/pkg/console"
"github.com/githubnext/gh-aw/pkg/constants"
"github.com/githubnext/gh-aw/pkg/logger"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -42,16 +38,16 @@ Examples:
` + constants.CLIExtensionPrefix + ` init
` + constants.CLIExtensionPrefix + ` init -v
` + constants.CLIExtensionPrefix + ` init --mcp`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
verbose, _ := cmd.Flags().GetBool("verbose")
mcp, _ := cmd.Flags().GetBool("mcp")
initCommandLog.Printf("Executing init command: verbose=%v, mcp=%v", verbose, mcp)
if err := InitRepository(verbose, mcp); err != nil {
initCommandLog.Printf("Init command failed: %v", err)
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
return err
}
initCommandLog.Print("Init command completed successfully")
return nil
},
}

Expand Down
7 changes: 2 additions & 5 deletions pkg/cli/mcp_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,8 @@ Examples:
gh aw mcp-server # Run with stdio transport
gh aw mcp-server --port 8080 # Run HTTP server on port 8080
gh aw mcp-server --cmd ./gh-aw # Use custom gh-aw binary`,
Run: func(cmd *cobra.Command, args []string) {
if err := runMCPServer(port, cmdPath); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
RunE: func(cmd *cobra.Command, args []string) error {
return runMCPServer(port, cmdPath)
},
}

Expand Down
10 changes: 3 additions & 7 deletions pkg/cli/update_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Examples:
` + constants.CLIExtensionPrefix + ` update --pr # Create PR with changes
` + constants.CLIExtensionPrefix + ` update --force # Force update even if no changes
` + constants.CLIExtensionPrefix + ` update --dir custom/workflows # Update workflows in custom directory`,
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
majorFlag, _ := cmd.Flags().GetBool("major")
forceFlag, _ := cmd.Flags().GetBool("force")
engineOverride, _ := cmd.Flags().GetString("engine")
Expand All @@ -64,14 +64,10 @@ Examples:
mergeFlag, _ := cmd.Flags().GetBool("merge")

if err := validateEngine(engineOverride); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
return err
}

if err := UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag, workflowDir, noStopAfter, stopAfter, mergeFlag); err != nil {
fmt.Fprintln(os.Stderr, console.FormatErrorMessage(err.Error()))
os.Exit(1)
}
return UpdateWorkflowsWithExtensionCheck(args, majorFlag, forceFlag, verbose, engineOverride, prFlag, workflowDir, noStopAfter, stopAfter, mergeFlag)
},
}

Expand Down