From 223668b02a793b849248861b8b25a0110478db44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:08:28 +0000 Subject: [PATCH 1/2] Initial plan From be5a457d9f1da7982eb761217f328b5a7a676c28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:00:03 +0000 Subject: [PATCH 2/2] feat: migrate raw fmt.Fprintf/Fprintln(os.Stderr) to console.Format* helpers for UX consistency Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3ddc1a3c-31e4-48fe-a7ab-c4e985ec0c5d Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/cli/actionlint.go | 8 +- pkg/cli/add_interactive_engine.go | 2 +- pkg/cli/add_interactive_git.go | 12 +- pkg/cli/add_interactive_orchestrator.go | 16 +- pkg/cli/add_interactive_secrets.go | 3 +- pkg/cli/add_interactive_workflow.go | 32 ++-- pkg/cli/audit.go | 6 +- pkg/cli/audit_cross_run_render.go | 24 +-- pkg/cli/audit_report_render.go | 26 +-- pkg/cli/audit_report_render_findings.go | 56 +++--- pkg/cli/audit_report_render_firewall.go | 14 +- pkg/cli/audit_report_render_guard.go | 12 +- pkg/cli/audit_report_render_overview.go | 50 +++--- pkg/cli/audit_report_render_tools.go | 8 +- pkg/cli/compile_file_operations.go | 2 +- pkg/cli/compile_stats.go | 12 +- pkg/cli/copilot_agents.go | 4 +- pkg/cli/copilot_setup.go | 29 +-- pkg/cli/deps_report.go | 16 +- pkg/cli/deps_security.go | 4 +- pkg/cli/devcontainer.go | 5 +- pkg/cli/domains_command.go | 2 +- pkg/cli/enable.go | 24 +-- pkg/cli/engine_secrets.go | 26 +-- pkg/cli/fix_command.go | 14 +- pkg/cli/interactive.go | 2 +- pkg/cli/list_workflows_command.go | 8 +- pkg/cli/logs_github_rate_limit_usage.go | 3 +- pkg/cli/mcp_config_file.go | 5 +- pkg/cli/mcp_inspect_inspector.go | 14 +- pkg/cli/mcp_inspect_list.go | 4 +- pkg/cli/mcp_inspect_mcp.go | 70 ++++---- pkg/cli/mcp_list.go | 6 +- pkg/cli/mcp_list_tools.go | 4 +- pkg/cli/observability_insights.go | 7 +- pkg/cli/preconditions.go | 28 +-- pkg/cli/remove_command.go | 10 +- pkg/cli/run_interactive.go | 8 +- pkg/cli/run_push.go | 6 +- pkg/cli/secret_set_command.go | 2 +- pkg/cli/shell_completion.go | 12 +- pkg/cli/token_usage.go | 5 +- pkg/cli/trial_confirmation.go | 8 +- pkg/cli/update_actions.go | 2 +- pkg/cli/update_container_pins.go | 4 +- pkg/cli/update_display.go | 2 +- pkg/cli/update_extension_check.go | 4 +- pkg/cli/vscode_config.go | 3 +- pkg/workflow/action_sha_checker.go | 4 +- pkg/workflow/cache.go | 5 +- pkg/workflow/claude_logs.go | 9 +- pkg/workflow/compiler_orchestrator_engine.go | 4 +- pkg/workflow/mcp_renderer.go | 3 +- pkg/workflow/push_to_pull_request_branch.go | 3 +- pkg/workflow/side_repo_maintenance.go | 5 +- process_file.py | 177 +++++++++++++++++++ 56 files changed, 512 insertions(+), 322 deletions(-) create mode 100755 process_file.py diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 92f69cc584b..a518ca4a71e 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -94,9 +94,9 @@ func displayActionlintSummary() { // Create visual separator separator := strings.Repeat("━", 60) - fmt.Fprintf(os.Stderr, "\n%s\n", separator) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\n"+separator)) fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Actionlint Summary")) - fmt.Fprintf(os.Stderr, "%s\n\n", separator) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(separator+"\n")) // Show total workflows checked fmt.Fprintf(os.Stderr, "%s\n", @@ -119,7 +119,7 @@ func displayActionlintSummary() { if len(actionlintStats.ErrorsByKind) > 0 { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("Issues by type:")) for kind, count := range actionlintStats.ErrorsByKind { - fmt.Fprintf(os.Stderr, " • %s: %d\n", kind, count) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s: %d", kind, count))) } } } else if actionlintStats.IntegrationErrors > 0 { @@ -141,7 +141,7 @@ func displayActionlintSummary() { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatWarningMessage(msg)) } - fmt.Fprintf(os.Stderr, "\n%s\n", separator) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\n"+separator)) } // getActionlintVersion fetches and caches the actionlint version from Docker diff --git a/pkg/cli/add_interactive_engine.go b/pkg/cli/add_interactive_engine.go index 5d7c17bc351..9dae8543b5a 100644 --- a/pkg/cli/add_interactive_engine.go +++ b/pkg/cli/add_interactive_engine.go @@ -74,7 +74,7 @@ func (c *AddInteractiveConfig) selectAIEngineAndKey() error { // If engine is already overridden, skip selection if c.EngineOverride != "" { - fmt.Fprintf(os.Stderr, "Using coding agent: %s\n", c.EngineOverride) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Using coding agent: "+c.EngineOverride)) return c.configureEngineAPISecret(c.EngineOverride) } diff --git a/pkg/cli/add_interactive_git.go b/pkg/cli/add_interactive_git.go index 3ab419d1b81..b02f0967ec3 100644 --- a/pkg/cli/add_interactive_git.go +++ b/pkg/cli/add_interactive_git.go @@ -48,7 +48,7 @@ func (c *AddInteractiveConfig) createWorkflowPRAndConfigureSecret(ctx context.Co // Step 8b: Optionally merge the PR – loop until merged, confirmed-merged, or user exits if result.PRNumber == 0 { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Could not determine PR number")) - fmt.Fprintln(os.Stderr, "Please merge the PR manually from the GitHub web interface.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please merge the PR manually from the GitHub web interface.")) } else { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Pull request created: "+result.PRURL)) fmt.Fprintln(os.Stderr, "") @@ -177,9 +177,9 @@ func (c *AddInteractiveConfig) createWorkflowPRAndConfigureSecret(ctx context.Co if err := c.addRepositorySecret(secretName, secretValue); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Failed to add secret: %v", err))) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Please add the secret manually:") - fmt.Fprintln(os.Stderr, " 1. Go to your repository Settings → Secrets and variables → Actions") - fmt.Fprintf(os.Stderr, " 2. Click 'New repository secret' and add '%s'\n", secretName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please add the secret manually:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" 1. Go to your repository Settings → Secrets and variables → Actions")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" 2. Click 'New repository secret' and add '%s'", secretName))) return fmt.Errorf("failed to add secret: %w", err) } @@ -266,8 +266,8 @@ func (c *AddInteractiveConfig) checkCleanWorkingDirectory() error { if err := checkCleanWorkingDirectory(c.Verbose); err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Working directory is not clean.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "The add wizard creates a pull request which requires a clean working directory.") - fmt.Fprintln(os.Stderr, "Please commit or stash your changes first:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("The add wizard creates a pull request which requires a clean working directory.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please commit or stash your changes first:")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git stash # Temporarily stash changes")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" git add -A && git commit -m 'wip' # Commit changes")) diff --git a/pkg/cli/add_interactive_orchestrator.go b/pkg/cli/add_interactive_orchestrator.go index 48c22b49d61..d321e7a8b60 100644 --- a/pkg/cli/add_interactive_orchestrator.go +++ b/pkg/cli/add_interactive_orchestrator.go @@ -206,9 +206,9 @@ func (c *AddInteractiveConfig) determineFilesToAdd() (workflowFiles []string, in } fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "The following workflow files will be added:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("The following workflow files will be added:")) for _, f := range workflowFiles { - fmt.Fprintf(os.Stderr, " • .github/workflows/%s\n", f) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • .github/workflows/"+f)) } return workflowFiles, initFiles, nil @@ -238,7 +238,7 @@ func (c *AddInteractiveConfig) confirmChanges(workflowFiles, initFiles []string, } if !confirmed { - fmt.Fprintln(os.Stderr, "Operation cancelled.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Operation cancelled.")) return errors.New("user cancelled the operation") } @@ -248,23 +248,23 @@ func (c *AddInteractiveConfig) confirmChanges(workflowFiles, initFiles []string, // showFinalInstructions shows final instructions to the user func (c *AddInteractiveConfig) showFinalInstructions() { fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("🎉 Addition complete!")) - fmt.Fprintln(os.Stderr, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")) fmt.Fprintln(os.Stderr, "") // Show summary with workflow name(s) if c.resolvedWorkflows != nil && len(c.resolvedWorkflows.Workflows) > 0 { wf := c.resolvedWorkflows.Workflows[0] - fmt.Fprintf(os.Stderr, "The workflow '%s' has been added to the repository and will now run automatically.\n", wf.Spec.WorkflowName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("The workflow '%s' has been added to the repository and will now run automatically.", wf.Spec.WorkflowName))) c.showWorkflowDescriptions() } - fmt.Fprintln(os.Stderr, "Useful commands:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Useful commands:")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s status # Check workflow status", string(constants.CLIExtensionPrefix)))) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s run # Trigger a workflow", string(constants.CLIExtensionPrefix)))) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s logs # View workflow logs", string(constants.CLIExtensionPrefix)))) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Learn more at: https://github.github.com/gh-aw/") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Learn more at: https://github.github.com/gh-aw/")) fmt.Fprintln(os.Stderr, "") } diff --git a/pkg/cli/add_interactive_secrets.go b/pkg/cli/add_interactive_secrets.go index 39663846743..a581cc3410d 100644 --- a/pkg/cli/add_interactive_secrets.go +++ b/pkg/cli/add_interactive_secrets.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/sliceutil" "github.com/github/gh-aw/pkg/workflow" ) @@ -41,7 +42,7 @@ func (c *AddInteractiveConfig) checkExistingSecrets() error { } if c.Verbose && len(c.existingSecrets) > 0 { - fmt.Fprintf(os.Stderr, "Found %d existing secret(s) (repository + organization)\n", len(c.existingSecrets)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d existing secret(s) (repository + organization)", len(c.existingSecrets)))) } return nil diff --git a/pkg/cli/add_interactive_workflow.go b/pkg/cli/add_interactive_workflow.go index ab9b1c7e042..009123ebec1 100644 --- a/pkg/cli/add_interactive_workflow.go +++ b/pkg/cli/add_interactive_workflow.go @@ -47,22 +47,22 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error parsed, _ := parseWorkflowSpec(c.WorkflowSpecs[0]) if parsed != nil { if c.Verbose { - fmt.Fprintf(os.Stderr, "Checking workflow status (attempt %d/5) for: %s\n", i+1, parsed.WorkflowName) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Checking workflow status (attempt %d/5) for: %s", i+1, parsed.WorkflowName))) } // Check if workflow is in status statuses, err := findWorkflowsByFilenamePattern(parsed.WorkflowName, c.RepoOverride, c.Verbose) if err != nil { if c.Verbose { - fmt.Fprintf(os.Stderr, "Status check error: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Status check error: %v", err))) } } else if len(statuses) > 0 { if c.Verbose { - fmt.Fprintf(os.Stderr, "Found %d workflow(s) matching pattern\n", len(statuses)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d workflow(s) matching pattern", len(statuses)))) } workflowFound = true break } else if c.Verbose { - fmt.Fprintln(os.Stderr, "No workflows found matching pattern yet") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No workflows found matching pattern yet")) } } } @@ -74,7 +74,7 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error if !workflowFound { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Could not verify workflow status.")) - fmt.Fprintf(os.Stderr, "You can check status with: %s status\n", string(constants.CLIExtensionPrefix)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("You can check status with: %s status", string(constants.CLIExtensionPrefix)))) c.showFinalInstructions() return nil } @@ -93,7 +93,7 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error addInteractiveLog.Print("Running in Codespaces, skipping run offer and showing Actions link") fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Running in GitHub Codespaces - please trigger the workflow manually from the Actions page")) - fmt.Fprintf(os.Stderr, "🔗 https://github.com/%s/actions\n", c.RepoOverride) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("🔗 https://github.com/%s/actions", c.RepoOverride))) c.showFinalInstructions() return nil } @@ -132,15 +132,15 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error // after the PR merge—avoids a race where git fetch runs before GitHub's git // objects have been updated, which caused "workflow file not found" errors. if !c.Verbose { - fmt.Fprintln(os.Stderr, "Updating local branch (this may take a few seconds)...") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Updating local branch (this may take a few seconds)...")) } if err := c.updateLocalBranch(); err != nil { addInteractiveLog.Printf("Failed to update local branch: %v", err) fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Could not update local branch: %v", err))) - fmt.Fprintln(os.Stderr, "You may need to switch to your repository's default branch (for example 'main') and run 'git pull' manually before running the workflow.") + fmt.Fprintln(os.Stderr, console.FormatProgressMessage("You may need to switch to your repository's default branch (for example 'main') and run 'git pull' manually before running the workflow.")) } if !c.Verbose { - fmt.Fprintln(os.Stderr, "Finished updating local branch.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Finished updating local branch.")) } if err := RunSpecificWorkflowInteractively(ctx, parsed.WorkflowName, c.Verbose, c.EngineOverride, c.RepoOverride, "", false, false, false); err != nil { @@ -155,7 +155,7 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Workflow triggered successfully!")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintf(os.Stderr, "🔗 View workflow run: %s\n", runInfo.URL) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🔗 View workflow run: "+runInfo.URL)) } } } @@ -176,20 +176,20 @@ func findWorkflowsByFilenamePattern(pattern, repoOverride string, verbose bool) } if verbose { - fmt.Fprintf(os.Stderr, "Running: gh %s\n", strings.Join(args, " ")) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage("Running: gh "+strings.Join(args, " "))) } output, err := workflow.RunGH("Checking workflow status...", args...) if err != nil { if verbose { - fmt.Fprintf(os.Stderr, "gh workflow list failed: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage("gh workflow list failed: "+err.Error())) } return nil, err } if verbose { - fmt.Fprintf(os.Stderr, "gh workflow list output: %s\n", string(output)) - fmt.Fprintf(os.Stderr, "Looking for workflow with filename containing: %s\n", pattern) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("gh workflow list output: "+string(output))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Looking for workflow with filename containing: "+pattern)) } // Check if any workflow path contains the pattern @@ -198,13 +198,13 @@ func findWorkflowsByFilenamePattern(pattern, repoOverride string, verbose bool) // We check if the path contains the pattern if strings.Contains(string(output), pattern+".lock.yml") || strings.Contains(string(output), pattern+".md") { if verbose { - fmt.Fprintf(os.Stderr, "Workflow with filename '%s' found in workflow list\n", pattern) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow with filename '%s' found in workflow list", pattern))) } return []WorkflowStatus{{Workflow: pattern}}, nil } if verbose { - fmt.Fprintf(os.Stderr, "Workflow with filename '%s' NOT found in workflow list\n", pattern) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow with filename '%s' NOT found in workflow list", pattern))) } return nil, nil } diff --git a/pkg/cli/audit.go b/pkg/cli/audit.go index 662fe03b336..518d5449cf2 100644 --- a/pkg/cli/audit.go +++ b/pkg/cli/audit.go @@ -745,18 +745,18 @@ func auditJobRun(runID int64, jobID int64, stepNumber int, owner, repo, hostname // Display file locations fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nDownloaded files:")) - fmt.Fprintf(os.Stderr, " - %s (full job log)\n", jobLogPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s (full job log)", jobLogPath))) if stepNumber > 0 { stepLogPath := filepath.Join(outputDir, fmt.Sprintf("job-%d-step-%d.log", jobID, stepNumber)) if _, err := os.Stat(stepLogPath); err == nil { - fmt.Fprintf(os.Stderr, " - %s (step %d output)\n", stepLogPath, stepNumber) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s (step %d output)", stepLogPath, stepNumber))) } } else { failingStepPath := filepath.Join(outputDir, fmt.Sprintf("job-%d-step-*-failed.log", jobID)) matches, _ := filepath.Glob(failingStepPath) for _, match := range matches { - fmt.Fprintf(os.Stderr, " - %s (first failing step)\n", match) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s (first failing step)", match))) } } } diff --git a/pkg/cli/audit_cross_run_render.go b/pkg/cli/audit_cross_run_render.go index 8c6d7282df0..36a2257f960 100644 --- a/pkg/cli/audit_cross_run_render.go +++ b/pkg/cli/audit_cross_run_render.go @@ -213,13 +213,13 @@ func renderCrossRunReportPretty(report *CrossRunAuditReport) { // Executive summary fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Executive Summary")) - fmt.Fprintf(os.Stderr, " Runs analyzed: %d\n", report.RunsAnalyzed) - fmt.Fprintf(os.Stderr, " Runs with firewall data: %d\n", report.RunsWithData) - fmt.Fprintf(os.Stderr, " Runs without firewall data: %d\n", report.RunsWithoutData) - fmt.Fprintf(os.Stderr, " Total requests: %d\n", report.Summary.TotalRequests) - fmt.Fprintf(os.Stderr, " Allowed / Blocked: %d / %d\n", report.Summary.TotalAllowed, report.Summary.TotalBlocked) - fmt.Fprintf(os.Stderr, " Overall denial rate: %.1f%%\n", report.Summary.OverallDenyRate*100) - fmt.Fprintf(os.Stderr, " Unique domains: %d\n", report.Summary.UniqueDomains) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Runs analyzed: %d", report.RunsAnalyzed))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Runs with firewall data: %d", report.RunsWithData))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Runs without firewall data: %d", report.RunsWithoutData))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total requests: %d", report.Summary.TotalRequests))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Allowed / Blocked: %d / %d", report.Summary.TotalAllowed, report.Summary.TotalBlocked))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Overall denial rate: %.1f%%", report.Summary.OverallDenyRate*100))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Unique domains: %d", report.Summary.UniqueDomains))) fmt.Fprintln(os.Stderr) // Metrics trends @@ -278,9 +278,9 @@ func renderCrossRunReportPretty(report *CrossRunAuditReport) { fmt.Fprintf(os.Stderr, " Runs with errors: %d/%d (%.0f%%)\n", et.RunsWithErrors, report.RunsAnalyzed, safePercent(et.RunsWithErrors, report.RunsAnalyzed)) - fmt.Fprintf(os.Stderr, " Total errors: %d (avg=%.2f/run)\n", et.TotalErrors, et.AvgErrorsPerRun) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total errors: %d (avg=%.2f/run)", et.TotalErrors, et.AvgErrorsPerRun))) if et.TotalWarnings > 0 { - fmt.Fprintf(os.Stderr, " Total warnings: %d (%d runs)\n", et.TotalWarnings, et.RunsWithWarnings) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total warnings: %d (%d runs)", et.TotalWarnings, et.RunsWithWarnings))) } fmt.Fprintln(os.Stderr) } @@ -310,10 +310,10 @@ func renderCrossRunReportPretty(report *CrossRunAuditReport) { case "low": severityIcon = "🟡" } - fmt.Fprintf(os.Stderr, " %s [%s/%s] %s\n", severityIcon, insight.Category, insight.Severity, insight.Title) - fmt.Fprintf(os.Stderr, " %s\n", insight.Summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s [%s/%s] %s", severityIcon, insight.Category, insight.Severity, insight.Title))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+insight.Summary)) if insight.Evidence != "" { - fmt.Fprintf(os.Stderr, " evidence: %s\n", insight.Evidence) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" evidence: "+insight.Evidence)) } } fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/audit_report_render.go b/pkg/cli/audit_report_render.go index 610adeec85b..f946804cd4a 100644 --- a/pkg/cli/audit_report_render.go +++ b/pkg/cli/audit_report_render.go @@ -164,10 +164,10 @@ func renderConsole(data AuditData, logsPath string) { fmt.Fprintln(os.Stderr, console.FormatSectionHeader("Missing Tools")) fmt.Fprintln(os.Stderr) for _, tool := range data.MissingTools { - fmt.Fprintf(os.Stderr, " • %s\n", tool.Tool) - fmt.Fprintf(os.Stderr, " Reason: %s\n", tool.Reason) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • "+tool.Tool)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Reason: "+tool.Reason)) if tool.Alternatives != "" { - fmt.Fprintf(os.Stderr, " Alternatives: %s\n", tool.Alternatives) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Alternatives: "+tool.Alternatives)) } } fmt.Fprintln(os.Stderr) @@ -185,7 +185,7 @@ func renderConsole(data AuditData, logsPath string) { fmt.Fprintln(os.Stderr, console.FormatSectionHeader("MCP Server Failures")) fmt.Fprintln(os.Stderr) for _, failure := range data.MCPFailures { - fmt.Fprintf(os.Stderr, " • %s: %s\n", failure.ServerName, failure.Status) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s: %s", failure.ServerName, failure.Status))) } fmt.Fprintln(os.Stderr) } @@ -236,7 +236,7 @@ func renderConsole(data AuditData, logsPath string) { fmt.Fprintln(os.Stderr, console.FormatSectionHeader("Logs Location")) fmt.Fprintln(os.Stderr) absPath, _ := filepath.Abs(logsPath) - fmt.Fprintf(os.Stderr, " %s\n", absPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+absPath)) fmt.Fprintln(os.Stderr) } @@ -257,31 +257,31 @@ func renderAuditComparison(comparison *AuditComparisonData) { } fmt.Fprintln(os.Stderr) if comparison.Baseline.Selection != "" { - fmt.Fprintf(os.Stderr, " Selection: %s\n", strings.ReplaceAll(comparison.Baseline.Selection, "_", " ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Selection: "+strings.ReplaceAll(comparison.Baseline.Selection, "_", " "))) } if len(comparison.Baseline.MatchedOn) > 0 { - fmt.Fprintf(os.Stderr, " Matched on: %s\n", strings.Join(comparison.Baseline.MatchedOn, ", ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Matched on: "+strings.Join(comparison.Baseline.MatchedOn, ", "))) } - fmt.Fprintf(os.Stderr, " Classification: %s\n", comparison.Classification.Label) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Classification: "+comparison.Classification.Label)) fmt.Fprintln(os.Stderr, " Changes:") if comparison.Delta.Turns.Changed { - fmt.Fprintf(os.Stderr, " - Turns: %d -> %d\n", comparison.Delta.Turns.Before, comparison.Delta.Turns.After) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - Turns: %d -> %d", comparison.Delta.Turns.Before, comparison.Delta.Turns.After))) } if comparison.Delta.Posture.Changed { - fmt.Fprintf(os.Stderr, " - Posture: %s -> %s\n", comparison.Delta.Posture.Before, comparison.Delta.Posture.After) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - Posture: %s -> %s", comparison.Delta.Posture.Before, comparison.Delta.Posture.After))) } if comparison.Delta.BlockedRequests.Changed { - fmt.Fprintf(os.Stderr, " - Blocked requests: %d -> %d\n", comparison.Delta.BlockedRequests.Before, comparison.Delta.BlockedRequests.After) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - Blocked requests: %d -> %d", comparison.Delta.BlockedRequests.Before, comparison.Delta.BlockedRequests.After))) } if comparison.Delta.MCPFailure != nil && comparison.Delta.MCPFailure.NewlyPresent { - fmt.Fprintf(os.Stderr, " - New MCP failure: %s\n", strings.Join(comparison.Delta.MCPFailure.After, ", ")) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(" - New MCP failure: "+strings.Join(comparison.Delta.MCPFailure.After, ", "))) } if len(comparison.Classification.ReasonCodes) == 0 { fmt.Fprintln(os.Stderr, " - No meaningful behavior change from the selected successful baseline") } if comparison.Recommendation != nil && comparison.Recommendation.Action != "" { - fmt.Fprintf(os.Stderr, " Recommended action: %s\n", comparison.Recommendation.Action) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Recommended action: "+comparison.Recommendation.Action)) } fmt.Fprintln(os.Stderr) } diff --git a/pkg/cli/audit_report_render_findings.go b/pkg/cli/audit_report_render_findings.go index d4052fba7f1..2ffcbe9d4ba 100644 --- a/pkg/cli/audit_report_render_findings.go +++ b/pkg/cli/audit_report_render_findings.go @@ -55,9 +55,9 @@ func renderKeyFindings(findings []Finding) { // Render critical findings first for _, finding := range critical { fmt.Fprintf(os.Stderr, " 🔴 %s [%s]\n", console.FormatErrorMessage(finding.Title), finding.Category) - fmt.Fprintf(os.Stderr, " %s\n", finding.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+finding.Description)) if finding.Impact != "" { - fmt.Fprintf(os.Stderr, " Impact: %s\n", finding.Impact) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Impact: "+finding.Impact)) } fmt.Fprintln(os.Stderr) } @@ -65,29 +65,29 @@ func renderKeyFindings(findings []Finding) { // Then high severity for _, finding := range high { fmt.Fprintf(os.Stderr, " 🟠 %s [%s]\n", console.FormatWarningMessage(finding.Title), finding.Category) - fmt.Fprintf(os.Stderr, " %s\n", finding.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+finding.Description)) if finding.Impact != "" { - fmt.Fprintf(os.Stderr, " Impact: %s\n", finding.Impact) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Impact: "+finding.Impact)) } fmt.Fprintln(os.Stderr) } // Medium severity for _, finding := range medium { - fmt.Fprintf(os.Stderr, " 🟡 %s [%s]\n", finding.Title, finding.Category) - fmt.Fprintf(os.Stderr, " %s\n", finding.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" 🟡 %s [%s]", finding.Title, finding.Category))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+finding.Description)) if finding.Impact != "" { - fmt.Fprintf(os.Stderr, " Impact: %s\n", finding.Impact) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Impact: "+finding.Impact)) } fmt.Fprintln(os.Stderr) } // Low severity for _, finding := range low { - fmt.Fprintf(os.Stderr, " ℹ️ %s [%s]\n", finding.Title, finding.Category) - fmt.Fprintf(os.Stderr, " %s\n", finding.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ℹ️ %s [%s]", finding.Title, finding.Category))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+finding.Description)) if finding.Impact != "" { - fmt.Fprintf(os.Stderr, " Impact: %s\n", finding.Impact) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Impact: "+finding.Impact)) } fmt.Fprintln(os.Stderr) } @@ -95,9 +95,9 @@ func renderKeyFindings(findings []Finding) { // Info findings for _, finding := range info { fmt.Fprintf(os.Stderr, " ✅ %s [%s]\n", console.FormatSuccessMessage(finding.Title), finding.Category) - fmt.Fprintf(os.Stderr, " %s\n", finding.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+finding.Description)) if finding.Impact != "" { - fmt.Fprintf(os.Stderr, " Impact: %s\n", finding.Impact) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Impact: "+finding.Impact)) } fmt.Fprintln(os.Stderr) } @@ -114,9 +114,9 @@ func renderRecommendations(recommendations []Recommendation) { // Render high priority first for i, rec := range high { fmt.Fprintf(os.Stderr, " %d. [HIGH] %s\n", i+1, console.FormatWarningMessage(rec.Action)) - fmt.Fprintf(os.Stderr, " Reason: %s\n", rec.Reason) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Reason: "+rec.Reason)) if rec.Example != "" { - fmt.Fprintf(os.Stderr, " Example: %s\n", rec.Example) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Example: "+rec.Example)) } fmt.Fprintln(os.Stderr) } @@ -124,10 +124,10 @@ func renderRecommendations(recommendations []Recommendation) { // Medium priority startIdx := len(high) + 1 for i, rec := range medium { - fmt.Fprintf(os.Stderr, " %d. [MEDIUM] %s\n", startIdx+i, rec.Action) - fmt.Fprintf(os.Stderr, " Reason: %s\n", rec.Reason) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %d. [MEDIUM] %s", startIdx+i, rec.Action))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Reason: "+rec.Reason)) if rec.Example != "" { - fmt.Fprintf(os.Stderr, " Example: %s\n", rec.Example) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Example: "+rec.Example)) } fmt.Fprintln(os.Stderr) } @@ -135,10 +135,10 @@ func renderRecommendations(recommendations []Recommendation) { // Low priority startIdx += len(medium) for i, rec := range low { - fmt.Fprintf(os.Stderr, " %d. [LOW] %s\n", startIdx+i, rec.Action) - fmt.Fprintf(os.Stderr, " Reason: %s\n", rec.Reason) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %d. [LOW] %s", startIdx+i, rec.Action))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Reason: "+rec.Reason)) if rec.Example != "" { - fmt.Fprintf(os.Stderr, " Example: %s\n", rec.Example) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Example: "+rec.Example)) } fmt.Fprintln(os.Stderr) } @@ -149,8 +149,8 @@ func renderSafeOutputSummary(summary *SafeOutputSummary) { if summary == nil { return } - fmt.Fprintf(os.Stderr, " Total Items: %d\n", summary.TotalItems) - fmt.Fprintf(os.Stderr, " Summary: %s\n", summary.Summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total Items: %d", summary.TotalItems))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Summary: "+summary.Summary)) fmt.Fprintln(os.Stderr) // Type breakdown table @@ -181,7 +181,7 @@ func renderTokenUsage(summary *TokenUsageSummary) { fmt.Fprintf(os.Stderr, " Requests: %d (avg %s)\n", summary.TotalRequests, timeutil.FormatDurationMs(summary.AvgDurationMs())) if summary.CacheEfficiency > 0 { - fmt.Fprintf(os.Stderr, " Cache hit: %.1f%%\n", summary.CacheEfficiency*100) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Cache hit: %.1f%%", summary.CacheEfficiency*100))) } fmt.Fprintln(os.Stderr) @@ -223,7 +223,7 @@ func renderGitHubRateLimitUsage(usage *GitHubRateLimitUsage) { console.FormatNumber(usage.CoreRemaining), ) } - fmt.Fprintf(os.Stderr, " %s\n\n", summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s\n", summary))) // Per-resource breakdown table (only when there are multiple resources or non-core resources) rows := usage.ResourceRows() @@ -253,9 +253,9 @@ func renderErrorsAndWarnings(errors []ErrorInfo, warnings []ErrorInfo) { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Errors (%d):", len(errors)))) for _, err := range errors { if err.File != "" && err.Line > 0 { - fmt.Fprintf(os.Stderr, " %s:%d: %s\n", filepath.Base(err.File), err.Line, err.Message) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s:%d: %s", filepath.Base(err.File), err.Line, err.Message))) } else { - fmt.Fprintf(os.Stderr, " %s\n", err.Message) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+err.Message)) } } fmt.Fprintln(os.Stderr) @@ -265,9 +265,9 @@ func renderErrorsAndWarnings(errors []ErrorInfo, warnings []ErrorInfo) { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Warnings (%d):", len(warnings)))) for _, warn := range warnings { if warn.File != "" && warn.Line > 0 { - fmt.Fprintf(os.Stderr, " %s:%d: %s\n", filepath.Base(warn.File), warn.Line, warn.Message) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s:%d: %s", filepath.Base(warn.File), warn.Line, warn.Message))) } else { - fmt.Fprintf(os.Stderr, " %s\n", warn.Message) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" "+warn.Message)) } } fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/audit_report_render_firewall.go b/pkg/cli/audit_report_render_firewall.go index 7317987bb1f..a0f1408f6ce 100644 --- a/pkg/cli/audit_report_render_firewall.go +++ b/pkg/cli/audit_report_render_firewall.go @@ -16,9 +16,9 @@ func renderFirewallAnalysis(analysis *FirewallAnalysis) { auditReportLog.Printf("Rendering firewall analysis: total=%d, allowed=%d, blocked=%d, allowed_domains=%d, blocked_domains=%d", analysis.TotalRequests, analysis.AllowedRequests, analysis.BlockedRequests, len(analysis.AllowedDomains), len(analysis.BlockedDomains)) // Summary statistics - fmt.Fprintf(os.Stderr, " Total Requests : %d\n", analysis.TotalRequests) - fmt.Fprintf(os.Stderr, " Allowed : %d\n", analysis.AllowedRequests) - fmt.Fprintf(os.Stderr, " Blocked : %d\n", analysis.BlockedRequests) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total Requests : %d", analysis.TotalRequests))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Allowed : %d", analysis.AllowedRequests))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Blocked : %d", analysis.BlockedRequests))) fmt.Fprintln(os.Stderr) // Allowed domains @@ -26,7 +26,7 @@ func renderFirewallAnalysis(analysis *FirewallAnalysis) { fmt.Fprintln(os.Stderr, " Allowed Domains:") for _, domain := range analysis.AllowedDomains { if stats, ok := analysis.RequestsByDomain[domain]; ok { - fmt.Fprintf(os.Stderr, " ✓ %s (%d requests)\n", domain, stats.Allowed) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✓ %s (%d requests)", domain, stats.Allowed))) } } fmt.Fprintln(os.Stderr) @@ -37,7 +37,7 @@ func renderFirewallAnalysis(analysis *FirewallAnalysis) { fmt.Fprintln(os.Stderr, " Blocked Domains:") for _, domain := range analysis.BlockedDomains { if stats, ok := analysis.RequestsByDomain[domain]; ok { - fmt.Fprintf(os.Stderr, " ✗ %s (%d requests)\n", domain, stats.Blocked) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✗ %s (%d requests)", domain, stats.Blocked))) } } fmt.Fprintln(os.Stderr) @@ -48,14 +48,14 @@ func renderFirewallAnalysis(analysis *FirewallAnalysis) { func renderRedactedDomainsAnalysis(analysis *RedactedDomainsAnalysis) { auditReportLog.Printf("Rendering redacted domains analysis: total_domains=%d", analysis.TotalDomains) // Summary statistics - fmt.Fprintf(os.Stderr, " Total Domains Redacted: %d\n", analysis.TotalDomains) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total Domains Redacted: %d", analysis.TotalDomains))) fmt.Fprintln(os.Stderr) // List domains if len(analysis.Domains) > 0 { fmt.Fprintln(os.Stderr, " Redacted Domains:") for _, domain := range analysis.Domains { - fmt.Fprintf(os.Stderr, " 🔒 %s\n", domain) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" 🔒 %s", domain))) } fmt.Fprintln(os.Stderr) } diff --git a/pkg/cli/audit_report_render_guard.go b/pkg/cli/audit_report_render_guard.go index ad154776fda..20f573e8df4 100644 --- a/pkg/cli/audit_report_render_guard.go +++ b/pkg/cli/audit_report_render_guard.go @@ -23,22 +23,22 @@ func renderGuardPolicySummary(summary *GuardPolicySummary) { // Breakdown by reason fmt.Fprintln(os.Stderr, " Block Reasons:") if summary.IntegrityBlocked > 0 { - fmt.Fprintf(os.Stderr, " Integrity below minimum : %d\n", summary.IntegrityBlocked) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Integrity below minimum : %d", summary.IntegrityBlocked))) } if summary.RepoScopeBlocked > 0 { - fmt.Fprintf(os.Stderr, " Repository not allowed : %d\n", summary.RepoScopeBlocked) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Repository not allowed : %d", summary.RepoScopeBlocked))) } if summary.AccessDenied > 0 { - fmt.Fprintf(os.Stderr, " Access denied : %d\n", summary.AccessDenied) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Access denied : %d", summary.AccessDenied))) } if summary.BlockedUserDenied > 0 { - fmt.Fprintf(os.Stderr, " Blocked user : %d\n", summary.BlockedUserDenied) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Blocked user : %d", summary.BlockedUserDenied))) } if summary.PermissionDenied > 0 { - fmt.Fprintf(os.Stderr, " Insufficient permissions: %d\n", summary.PermissionDenied) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Insufficient permissions: %d", summary.PermissionDenied))) } if summary.PrivateRepoDenied > 0 { - fmt.Fprintf(os.Stderr, " Private repo denied : %d\n", summary.PrivateRepoDenied) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Private repo denied : %d", summary.PrivateRepoDenied))) } fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/audit_report_render_overview.go b/pkg/cli/audit_report_render_overview.go index 07254aedb76..a9454a141ee 100644 --- a/pkg/cli/audit_report_render_overview.go +++ b/pkg/cli/audit_report_render_overview.go @@ -74,12 +74,12 @@ func renderBehaviorFingerprint(fingerprint *BehaviorFingerprint) { func renderAgenticAssessments(assessments []AgenticAssessment) { for _, assessment := range assessments { severity := strings.ToUpper(assessment.Severity) - fmt.Fprintf(os.Stderr, " [%s] %s\n", severity, assessment.Summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" [%s] %s", severity, assessment.Summary))) if assessment.Evidence != "" { - fmt.Fprintf(os.Stderr, " Evidence: %s\n", assessment.Evidence) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Evidence: %s", assessment.Evidence))) } if assessment.Recommendation != "" { - fmt.Fprintf(os.Stderr, " Recommendation: %s\n", assessment.Recommendation) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Recommendation: %s", assessment.Recommendation))) } fmt.Fprintln(os.Stderr) } @@ -90,7 +90,7 @@ func renderPerformanceMetrics(metrics *PerformanceMetrics) { auditReportLog.Printf("Rendering performance metrics: tokens_per_min=%.1f, cost_efficiency=%s, most_used_tool=%s", metrics.TokensPerMinute, metrics.CostEfficiency, metrics.MostUsedTool) if metrics.TokensPerMinute > 0 { - fmt.Fprintf(os.Stderr, " Tokens per Minute: %.1f\n", metrics.TokensPerMinute) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Tokens per Minute: %.1f", metrics.TokensPerMinute))) } if metrics.CostEfficiency != "" { @@ -103,19 +103,19 @@ func renderPerformanceMetrics(metrics *PerformanceMetrics) { case "poor": efficiencyDisplay = console.FormatErrorMessage(metrics.CostEfficiency) } - fmt.Fprintf(os.Stderr, " Cost Efficiency: %s\n", efficiencyDisplay) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Cost Efficiency: %s", efficiencyDisplay))) } if metrics.AvgToolDuration != "" { - fmt.Fprintf(os.Stderr, " Average Tool Duration: %s\n", metrics.AvgToolDuration) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Average Tool Duration: %s", metrics.AvgToolDuration))) } if metrics.MostUsedTool != "" { - fmt.Fprintf(os.Stderr, " Most Used Tool: %s\n", metrics.MostUsedTool) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Most Used Tool: %s", metrics.MostUsedTool))) } if metrics.NetworkRequests > 0 { - fmt.Fprintf(os.Stderr, " Network Requests: %d\n", metrics.NetworkRequests) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Network Requests: %d", metrics.NetworkRequests))) } fmt.Fprintln(os.Stderr) @@ -126,30 +126,30 @@ func renderEngineConfig(config *AuditEngineConfig) { if config == nil { return } - fmt.Fprintf(os.Stderr, " Engine ID: %s\n", config.EngineID) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Engine ID: %s", config.EngineID))) if config.EngineName != "" { - fmt.Fprintf(os.Stderr, " Engine Name: %s\n", config.EngineName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Engine Name: %s", config.EngineName))) } if config.Model != "" { - fmt.Fprintf(os.Stderr, " Model: %s\n", config.Model) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Model: %s", config.Model))) } if config.Version != "" { - fmt.Fprintf(os.Stderr, " Version: %s\n", config.Version) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Version: %s", config.Version))) } if config.CLIVersion != "" { - fmt.Fprintf(os.Stderr, " CLI Version: %s\n", config.CLIVersion) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" CLI Version: %s", config.CLIVersion))) } if config.FirewallVersion != "" { - fmt.Fprintf(os.Stderr, " Firewall Version: %s\n", config.FirewallVersion) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Firewall Version: %s", config.FirewallVersion))) } if config.TriggerEvent != "" { - fmt.Fprintf(os.Stderr, " Trigger Event: %s\n", config.TriggerEvent) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Trigger Event: %s", config.TriggerEvent))) } if config.Repository != "" { - fmt.Fprintf(os.Stderr, " Repository: %s\n", config.Repository) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Repository: %s", config.Repository))) } if len(config.MCPServers) > 0 { - fmt.Fprintf(os.Stderr, " MCP Servers: %s\n", strings.Join(config.MCPServers, ", ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" MCP Servers: %s", strings.Join(config.MCPServers, ", ")))) } fmt.Fprintln(os.Stderr) } @@ -161,7 +161,7 @@ func renderPromptAnalysis(analysis *PromptAnalysis) { } fmt.Fprintf(os.Stderr, " Prompt Size: %s chars\n", console.FormatNumber(analysis.PromptSize)) if analysis.PromptFile != "" { - fmt.Fprintf(os.Stderr, " Prompt File: %s\n", analysis.PromptFile) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Prompt File: %s", analysis.PromptFile))) } fmt.Fprintln(os.Stderr) } @@ -172,28 +172,28 @@ func renderSessionAnalysis(session *SessionAnalysis) { return } if session.WallTime != "" { - fmt.Fprintf(os.Stderr, " Wall Time: %s\n", session.WallTime) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Wall Time: %s", session.WallTime))) } if session.TurnCount > 0 { - fmt.Fprintf(os.Stderr, " Turn Count: %d\n", session.TurnCount) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Turn Count: %d", session.TurnCount))) } if session.AvgTurnDuration != "" { - fmt.Fprintf(os.Stderr, " Avg Turn Duration: %s\n", session.AvgTurnDuration) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Avg Turn Duration: %s", session.AvgTurnDuration))) } if session.AvgTimeBetweenTurns != "" { - fmt.Fprintf(os.Stderr, " Avg Time Between Turns: %s\n", session.AvgTimeBetweenTurns) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Avg Time Between Turns: %s", session.AvgTimeBetweenTurns))) } if session.MaxTimeBetweenTurns != "" { - fmt.Fprintf(os.Stderr, " Max Time Between Turns: %s\n", session.MaxTimeBetweenTurns) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Max Time Between Turns: %s", session.MaxTimeBetweenTurns))) } if session.CacheWarning != "" { fmt.Fprintf(os.Stderr, " Cache Warning: %s\n", console.FormatWarningMessage(session.CacheWarning)) } if session.TokensPerMinute > 0 { - fmt.Fprintf(os.Stderr, " Tokens/Minute: %.1f\n", session.TokensPerMinute) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Tokens/Minute: %.1f", session.TokensPerMinute))) } if session.NoopCount > 0 { - fmt.Fprintf(os.Stderr, " Noop Count: %d\n", session.NoopCount) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Noop Count: %d", session.NoopCount))) } if session.TimeoutDetected { fmt.Fprintf(os.Stderr, " Timeout Detected: %s\n", console.FormatWarningMessage("Yes")) diff --git a/pkg/cli/audit_report_render_tools.go b/pkg/cli/audit_report_render_tools.go index 8ea851bdfcd..cc1f681e552 100644 --- a/pkg/cli/audit_report_render_tools.go +++ b/pkg/cli/audit_report_render_tools.go @@ -128,11 +128,11 @@ func renderMCPServerHealth(health *MCPServerHealth) { if health == nil { return } - fmt.Fprintf(os.Stderr, " %s\n", health.Summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", health.Summary))) if health.TotalRequests > 0 { - fmt.Fprintf(os.Stderr, " Total Requests: %d\n", health.TotalRequests) - fmt.Fprintf(os.Stderr, " Total Errors: %d\n", health.TotalErrors) - fmt.Fprintf(os.Stderr, " Error Rate: %.1f%%\n", health.ErrorRate) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total Requests: %d", health.TotalRequests))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total Errors: %d", health.TotalErrors))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Error Rate: %.1f%%", health.ErrorRate))) } fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/compile_file_operations.go b/pkg/cli/compile_file_operations.go index b068a1cd627..73273016d8f 100644 --- a/pkg/cli/compile_file_operations.go +++ b/pkg/cli/compile_file_operations.go @@ -210,7 +210,7 @@ func compileModifiedFilesWithDependencies(compiler *workflow.Compiler, depGraph } } - fmt.Fprintln(os.Stderr, "Watching for file changes") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Watching for file changes")) if verbose { fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Recompiling %d workflow(s) affected by %d change(s)...", len(workflowsToCompile), len(files)))) } diff --git a/pkg/cli/compile_stats.go b/pkg/cli/compile_stats.go index c5e0c48de55..332536d0858 100644 --- a/pkg/cli/compile_stats.go +++ b/pkg/cli/compile_stats.go @@ -139,7 +139,7 @@ func printCompilationSummary(stats *CompilationStats) { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Failed workflows:")) for _, failure := range stats.FailureDetails { - fmt.Fprintf(os.Stderr, " ✗ %s\n", filepath.Base(failure.Path)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✗ %s", filepath.Base(failure.Path)))) } fmt.Fprintln(os.Stderr) @@ -154,7 +154,7 @@ func printCompilationSummary(stats *CompilationStats) { fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr, console.FormatErrorMessage("Failed workflows:")) for _, workflow := range stats.FailedWorkflows { - fmt.Fprintf(os.Stderr, " ✗ %s\n", workflow) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ✗ %s", workflow))) } fmt.Fprintln(os.Stderr) } @@ -264,11 +264,11 @@ func displayStatsTable(statsList []*WorkflowStats) { // Print summary fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Summary:")) if len(statsList) > maxDisplay { - fmt.Fprintf(os.Stderr, " Showing top %d of %d workflows (sorted by size)\n", maxDisplay, len(statsList)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Showing top %d of %d workflows (sorted by size)", maxDisplay, len(statsList)))) } - fmt.Fprintf(os.Stderr, " Total workflows: %d\n", len(statsList)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total workflows: %d", len(statsList)))) fmt.Fprintf(os.Stderr, " Total size: %s\n", console.FormatFileSize(totalSize)) - fmt.Fprintf(os.Stderr, " Total jobs: %d\n", totalJobs) - fmt.Fprintf(os.Stderr, " Total steps: %d\n", totalSteps) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total jobs: %d", totalJobs))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Total steps: %d", totalSteps))) fmt.Fprintf(os.Stderr, " Total scripts: %d (%s)\n", totalScripts, console.FormatFileSize(int64(totalScriptSize))) } diff --git a/pkg/cli/copilot_agents.go b/pkg/cli/copilot_agents.go index 5a6c278c7ce..af3c56a7520 100644 --- a/pkg/cli/copilot_agents.go +++ b/pkg/cli/copilot_agents.go @@ -117,7 +117,7 @@ func deleteSetupAgenticWorkflowsAgent(verbose bool) error { return fmt.Errorf("failed to remove setup-agentic-workflows agent: %w", err) } if verbose { - fmt.Fprintf(os.Stderr, "Removed setup-agentic-workflows agent: %s\n", agentPath) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed setup-agentic-workflows agent: %s", agentPath))) } } @@ -215,7 +215,7 @@ func deleteOldAgentFiles(verbose bool) error { return fmt.Errorf("failed to remove old %s file %s: %w", subdir, file, err) } if verbose { - fmt.Fprintf(os.Stderr, "Removed old %s file: %s\n", subdir, path) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Removed old %s file: %s", subdir, path))) } } } diff --git a/pkg/cli/copilot_setup.go b/pkg/cli/copilot_setup.go index c7071672cad..df464127f43 100644 --- a/pkg/cli/copilot_setup.go +++ b/pkg/cli/copilot_setup.go @@ -8,6 +8,7 @@ import ( "regexp" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/workflow" ) @@ -219,7 +220,7 @@ func ensureCopilotSetupStepsWithUpgrade(verbose bool, actionMode workflow.Action if !upgraded { copilotSetupLog.Print("No version upgrade needed") if verbose { - fmt.Fprintf(os.Stderr, "No version upgrade needed for %s\n", setupStepsPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("No version upgrade needed for %s", setupStepsPath))) } return nil } @@ -230,7 +231,7 @@ func ensureCopilotSetupStepsWithUpgrade(verbose bool, actionMode workflow.Action copilotSetupLog.Printf("Upgraded version in file: %s", setupStepsPath) if verbose { - fmt.Fprintf(os.Stderr, "Updated %s with new version %s\n", setupStepsPath, version) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Updated %s with new version %s", setupStepsPath, version))) } return nil } @@ -239,7 +240,7 @@ func ensureCopilotSetupStepsWithUpgrade(verbose bool, actionMode workflow.Action if hasLegacyInstall || hasActionInstall { copilotSetupLog.Print("Extension install step already exists, file is up to date") if verbose { - fmt.Fprintf(os.Stderr, "Skipping %s (already has gh-aw extension install step)\n", setupStepsPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Skipping %s (already has gh-aw extension install step)", setupStepsPath))) } return nil } @@ -266,8 +267,8 @@ func renderCopilotSetupUpdateInstructions(filePath string, actionMode workflow.A "ℹ", "Existing file detected: "+filePath) fmt.Fprintln(os.Stderr) - fmt.Fprintln(os.Stderr, "To enable GitHub Copilot Agent integration, please add the following steps") - fmt.Fprintln(os.Stderr, "to the 'copilot-setup-steps' job in your .github/workflows/copilot-setup-steps.yml file:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable GitHub Copilot Agent integration, please add the following steps")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("to the 'copilot-setup-steps' job in your .github/workflows/copilot-setup-steps.yml file:")) fmt.Fprintln(os.Stderr) // Determine the action reference @@ -278,16 +279,16 @@ func renderCopilotSetupUpdateInstructions(filePath string, actionMode workflow.A if actionMode.IsAction() { actionRepo = "github/gh-aw-actions/setup-cli" } - fmt.Fprintln(os.Stderr, " - name: Checkout repository") - fmt.Fprintln(os.Stderr, " uses: actions/checkout@v6") - fmt.Fprintf(os.Stderr, " - name: Install gh-aw extension\n") - fmt.Fprintf(os.Stderr, " uses: %s%s\n", actionRepo, actionRef) - fmt.Fprintln(os.Stderr, " with:") - fmt.Fprintf(os.Stderr, " version: %s\n", version) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" - name: Checkout repository")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" uses: actions/checkout@v6")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" - name: Install gh-aw extension")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" uses: %s%s", actionRepo, actionRef))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" with:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" version: %s", version))) } else { - fmt.Fprintln(os.Stderr, " - name: Install gh-aw extension") - fmt.Fprintln(os.Stderr, " run: |") - fmt.Fprintln(os.Stderr, " curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" - name: Install gh-aw extension")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" run: |")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash")) } fmt.Fprintln(os.Stderr) } diff --git a/pkg/cli/deps_report.go b/pkg/cli/deps_report.go index f3cacbe46a8..42e5f9a71c9 100644 --- a/pkg/cli/deps_report.go +++ b/pkg/cli/deps_report.go @@ -109,14 +109,14 @@ func DisplayDependencyReport(report *DependencyReport) { // Summary section fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Summary")) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("-------")) - fmt.Fprintf(os.Stderr, "Total dependencies: %d (%d direct, %d indirect)\n", report.TotalDeps, report.DirectDeps, report.IndirectDeps) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Total dependencies: %d (%d direct, %d indirect)", report.TotalDeps, report.DirectDeps, report.IndirectDeps))) outdatedPercentage := 0.0 if report.DirectDeps > 0 { outdatedPercentage = float64(len(report.Outdated)) / float64(report.DirectDeps) * 100 } - fmt.Fprintf(os.Stderr, "Outdated: %d (%.0f%%)\n", len(report.Outdated), outdatedPercentage) - fmt.Fprintf(os.Stderr, "Security advisories: %d\n", len(report.Advisories)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Outdated: %d (%.0f%%)", len(report.Outdated), outdatedPercentage))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Security advisories: %d", len(report.Advisories)))) v0Percentage := 0.0 if report.TotalDeps > 0 { @@ -160,13 +160,13 @@ func DisplayDependencyReport(report *DependencyReport) { if report.TotalDeps > 0 { v1Percentage = float64(report.V1PlusCount) / float64(report.TotalDeps) * 100 } - fmt.Fprintf(os.Stderr, "v1.x (stable): %d (%.0f%%)\n", report.V1PlusCount, v1Percentage) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("v1.x (stable): %d (%.0f%%)", report.V1PlusCount, v1Percentage))) v2Percentage := 0.0 if report.TotalDeps > 0 { v2Percentage = float64(report.V2PlusCount) / float64(report.TotalDeps) * 100 } - fmt.Fprintf(os.Stderr, "v2+ (mature): %d (%.0f%%)\n", report.V2PlusCount, v2Percentage) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("v2+ (mature): %d (%.0f%%)", report.V2PlusCount, v2Percentage))) fmt.Fprintln(os.Stderr, "") // Recommendations section @@ -175,15 +175,15 @@ func DisplayDependencyReport(report *DependencyReport) { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("---------------")) if len(report.Advisories) > 0 { - fmt.Fprintf(os.Stderr, "🔴 CRITICAL: Address %d security %s immediately\n", len(report.Advisories), pluralize("advisory", len(report.Advisories))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("🔴 CRITICAL: Address %d security %s immediately", len(report.Advisories), pluralize("advisory", len(report.Advisories))))) } if len(report.Outdated) > 0 { - fmt.Fprintf(os.Stderr, "📦 Update %d outdated %s\n", len(report.Outdated), pluralize("dependency", len(report.Outdated))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("📦 Update %d outdated %s", len(report.Outdated), pluralize("dependency", len(report.Outdated))))) } if v0Percentage > 30 { - fmt.Fprintf(os.Stderr, "⚠️ Reduce v0.x exposure from %.0f%% to <30%%\n", v0Percentage) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("⚠️ Reduce v0.x exposure from %.0f%% to <30%%", v0Percentage))) } fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/deps_security.go b/pkg/cli/deps_security.go index 71304dd2c17..a1996038d1c 100644 --- a/pkg/cli/deps_security.go +++ b/pkg/cli/deps_security.go @@ -121,10 +121,10 @@ func DisplaySecurityAdvisories(advisories []SecurityAdvisory) { fmt.Fprintln(os.Stderr, "") if len(adv.PatchedVers) > 0 { - fmt.Fprintf(os.Stderr, " Fixed in: %s\n", strings.Join(adv.PatchedVers, ", ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Fixed in: %s", strings.Join(adv.PatchedVers, ", ")))) } - fmt.Fprintf(os.Stderr, " %s\n", adv.URL) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", adv.URL))) fmt.Fprintln(os.Stderr, "") } } diff --git a/pkg/cli/devcontainer.go b/pkg/cli/devcontainer.go index bf2eedfb972..92aa2cac853 100644 --- a/pkg/cli/devcontainer.go +++ b/pkg/cli/devcontainer.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/gitutil" "github.com/github/gh-aw/pkg/logger" ) @@ -162,7 +163,7 @@ func ensureDevcontainerConfig(verbose bool, additionalRepos []string) error { } if verbose { - fmt.Fprintf(os.Stderr, "Updated existing devcontainer at %s\n", devcontainerPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Updated existing devcontainer at %s", devcontainerPath))) } } else { // Create new configuration @@ -189,7 +190,7 @@ func ensureDevcontainerConfig(verbose bool, additionalRepos []string) error { } if verbose { - fmt.Fprintf(os.Stderr, "Created new devcontainer at %s\n", devcontainerPath) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created new devcontainer at %s", devcontainerPath))) } } diff --git a/pkg/cli/domains_command.go b/pkg/cli/domains_command.go index 993fc845303..9af6997eccc 100644 --- a/pkg/cli/domains_command.go +++ b/pkg/cli/domains_command.go @@ -191,7 +191,7 @@ func RunWorkflowDomains(workflowArg string, jsonOutput bool) error { } fmt.Fprint(os.Stderr, console.RenderTable(tableConfig)) - fmt.Fprintf(os.Stderr, "\n%d allowed, %d blocked\n", len(allowedDomains), len(blockedDomains)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\n%d allowed, %d blocked", len(allowedDomains), len(blockedDomains)))) return nil } diff --git a/pkg/cli/enable.go b/pkg/cli/enable.go index aed89e2ef57..63718fae708 100644 --- a/pkg/cli/enable.go +++ b/pkg/cli/enable.go @@ -43,7 +43,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st // If no specific workflow names provided, enable/disable all workflows if len(workflowNames) == 0 { enableLog.Print("No specific workflows provided, processing all workflows") - fmt.Fprintf(os.Stderr, "No specific workflows provided. %sing all workflows...\n", strings.ToUpper(action[:1])+action[1:]) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("%sing all workflows...", strings.ToUpper(action[:1])+action[1:]))) // Get all workflow names and process them mdFiles, err := getMarkdownWorkflowFiles("") if err != nil { @@ -133,12 +133,12 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st if exists { if enable && githubWorkflow.State == "active" { // Already enabled - fmt.Fprintf(os.Stderr, "Workflow %s is already enabled\n", name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow %s is already enabled", name))) continue } if !enable && githubWorkflow.State == "disabled_manually" { // Already disabled - fmt.Fprintf(os.Stderr, "Workflow %s is already disabled\n", name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow %s is already disabled", name))) continue } } @@ -189,15 +189,15 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st // If no targets after filtering, everything was already in the desired state if len(targets) == 0 { enableLog.Printf("No workflows need to be %sd - all already in desired state", action) - fmt.Fprintf(os.Stderr, "All specified workflows are already %sd\n", action) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("All specified workflows are already %sd", action))) return nil } enableLog.Printf("Proceeding to %s %d workflows", action, len(targets)) // Show what will be changed - fmt.Fprintf(os.Stderr, "The following workflows will be %sd:\n", action) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("The following workflows will be %sd:", action))) for _, t := range targets { - fmt.Fprintf(os.Stderr, " %s (current state: %s)\n", t.Name, t.CurrentState) + fmt.Fprintln(os.Stderr, console.FormatListItem(fmt.Sprintf("%s (current state: %s)", t.Name, t.CurrentState))) } // Perform the action @@ -257,7 +257,7 @@ func toggleWorkflowsByNames(workflowNames []string, enable bool, repoOverride st } failures = append(failures, t.Name) } else { - fmt.Fprintf(os.Stderr, "%sd workflow: %s\n", strings.ToUpper(action[:1])+action[1:], t.Name) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%sd workflow: %s", strings.ToUpper(action[:1])+action[1:], t.Name))) } } @@ -318,7 +318,7 @@ func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbos // Skip if it's in the keep-enabled set if keepEnabled[base] { if verbose { - fmt.Fprintf(os.Stderr, "Keeping enabled: %s\n", base) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Keeping enabled: %s", base))) } continue } @@ -327,7 +327,7 @@ func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbos nameWithoutExt := strings.TrimSuffix(base, filepath.Ext(base)) if keepEnabled[nameWithoutExt] { if verbose { - fmt.Fprintf(os.Stderr, "Keeping enabled: %s\n", base) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Keeping enabled: %s", base))) } continue } @@ -343,9 +343,9 @@ func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbos } // Show what will be disabled - fmt.Fprintf(os.Stderr, "Disabling %d workflow(s) in cloned repository:\n", len(workflowsToDisable)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Disabling %d workflow(s) in cloned repository:", len(workflowsToDisable)))) for _, wf := range workflowsToDisable { - fmt.Fprintf(os.Stderr, " %s\n", wf) + fmt.Fprintln(os.Stderr, console.FormatListItem(wf)) } // Disable each workflow @@ -364,7 +364,7 @@ func DisableAllWorkflowsExcept(repoSlug string, exceptWorkflows []string, verbos failures = append(failures, wf) } else { if verbose { - fmt.Fprintf(os.Stderr, "Disabled workflow: %s\n", wf) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Disabled workflow: %s", wf))) } } } diff --git a/pkg/cli/engine_secrets.go b/pkg/cli/engine_secrets.go index a447a557f88..716b159742e 100644 --- a/pkg/cli/engine_secrets.go +++ b/pkg/cli/engine_secrets.go @@ -290,19 +290,19 @@ func promptForSecret(req SecretRequirement, config EngineSecretConfig) error { // promptForCopilotPATUnified prompts the user for a Copilot PAT with detailed instructions func promptForCopilotPATUnified(req SecretRequirement, config EngineSecretConfig) error { fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "GitHub Copilot requires a fine-grained Personal Access Token (PAT) with 'Copilot requests' permissions.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("GitHub Copilot requires a fine-grained Personal Access Token (PAT) with 'Copilot requests' permissions.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Please create a token at:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please create a token at:")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" "+req.KeyURL)) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Configure the token with:") - fmt.Fprintln(os.Stderr, " • Token name: Agentic Workflows Copilot") - fmt.Fprintln(os.Stderr, " • Expiration: 90 days (recommended for testing)") - fmt.Fprintln(os.Stderr, " • Resource owner: Your personal account") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Configure the token with:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • Token name: Agentic Workflows Copilot")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • Expiration: 90 days (recommended for testing)")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • Resource owner: Your personal account")) fmt.Fprintln(os.Stderr, " • Repository access: \"Public repositories\" (you must use this setting for Copilot Requests permission to appear)") - fmt.Fprintln(os.Stderr, " • Add permissions → Copilot Requests: Read-only") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" • Add permissions → Copilot Requests: Read-only")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "If you run into trouble see https://github.github.com/gh-aw/reference/auth/#copilot_github_token.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("If you run into trouble see https://github.github.com/gh-aw/reference/auth/#copilot_github_token.")) var token string form := huh.NewForm( @@ -343,12 +343,12 @@ func promptForSystemTokenUnified(req SecretRequirement, config EngineSecretConfi engineSecretsLog.Printf("Prompting for system token: %s", req.Name) fmt.Fprintln(os.Stderr, "") - fmt.Fprintf(os.Stderr, "%s requires a GitHub Personal Access Token (PAT).\n", req.Name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("%s requires a GitHub Personal Access Token (PAT).", req.Name))) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("When needed: "+req.WhenNeeded)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Recommended scopes: "+req.Description)) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Create a token at:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Create a token at:")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" https://github.com/settings/personal-access-tokens/new")) fmt.Fprintln(os.Stderr, "") @@ -397,10 +397,10 @@ func promptForGenericAPIKeyUnified(req SecretRequirement, config EngineSecretCon } fmt.Fprintln(os.Stderr, "") - fmt.Fprintf(os.Stderr, "%s requires an API key.\n", label) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("%s requires an API key.", label))) fmt.Fprintln(os.Stderr, "") if req.KeyURL != "" { - fmt.Fprintln(os.Stderr, "Get your API key from:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Get your API key from:")) fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" "+req.KeyURL)) fmt.Fprintln(os.Stderr, "") } @@ -683,7 +683,7 @@ func displaySecretsSummaryTable(requirements []SecretRequirement, existingSecret nameWithPadding := fmt.Sprintf("%-*s", maxNameWidth, req.Name) // Display the line - fmt.Fprintf(os.Stderr, " %s %s - %s\n", statusLine, nameWithPadding, req.WhenNeeded) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s %s - %s", statusLine, nameWithPadding, req.WhenNeeded))) } fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/fix_command.go b/pkg/cli/fix_command.go index 31706234ab5..6a7588ab90a 100644 --- a/pkg/cli/fix_command.go +++ b/pkg/cli/fix_command.go @@ -98,11 +98,11 @@ func listAvailableCodemods() error { for _, codemod := range codemods { fmt.Fprintf(os.Stderr, " %s\n", console.FormatInfoMessage(codemod.Name)) - fmt.Fprintf(os.Stderr, " ID: %s\n", codemod.ID) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" ID: %s", codemod.ID))) if codemod.IntroducedIn != "" { - fmt.Fprintf(os.Stderr, " Introduced in: %s\n", codemod.IntroducedIn) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Introduced in: %s", codemod.IntroducedIn))) } - fmt.Fprintf(os.Stderr, " %s\n", codemod.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", codemod.Description))) fmt.Fprintln(os.Stderr, "") } @@ -241,12 +241,12 @@ func runFixCommand(workflowIDs []string, write bool, verbose bool, workflowDir s // Output as agent prompt fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To fix these issues, run:")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, " gh aw fix --write") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh aw fix --write")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Or fix them individually:")) fmt.Fprintln(os.Stderr, "") for _, wf := range workflowsNeedingFixes { - fmt.Fprintf(os.Stderr, " gh aw fix %s --write\n", strings.TrimSuffix(wf.File, ".md")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" gh aw fix %s --write", strings.TrimSuffix(wf.File, ".md")))) } } else { fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("✓ No fixes needed")) @@ -326,12 +326,12 @@ func processWorkflowFileWithInfo(filePath string, codemods []Codemod, write bool fmt.Fprintf(os.Stderr, "%s\n", console.FormatSuccessMessage("✓ "+fileName)) for _, codemodName := range appliedCodemods { - fmt.Fprintf(os.Stderr, " • %s\n", codemodName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s", codemodName))) } } else { fmt.Fprintf(os.Stderr, "%s\n", console.FormatWarningMessage("⚠ "+fileName)) for _, codemodName := range appliedCodemods { - fmt.Fprintf(os.Stderr, " • %s\n", codemodName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s", codemodName))) } } diff --git a/pkg/cli/interactive.go b/pkg/cli/interactive.go index cd64f370aca..f469f3ec55f 100644 --- a/pkg/cli/interactive.go +++ b/pkg/cli/interactive.go @@ -289,7 +289,7 @@ func (b *InteractiveWorkflowBuilder) generateWorkflow(force bool) error { } interactiveLog.Printf("Workflow file created successfully: %s", destFile) - fmt.Fprintf(os.Stderr, "Created new workflow: %s\n", destFile) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created new workflow: %s", destFile))) return nil } diff --git a/pkg/cli/list_workflows_command.go b/pkg/cli/list_workflows_command.go index 9c84fde9ce4..56887be08fe 100644 --- a/pkg/cli/list_workflows_command.go +++ b/pkg/cli/list_workflows_command.go @@ -93,7 +93,7 @@ func RunListWorkflows(repo, path, pattern string, verbose bool, jsonOutput bool, // List workflows from remote repository isRemote = true if verbose && !jsonOutput { - fmt.Fprintf(os.Stderr, "Listing workflow files from %s\n", repo) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Listing workflow files from %s", repo))) } mdFiles, err = getRemoteWorkflowFiles(repo, path, verbose, jsonOutput) } else { @@ -101,7 +101,7 @@ func RunListWorkflows(repo, path, pattern string, verbose bool, jsonOutput bool, if verbose && !jsonOutput { fmt.Fprintf(os.Stderr, "Listing workflow files\n") if pattern != "" { - fmt.Fprintf(os.Stderr, "Filtering by pattern: %s\n", pattern) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Filtering by pattern: %s", pattern))) } } mdFiles, err = getMarkdownWorkflowFiles(path) @@ -127,7 +127,7 @@ func RunListWorkflows(repo, path, pattern string, verbose bool, jsonOutput bool, } if verbose && !jsonOutput { - fmt.Fprintf(os.Stderr, "Found %d markdown workflow files\n", len(mdFiles)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d markdown workflow files", len(mdFiles)))) } // Build workflow list @@ -259,7 +259,7 @@ func getRemoteWorkflowFiles(repoSpec, workflowPath string, verbose bool, jsonOut repo = repoParts[1] if verbose && !jsonOutput { - fmt.Fprintf(os.Stderr, "Fetching workflow files from %s/%s@%s (path: %s)\n", owner, repo, ref, workflowPath) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Fetching workflow files from %s/%s@%s (path: %s)", owner, repo, ref, workflowPath))) } // Use the parser package to list workflow files diff --git a/pkg/cli/logs_github_rate_limit_usage.go b/pkg/cli/logs_github_rate_limit_usage.go index 4ca46d188a7..e7d488cad6a 100644 --- a/pkg/cli/logs_github_rate_limit_usage.go +++ b/pkg/cli/logs_github_rate_limit_usage.go @@ -18,6 +18,7 @@ import ( "sort" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" ) @@ -259,7 +260,7 @@ func analyzeGitHubRateLimits(runDir string, verbose bool) (*GitHubRateLimitUsage if verbose { if info, err := os.Stat(filePath); err == nil { - fmt.Fprintf(os.Stderr, " Found GitHub rate limits file: %s (%d bytes)\n", filepath.Base(filePath), info.Size()) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Found GitHub rate limits file: %s (%d bytes)", filepath.Base(filePath), info.Size()))) } } diff --git a/pkg/cli/mcp_config_file.go b/pkg/cli/mcp_config_file.go index 6a771361ace..19c68181e7d 100644 --- a/pkg/cli/mcp_config_file.go +++ b/pkg/cli/mcp_config_file.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -59,7 +60,7 @@ func ensureMCPConfig(verbose bool) error { if string(existingJSON) == string(newJSON) { mcpConfigLog.Print("Configuration is identical, skipping") if verbose { - fmt.Fprintf(os.Stderr, "MCP server '%s' already configured in %s\n", ghAwServerName, mcpConfigPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("MCP server '%s' already configured in %s", ghAwServerName, mcpConfigPath))) } return nil } @@ -103,7 +104,7 @@ func renderMCPConfigUpdateInstructions(filePath, serverName string, serverConfig "ℹ", "Existing file detected: "+filePath) fmt.Fprintln(os.Stderr) - fmt.Fprintln(os.Stderr, "To enable GitHub Copilot Agent MCP server integration, please add the following") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable GitHub Copilot Agent MCP server integration, please add the following")) fmt.Fprintf(os.Stderr, "to the \"mcpServers\" section of your %s file:\n", filePath) fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/mcp_inspect_inspector.go b/pkg/cli/mcp_inspect_inspector.go index bdc02ff6fd8..00cafe45219 100644 --- a/pkg/cli/mcp_inspect_inspector.go +++ b/pkg/cli/mcp_inspect_inspector.go @@ -73,7 +73,7 @@ func spawnMCPInspector(workflowFile string, serverFilter string, verbose bool) e if len(mcpConfigs) > 0 { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Found %d MCP server(s) in workflow:", len(mcpConfigs)))) for _, config := range mcpConfigs { - fmt.Fprintf(os.Stderr, " • %s (%s)\n", config.Name, config.Type) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s (%s)", config.Name, config.Type))) } fmt.Fprintln(os.Stderr) @@ -155,22 +155,22 @@ func spawnMCPInspector(workflowFile string, serverFilter string, verbose bool) e fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Configuration details for MCP inspector:")) for _, config := range mcpConfigs { - fmt.Fprintf(os.Stderr, "\n📡 %s (%s):\n", config.Name, config.Type) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\n📡 %s (%s):", config.Name, config.Type))) switch config.Type { case "stdio": if config.Container != "" { - fmt.Fprintf(os.Stderr, " Container: %s\n", config.Container) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Container: %s", config.Container))) } else { - fmt.Fprintf(os.Stderr, " Command: %s\n", config.Command) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Command: %s", config.Command))) if len(config.Args) > 0 { - fmt.Fprintf(os.Stderr, " Args: %s\n", strings.Join(config.Args, " ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Args: %s", strings.Join(config.Args, " ")))) } } case "http": - fmt.Fprintf(os.Stderr, " URL: %s\n", config.URL) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" URL: %s", config.URL))) } if len(config.Env) > 0 { - fmt.Fprintf(os.Stderr, " Environment Variables: %v\n", config.Env) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Environment Variables: %v", config.Env))) } } fmt.Fprintln(os.Stderr) diff --git a/pkg/cli/mcp_inspect_list.go b/pkg/cli/mcp_inspect_list.go index 102c925dc2e..c61c4ece1e2 100644 --- a/pkg/cli/mcp_inspect_list.go +++ b/pkg/cli/mcp_inspect_list.go @@ -59,9 +59,9 @@ func listWorkflowsWithMCP(workflowsDir string, verbose bool) error { mcpInspectListLog.Printf("Found %d workflows with MCP servers", len(workflowsWithMCP)) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Workflows with MCP servers:")) for _, workflow := range workflowsWithMCP { - fmt.Fprintf(os.Stderr, " • %s\n", workflow) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s", workflow))) } - fmt.Fprintf(os.Stderr, "\nRun 'gh aw mcp inspect ' to inspect MCP servers in a specific workflow.\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nRun 'gh aw mcp inspect ' to inspect MCP servers in a specific workflow.")) return nil } diff --git a/pkg/cli/mcp_inspect_mcp.go b/pkg/cli/mcp_inspect_mcp.go index ccaa6e41699..1e37c4fb172 100644 --- a/pkg/cli/mcp_inspect_mcp.go +++ b/pkg/cli/mcp_inspect_mcp.go @@ -443,7 +443,7 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { for i, tool := range info.Tools { toolNames[i] = tool.Name } - fmt.Fprintf(os.Stderr, "%s\n", strings.Join(toolNames, ", ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(strings.Join(toolNames, ", "))) return } @@ -456,23 +456,23 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader("🛠️ Tool Details: "+foundTool.Name)) // Display basic information - fmt.Fprintf(os.Stderr, "📋 **Name:** %s\n", foundTool.Name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("📋 **Name:** %s", foundTool.Name))) // Show title if available and different from name if foundTool.Title != "" && foundTool.Title != foundTool.Name { - fmt.Fprintf(os.Stderr, "📄 **Title:** %s\n", foundTool.Title) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("📄 **Title:** %s", foundTool.Title))) } if foundTool.Annotations != nil && foundTool.Annotations.Title != "" && foundTool.Annotations.Title != foundTool.Name && foundTool.Annotations.Title != foundTool.Title { - fmt.Fprintf(os.Stderr, "📄 **Annotation Title:** %s\n", foundTool.Annotations.Title) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("📄 **Annotation Title:** %s", foundTool.Annotations.Title))) } - fmt.Fprintf(os.Stderr, "📝 **Description:** %s\n", foundTool.Description) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("📝 **Description:** %s", foundTool.Description))) // Display allowance status if isAllowed { - fmt.Fprintf(os.Stderr, "✅ **Status:** Allowed\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("✅ **Status:** Allowed")) } else { - fmt.Fprintf(os.Stderr, "🚫 **Status:** Not allowed (add to 'allowed' list in workflow frontmatter)\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🚫 **Status:** Not allowed (add to 'allowed' list in workflow frontmatter)")) } // Display annotations if available @@ -480,28 +480,28 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader("⚙️ Tool Attributes")) if foundTool.Annotations.ReadOnlyHint { - fmt.Fprintf(os.Stderr, "🔒 **Read-only:** This tool does not modify its environment\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🔒 **Read-only:** This tool does not modify its environment")) } else { - fmt.Fprintf(os.Stderr, "🔓 **Modifies environment:** This tool can make changes\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🔓 **Modifies environment:** This tool can make changes")) } if foundTool.Annotations.IdempotentHint { - fmt.Fprintf(os.Stderr, "🔄 **Idempotent:** Calling with same arguments has no additional effect\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🔄 **Idempotent:** Calling with same arguments has no additional effect")) } if foundTool.Annotations.DestructiveHint != nil { if *foundTool.Annotations.DestructiveHint { - fmt.Fprintf(os.Stderr, "⚠️ **Destructive:** May perform destructive updates\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("⚠️ **Destructive:** May perform destructive updates")) } else { - fmt.Fprintf(os.Stderr, "➕ **Additive:** Performs only additive updates\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("➕ **Additive:** Performs only additive updates")) } } if foundTool.Annotations.OpenWorldHint != nil { if *foundTool.Annotations.OpenWorldHint { - fmt.Fprintf(os.Stderr, "🌐 **Open world:** Interacts with external entities\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🌐 **Open world:** Interacts with external entities")) } else { - fmt.Fprintf(os.Stderr, "🏠 **Closed world:** Domain of interaction is closed\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("🏠 **Closed world:** Domain of interaction is closed")) } } } @@ -510,9 +510,9 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { if foundTool.InputSchema != nil { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader("📥 Input Schema")) if schemaJSON, err := json.MarshalIndent(foundTool.InputSchema, "", " "); err == nil { - fmt.Fprintf(os.Stderr, "```json\n%s\n```\n", string(schemaJSON)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("```json\n%s\n```", string(schemaJSON)))) } else { - fmt.Fprintf(os.Stderr, "Error displaying input schema: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Error displaying input schema: %v", err))) } } else { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("📥 No input schema defined")) @@ -522,9 +522,9 @@ func displayDetailedToolInfo(info *parser.MCPServerInfo, toolName string) { if foundTool.OutputSchema != nil { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatSectionHeader("📤 Output Schema")) if schemaJSON, err := json.MarshalIndent(foundTool.OutputSchema, "", " "); err == nil { - fmt.Fprintf(os.Stderr, "```json\n%s\n```\n", string(schemaJSON)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("```json\n%s\n```", string(schemaJSON)))) } else { - fmt.Fprintf(os.Stderr, "Error displaying output schema: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Error displaying output schema: %v", err))) } } else { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("📤 No output schema defined")) @@ -553,29 +553,29 @@ func displayToolAllowanceHint(info *parser.MCPServerInfo) { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("💡 To allow blocked tools, add them to your workflow frontmatter:")) // Show the frontmatter syntax example - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "```yaml\n") - fmt.Fprintf(os.Stderr, "tools:\n") - fmt.Fprintf(os.Stderr, " %s:\n", info.Config.Name) - fmt.Fprintf(os.Stderr, " allowed:\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("```yaml")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("tools:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s:", info.Config.Name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" allowed:")) // Add currently allowed tools first (if any) for _, allowed := range info.Config.Allowed { - fmt.Fprintf(os.Stderr, " - %s\n", allowed) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", allowed))) } // Show first few blocked tools as examples (limit to 3 for readability) exampleCount := min(len(blockedTools), 3) for i := range exampleCount { - fmt.Fprintf(os.Stderr, " - %s\n", blockedTools[i]) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", blockedTools[i]))) } if len(blockedTools) > 3 { - fmt.Fprintf(os.Stderr, " # ... and %d more tools\n", len(blockedTools)-3) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" # ... and %d more tools", len(blockedTools)-3))) } - fmt.Fprintf(os.Stderr, "```\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("```")) if len(blockedTools) > 3 { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("📋 All blocked tools: "+strings.Join(blockedTools, ", "))) @@ -585,16 +585,16 @@ func displayToolAllowanceHint(info *parser.MCPServerInfo) { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("💡 All tools are currently allowed (no 'allowed' list specified)")) if len(info.Tools) > 0 { fmt.Fprintf(os.Stderr, "\n%s\n", console.FormatInfoMessage("To restrict tools, add an 'allowed' list to your workflow frontmatter:")) - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "```yaml\n") - fmt.Fprintf(os.Stderr, "tools:\n") - fmt.Fprintf(os.Stderr, " %s:\n", info.Config.Name) - fmt.Fprintf(os.Stderr, " allowed:\n") - fmt.Fprintf(os.Stderr, " - %s # Allow only specific tools\n", info.Tools[0].Name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("```yaml")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("tools:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s:", info.Config.Name))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" allowed:")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s # Allow only specific tools", info.Tools[0].Name))) if len(info.Tools) > 1 { - fmt.Fprintf(os.Stderr, " - %s\n", info.Tools[1].Name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", info.Tools[1].Name))) } - fmt.Fprintf(os.Stderr, "```\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("```")) } } else { // All tools are explicitly allowed diff --git a/pkg/cli/mcp_list.go b/pkg/cli/mcp_list.go index 5b9d78003da..ad66477cfc2 100644 --- a/pkg/cli/mcp_list.go +++ b/pkg/cli/mcp_list.go @@ -122,7 +122,7 @@ func ListWorkflowMCP(workflowFile string, verbose bool) error { } if !verbose { - fmt.Fprintf(os.Stderr, "\nRun 'gh aw mcp list %s --verbose' for detailed information\n", workflowFile) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nRun 'gh aw mcp list %s --verbose' for detailed information", workflowFile))) } return nil @@ -220,9 +220,9 @@ func listWorkflowsWithMCPServers(workflowsDir string, verbose bool) error { } if !verbose { - fmt.Fprintf(os.Stderr, "\nRun 'gh aw mcp list --verbose' for detailed information\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nRun 'gh aw mcp list --verbose' for detailed information")) } - fmt.Fprintf(os.Stderr, "Run 'gh aw mcp list ' to list MCP servers in a specific workflow\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Run 'gh aw mcp list ' to list MCP servers in a specific workflow")) return nil } diff --git a/pkg/cli/mcp_list_tools.go b/pkg/cli/mcp_list_tools.go index 57f459496b9..97b60acf6ba 100644 --- a/pkg/cli/mcp_list_tools.go +++ b/pkg/cli/mcp_list_tools.go @@ -75,7 +75,7 @@ func ListToolsForMCP(workflowFile string, mcpServerName string, verbose bool) er if len(mcpConfigs) > 0 { fmt.Fprintf(os.Stderr, "Available MCP servers: ") serverNames := sliceutil.Map(mcpConfigs, func(config parser.RegistryMCPServerConfig) string { return config.Name }) - fmt.Fprintf(os.Stderr, "%s\n", strings.Join(serverNames, ", ")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(strings.Join(serverNames, ", "))) } return nil } @@ -132,7 +132,7 @@ func findWorkflowsWithMCPServer(workflowsDir string, mcpServerName string, verbo // Display matching workflows and suggest using one fmt.Fprintf(os.Stderr, "Found MCP server '%s' in %d workflow(s): %s\n", mcpServerName, len(matchingWorkflows), strings.Join(matchingWorkflows, ", ")) - fmt.Fprintf(os.Stderr, "\nRun 'gh aw mcp list-tools %s ' to list tools for a specific workflow\n", mcpServerName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nRun 'gh aw mcp list-tools %s ' to list tools for a specific workflow", mcpServerName))) return nil } diff --git a/pkg/cli/observability_insights.go b/pkg/cli/observability_insights.go index 1869a9c4517..412ab4c4d60 100644 --- a/pkg/cli/observability_insights.go +++ b/pkg/cli/observability_insights.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -342,10 +343,10 @@ func renderObservabilityInsights(insights []ObservabilityInsight) { icon = "[low]" } - fmt.Fprintf(os.Stderr, " %s %s [%s]\n", icon, insight.Title, insight.Category) - fmt.Fprintf(os.Stderr, " %s\n", insight.Summary) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s %s [%s]", icon, insight.Title, insight.Category))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", insight.Summary))) if strings.TrimSpace(insight.Evidence) != "" { - fmt.Fprintf(os.Stderr, " Evidence: %s\n", insight.Evidence) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Evidence: %s", insight.Evidence))) } fmt.Fprintln(os.Stderr) } diff --git a/pkg/cli/preconditions.go b/pkg/cli/preconditions.go index fd757f9b32e..9fec7562172 100644 --- a/pkg/cli/preconditions.go +++ b/pkg/cli/preconditions.go @@ -23,7 +23,7 @@ func checkGHAuthStatusShared(verbose bool) error { if err != nil { fmt.Fprintln(os.Stderr, console.FormatErrorMessage("You are not logged in to GitHub CLI.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Please run the following command to authenticate:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please run the following command to authenticate:")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" gh auth login")) fmt.Fprintln(os.Stderr, "") @@ -68,8 +68,8 @@ func checkActionsEnabledShared(repoSlug string, verbose bool) error { if !permissions.Enabled { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("GitHub Actions appears to be disabled for this repository.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "You can still add workflows, but they won't run until Actions is enabled.") - fmt.Fprintln(os.Stderr, "To enable GitHub Actions, go to Settings → Actions → General.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("You can still add workflows, but they won't run until Actions is enabled.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable GitHub Actions, go to Settings → Actions → General.")) fmt.Fprintln(os.Stderr, "") return nil } @@ -85,12 +85,12 @@ func checkActionsEnabledShared(repoSlug string, verbose bool) error { // Only local actions allowed - this won't work for agentic workflows fmt.Fprintln(os.Stderr, console.FormatErrorMessage("This repository only allows local actions (actions defined in this repository).")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Agentic workflows require GitHub-owned actions to run.") - fmt.Fprintln(os.Stderr, "To allow this, go to Settings → Actions → General → Actions permissions") - fmt.Fprintln(os.Stderr, "and select 'Allow all actions' or 'Allow select actions' with GitHub-owned actions enabled.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Agentic workflows require GitHub-owned actions to run.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To allow this, go to Settings → Actions → General → Actions permissions")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("and select 'Allow all actions' or 'Allow select actions' with GitHub-owned actions enabled.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Note: For organization repositories, this setting may be controlled at the org level.") - fmt.Fprintln(os.Stderr, "Contact an organization owner if you cannot change this setting.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("For organization repositories, this setting may be controlled at the org level.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Contact an organization owner if you cannot change this setting.")) fmt.Fprintln(os.Stderr, "") return errors.New("repository action permissions prevent agentic workflows from running") case "selected": @@ -137,12 +137,12 @@ func checkSelectedActionsPermissions(selectedActionsURL string, verbose bool) er if !selectedActions.GitHubOwnedAllowed { fmt.Fprintln(os.Stderr, console.FormatErrorMessage("This repository does not allow GitHub-owned actions.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Agentic workflows require GitHub-owned actions (like actions/checkout) to run.") - fmt.Fprintln(os.Stderr, "To allow this, go to Settings → Actions → General → Actions permissions") - fmt.Fprintln(os.Stderr, "and enable 'Allow actions created by GitHub'.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Agentic workflows require GitHub-owned actions (like actions/checkout) to run.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To allow this, go to Settings → Actions → General → Actions permissions")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("and enable 'Allow actions created by GitHub'.")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "Note: For organization repositories, this setting may be controlled at the org level.") - fmt.Fprintln(os.Stderr, "Contact an organization owner if you cannot change this setting.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("For organization repositories, this setting may be controlled at the org level.")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Contact an organization owner if you cannot change this setting.")) fmt.Fprintln(os.Stderr, "") return errors.New("GitHub-owned actions are not allowed in this repository") } @@ -183,7 +183,7 @@ func checkUserPermissionsShared(repoSlug string, verbose bool) (bool, error) { if !hasAccess { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("You do not have write access to %s/%s.", owner, repo))) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "You can still add workflows, but you'll need to propose changes via pull requests.") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("You can still add workflows, but you'll need to propose changes via pull requests.")) fmt.Fprintln(os.Stderr, "") } else if verbose { fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Repository permissions verified")) diff --git a/pkg/cli/remove_command.go b/pkg/cli/remove_command.go index 14a08b3391b..59d02298262 100644 --- a/pkg/cli/remove_command.go +++ b/pkg/cli/remove_command.go @@ -55,7 +55,7 @@ func RemoveWorkflows(pattern string, keepOrphans bool, workflowDir string) error if workflowName != "" { fmt.Fprintf(os.Stderr, " %-20s - %s\n", name, workflowName) } else { - fmt.Fprintf(os.Stderr, " %s\n", name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", name))) } } fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("\nUsage: %s remove ", string(constants.CLIExtensionPrefix)))) @@ -99,15 +99,15 @@ func RemoveWorkflows(pattern string, keepOrphans bool, workflowDir string) error for _, file := range filesToRemove { workflowName, _ := extractWorkflowNameFromFile(file) if workflowName != "" { - fmt.Fprintf(os.Stderr, " %s - %s\n", filepath.Base(file), workflowName) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s - %s", filepath.Base(file), workflowName))) } else { - fmt.Fprintf(os.Stderr, " %s\n", filepath.Base(file)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s", filepath.Base(file)))) } // Also check for corresponding .lock.yml file in .github/workflows lockFile := stringutil.MarkdownToLockFile(file) if _, err := os.Stat(lockFile); err == nil { - fmt.Fprintf(os.Stderr, " %s (compiled workflow)\n", filepath.Base(lockFile)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s (compiled workflow)", filepath.Base(lockFile)))) } } @@ -115,7 +115,7 @@ func RemoveWorkflows(pattern string, keepOrphans bool, workflowDir string) error if len(orphanedIncludes) > 0 { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nThe following orphaned include files will also be removed (suppress with --keep-orphans):")) for _, include := range orphanedIncludes { - fmt.Fprintf(os.Stderr, " %s (orphaned include)\n", include) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s (orphaned include)", include))) } } diff --git a/pkg/cli/run_interactive.go b/pkg/cli/run_interactive.go index 2ce10d7c857..1d03e172810 100644 --- a/pkg/cli/run_interactive.go +++ b/pkg/cli/run_interactive.go @@ -118,7 +118,7 @@ func findRunnableWorkflows(verbose bool) ([]WorkflowOption, error) { } if verbose { - fmt.Fprintf(os.Stderr, "Found %d workflow files, checking for workflow_dispatch trigger...\n", len(mdFiles)) + fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Found %d workflow files, checking for workflow_dispatch trigger...", len(mdFiles)))) } var runnableWorkflows []WorkflowOption @@ -210,9 +210,9 @@ func selectWorkflow(ctx context.Context, workflows []WorkflowOption) (*WorkflowO func selectWorkflowNonInteractive(workflows []WorkflowOption) (*WorkflowOption, error) { runInteractiveLog.Printf("Non-TTY detected, showing text list: %d workflows", len(workflows)) - fmt.Fprintf(os.Stderr, "\nSelect a workflow to run:\n\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("\nSelect a workflow to run:\n")) for i, wf := range workflows { - fmt.Fprintf(os.Stderr, " %d) %s\n", i+1, wf.Name) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %d) %s", i+1, wf.Name))) } fmt.Fprintf(os.Stderr, "\nSelect (1-%d): ", len(workflows)) @@ -251,7 +251,7 @@ func showWorkflowInfo(wf *WorkflowOption) { if input.Default != "" { defaultVal = fmt.Sprintf(" [default: %s]", input.Default) } - fmt.Fprintf(os.Stderr, " • %s%s%s%s\n", name, required, desc, defaultVal) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" • %s%s%s%s", name, required, desc, defaultVal))) } } fmt.Fprintln(os.Stderr, "") diff --git a/pkg/cli/run_push.go b/pkg/cli/run_push.go index 2ffd446d025..a3dd50056e6 100644 --- a/pkg/cli/run_push.go +++ b/pkg/cli/run_push.go @@ -416,7 +416,7 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Staging %d files for commit", len(files)))) for _, file := range files { - fmt.Fprintf(os.Stderr, " - %s\n", file) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", file))) } } @@ -535,7 +535,7 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Extra staged files:")) for _, file := range extraStagedFiles { - fmt.Fprintf(os.Stderr, " - %s\n", file) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", file))) } fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please commit or unstage these files before using --push")) @@ -553,7 +553,7 @@ func pushWorkflowFiles(workflowName string, files []string, refOverride string, fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Ready to commit and push the following files:")) for _, file := range files { - fmt.Fprintf(os.Stderr, " - %s\n", file) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" - %s", file))) } fmt.Fprintln(os.Stderr, "") fmt.Fprintf(os.Stderr, console.FormatInfoMessage("Commit message: %s\n"), commitMessage) diff --git a/pkg/cli/secret_set_command.go b/pkg/cli/secret_set_command.go index 11c379fe2e8..86e4b9a7c3d 100644 --- a/pkg/cli/secret_set_command.go +++ b/pkg/cli/secret_set_command.go @@ -163,7 +163,7 @@ func resolveSecretValueForSet(fromEnv, fromFlag string) (string, error) { // Fallback to non-interactive stdin reading (piped input or non-TTY) secretSetLog.Print("Using non-interactive stdin reading") if isTerminal { - fmt.Fprintln(os.Stderr, "Enter secret value, then press Ctrl+D:") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Enter secret value, then press Ctrl+D:")) } reader := io.Reader(os.Stdin) diff --git a/pkg/cli/shell_completion.go b/pkg/cli/shell_completion.go index 62ad66d3c7b..96e9d7bc067 100644 --- a/pkg/cli/shell_completion.go +++ b/pkg/cli/shell_completion.go @@ -289,8 +289,8 @@ func installZshCompletion(verbose bool, cmd *cobra.Command) error { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable completions, add the following to your ~/.zshrc:")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintf(os.Stderr, " fpath=(~/.zsh/completions $fpath)\n") - fmt.Fprintf(os.Stderr, " autoload -Uz compinit && compinit\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" fpath=(~/.zsh/completions $fpath)")) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" autoload -Uz compinit && compinit")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Then restart your shell or run: source ~/.zshrc")) } else { @@ -363,14 +363,14 @@ func installPowerShellCompletion(verbose bool, cmd *cobra.Command) error { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To enable completions, add the following to your PowerShell profile:")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, " gh aw completion powershell | Out-String | Invoke-Expression") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh aw completion powershell | Out-String | Invoke-Expression")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Or run the following command to append it automatically:")) fmt.Fprintln(os.Stderr, "") if runtime.GOOS == "windows" { - fmt.Fprintln(os.Stderr, " gh aw completion powershell >> $PROFILE") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh aw completion powershell >> $PROFILE")) } else { - fmt.Fprintln(os.Stderr, " echo 'gh aw completion powershell | Out-String | Invoke-Expression' >> $PROFILE") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" echo 'gh aw completion powershell | Out-String | Invoke-Expression' >> $PROFILE")) } fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Then restart your shell or run: . $PROFILE")) @@ -547,7 +547,7 @@ func uninstallPowerShellCompletion(verbose bool) error { fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("To uninstall completions, remove the following line from your PowerShell profile:")) fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, " gh aw completion powershell | Out-String | Invoke-Expression") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh aw completion powershell | Out-String | Invoke-Expression")) fmt.Fprintln(os.Stderr, "") fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Then restart your shell or run: . $PROFILE")) diff --git a/pkg/cli/token_usage.go b/pkg/cli/token_usage.go index 550e9d2056d..03fcc1d0e65 100644 --- a/pkg/cli/token_usage.go +++ b/pkg/cli/token_usage.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/timeutil" "github.com/github/gh-aw/pkg/types" @@ -385,7 +386,7 @@ func analyzeTokenUsage(runDir string, verbose bool) (*TokenUsageSummary, error) if verbose { fileInfo, _ := os.Stat(filePath) if fileInfo != nil { - fmt.Fprintf(os.Stderr, " Found token usage file: %s (%d bytes)\n", filepath.Base(filePath), fileInfo.Size()) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Found token usage file: %s (%d bytes)", filepath.Base(filePath), fileInfo.Size()))) } } @@ -401,7 +402,7 @@ func analyzeTokenUsage(runDir string, verbose bool) (*TokenUsageSummary, error) if verbose { fileInfo, _ := os.Stat(agentUsagePath) if fileInfo != nil { - fmt.Fprintf(os.Stderr, " Found agent usage file: %s (%d bytes)\n", filepath.Base(agentUsagePath), fileInfo.Size()) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Found agent usage file: %s (%d bytes)", filepath.Base(agentUsagePath), fileInfo.Size()))) } } diff --git a/pkg/cli/trial_confirmation.go b/pkg/cli/trial_confirmation.go index 112860f8da1..c77857c6ae2 100644 --- a/pkg/cli/trial_confirmation.go +++ b/pkg/cli/trial_confirmation.go @@ -165,8 +165,8 @@ func showTrialConfirmation(parsedSpecs []*WorkflowSpec, logicalRepoSlug, cloneRe workflowName := parsedSpecs[0].WorkflowName if repeatCount > 0 && autoMergePRs { fmt.Fprintf(os.Stderr, console.FormatInfoMessage(" %d. For each of %d executions:\n"), stepNum, repeatCount+1) - fmt.Fprintf(os.Stderr, " a. Execute %s\n", workflowName) - fmt.Fprintf(os.Stderr, " b. Auto-merge any pull requests created during execution\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" a. Execute %s", workflowName))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" b. Auto-merge any pull requests created during execution")) } else if repeatCount > 0 { fmt.Fprintf(os.Stderr, console.FormatInfoMessage(" %d. Execute %s %d times\n"), stepNum, workflowName, repeatCount+1) } else if autoMergePRs { @@ -182,8 +182,8 @@ func showTrialConfirmation(parsedSpecs []*WorkflowSpec, logicalRepoSlug, cloneRe if repeatCount > 0 && autoMergePRs { fmt.Fprintf(os.Stderr, console.FormatInfoMessage(" %d. For each of %d executions:\n"), stepNum, repeatCount+1) - fmt.Fprintf(os.Stderr, " a. Execute: %s\n", workflowList) - fmt.Fprintf(os.Stderr, " b. Auto-merge any pull requests created during execution\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" a. Execute: %s", workflowList))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" b. Auto-merge any pull requests created during execution")) } else if repeatCount > 0 { fmt.Fprintf(os.Stderr, console.FormatInfoMessage(" %d. Execute %d times: %s\n"), stepNum, repeatCount+1, workflowList) } else if autoMergePRs { diff --git a/pkg/cli/update_actions.go b/pkg/cli/update_actions.go index c29dbd36e75..b6792114eab 100644 --- a/pkg/cli/update_actions.go +++ b/pkg/cli/update_actions.go @@ -182,7 +182,7 @@ func UpdateActions(ctx context.Context, allowMajor, verbose, disableReleaseBump if len(failedActions) > 0 { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to check %d action(s):", len(failedActions)))) for _, f := range failedActions { - fmt.Fprintf(os.Stderr, " %s: %s\n", f.name, f.err) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s: %s", f.name, f.err))) } fmt.Fprintln(os.Stderr, "") } diff --git a/pkg/cli/update_container_pins.go b/pkg/cli/update_container_pins.go index 594172944a7..933af8165ad 100644 --- a/pkg/cli/update_container_pins.go +++ b/pkg/cli/update_container_pins.go @@ -147,7 +147,7 @@ func UpdateContainerPins(ctx context.Context, workflowDir string, verbose bool) if len(failedImages) > 0 { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to resolve digest for %d image(s) (Docker/crane may be unavailable):", len(failedImages)))) for _, f := range failedImages { - fmt.Fprintf(os.Stderr, " %s: %s\n", f.image, f.reason) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s: %s", f.image, f.reason))) } fmt.Fprintln(os.Stderr, "") } @@ -307,7 +307,7 @@ func resolveDigestViaCrane(ctx context.Context, image string) (string, error) { // resolveDigestViaPull pulls the image and then reads its RepoDigests field. func resolveDigestViaPull(ctx context.Context, image string, verbose bool) (string, error) { if verbose { - fmt.Fprintf(os.Stderr, " Pulling %s to resolve digest...\n", image) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" Pulling %s to resolve digest...", image))) } pullCtx, pullCancel := context.WithTimeout(ctx, dockerCmdTimeout) diff --git a/pkg/cli/update_display.go b/pkg/cli/update_display.go index b16833bb5e6..55ba5874e78 100644 --- a/pkg/cli/update_display.go +++ b/pkg/cli/update_display.go @@ -24,7 +24,7 @@ func showUpdateSummary(successfulUpdates []string, failedUpdates []updateFailure if len(failedUpdates) > 0 { fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Failed to update %d workflow(s):", len(failedUpdates)))) for _, failure := range failedUpdates { - fmt.Fprintf(os.Stderr, " %s: %s\n", failure.Name, failure.Error) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf(" %s: %s", failure.Name, failure.Error))) } fmt.Fprintln(os.Stderr, "") } diff --git a/pkg/cli/update_extension_check.go b/pkg/cli/update_extension_check.go index 9a745f8e068..49d6a637f6c 100644 --- a/pkg/cli/update_extension_check.go +++ b/pkg/cli/update_extension_check.go @@ -186,9 +186,9 @@ func upgradeExtensionIfOutdated(verbose bool, includePrereleases bool) (bool, st // running. Guide the user to upgrade manually from a separate shell. fmt.Fprintln(os.Stderr, console.FormatInfoMessage("On Windows, gh-aw cannot self-upgrade while it is running.")) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Please upgrade manually by running one of the following:")) - fmt.Fprintln(os.Stderr, " gh extension upgrade gh-aw") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh extension upgrade gh-aw")) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("If that does not work, try reinstalling:")) - fmt.Fprintln(os.Stderr, " gh extension remove gh-aw") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" gh extension remove gh-aw")) fmt.Fprintln(os.Stderr, " gh extension install "+extensionRepo) } return false, "", fmt.Errorf("failed to upgrade gh-aw extension: %w", retryErr) diff --git a/pkg/cli/vscode_config.go b/pkg/cli/vscode_config.go index 511223565f5..851cfc9a95e 100644 --- a/pkg/cli/vscode_config.go +++ b/pkg/cli/vscode_config.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -77,7 +78,7 @@ func ensureVSCodeSettings(verbose bool) error { if _, err := os.Stat(settingsPath); err == nil { vscodeConfigLog.Print("Settings file already exists, skipping creation") if verbose { - fmt.Fprintf(os.Stderr, "Settings file already exists at %s\n", settingsPath) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Settings file already exists at %s", settingsPath))) } return nil } diff --git a/pkg/workflow/action_sha_checker.go b/pkg/workflow/action_sha_checker.go index e4ed3ae144f..bb1c510b21c 100644 --- a/pkg/workflow/action_sha_checker.go +++ b/pkg/workflow/action_sha_checker.go @@ -183,8 +183,8 @@ func ValidateActionSHAsInLockFile(lockFilePath string, cache *ActionCache, verbo // Show full SHA in verbose mode if verbose { - fmt.Fprintf(os.Stderr, " Current: %s\n", check.Action.SHA) - fmt.Fprintf(os.Stderr, " Latest: %s\n", check.LatestSHA) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Current: "+check.Action.SHA)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Latest: "+check.LatestSHA)) } } } diff --git a/pkg/workflow/cache.go b/pkg/workflow/cache.go index e9e0e107777..df5aa23b06d 100644 --- a/pkg/workflow/cache.go +++ b/pkg/workflow/cache.go @@ -8,6 +8,7 @@ import ( "sort" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/logger" "github.com/goccy/go-yaml" @@ -335,7 +336,7 @@ func generateCacheSteps(builder *strings.Builder, data *WorkflowData, verbose bo var topLevel map[string]any if err := yaml.Unmarshal([]byte(data.Cache), &topLevel); err != nil { if verbose { - fmt.Fprintf(os.Stderr, "Warning: Failed to parse cache configuration: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Warning: Failed to parse cache configuration: %v", err))) } return } @@ -344,7 +345,7 @@ func generateCacheSteps(builder *strings.Builder, data *WorkflowData, verbose bo cacheConfig, exists := topLevel["cache"] if !exists { if verbose { - fmt.Fprintf(os.Stderr, "Warning: No cache key found in parsed configuration\n") + fmt.Fprintln(os.Stderr, console.FormatWarningMessage("No cache key found in parsed configuration")) } return } diff --git a/pkg/workflow/claude_logs.go b/pkg/workflow/claude_logs.go index 2526ba9edd9..cb6d66871f5 100644 --- a/pkg/workflow/claude_logs.go +++ b/pkg/workflow/claude_logs.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/typeutil" ) @@ -152,7 +153,7 @@ func (e *ClaudeEngine) parseClaudeJSONLog(logContent string, verbose bool) LogMe // If that fails, try to parse as mixed format (debug logs + JSONL) claudeLogsLog.Print("JSON array parse failed, trying JSONL format") if verbose { - fmt.Fprintf(os.Stderr, "Failed to parse Claude log as JSON array, trying JSONL format: %v\n", err) + fmt.Fprintln(os.Stderr, console.FormatErrorMessage(fmt.Sprintf("Failed to parse Claude log as JSON array, trying JSONL format: %v", err))) } logEntries = []map[string]any{} @@ -213,7 +214,7 @@ func (e *ClaudeEngine) parseClaudeJSONLog(logContent string, verbose bool) LogMe if err := json.Unmarshal([]byte(trimmedLine), &jsonEntry); err != nil { // Skip invalid JSON lines (could be partial debug output) if verbose { - fmt.Fprintf(os.Stderr, "Skipping invalid JSON line: %s\n", trimmedLine) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Skipping invalid JSON line: "+trimmedLine)) } continue } @@ -223,13 +224,13 @@ func (e *ClaudeEngine) parseClaudeJSONLog(logContent string, verbose bool) LogMe if len(logEntries) == 0 { if verbose { - fmt.Fprintf(os.Stderr, "No valid JSON entries found in Claude log\n") + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("No valid JSON entries found in Claude log")) } return metrics } if verbose { - fmt.Fprintf(os.Stderr, "Extracted %d JSON entries from mixed format Claude log\n", len(logEntries)) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Extracted %d JSON entries from mixed format Claude log", len(logEntries)))) } } diff --git a/pkg/workflow/compiler_orchestrator_engine.go b/pkg/workflow/compiler_orchestrator_engine.go index 6d47c6ebfb8..9e1bb1001ff 100644 --- a/pkg/workflow/compiler_orchestrator_engine.go +++ b/pkg/workflow/compiler_orchestrator_engine.go @@ -168,13 +168,13 @@ func (c *Compiler) setupEngineAndImports(result *parser.FrontmatterResult, clean fullPath, resolveErr := parser.ResolveIncludePath(importFilePath, markdownDir, importCache) if resolveErr != nil { orchestratorEngineLog.Printf("Skipping security scan for unresolvable import: %s: %v", importedFile, resolveErr) - fmt.Fprintf(os.Stderr, "WARNING: Skipping security scan for unresolvable import '%s': %v\n", importedFile, resolveErr) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping security scan for unresolvable import '%s': %v", importedFile, resolveErr))) continue } importContent, readErr := parser.ReadFile(fullPath) if readErr != nil { orchestratorEngineLog.Printf("Skipping security scan for unreadable import: %s: %v", fullPath, readErr) - fmt.Fprintf(os.Stderr, "WARNING: Skipping security scan for unreadable import '%s' (resolved path: %s): %v\n", importedFile, fullPath, readErr) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping security scan for unreadable import '%s' (resolved path: %s): %v", importedFile, fullPath, readErr))) continue } if findings := ScanMarkdownSecurity(string(importContent)); len(findings) > 0 { diff --git a/pkg/workflow/mcp_renderer.go b/pkg/workflow/mcp_renderer.go index ad9efaa1dd2..5b3c1a6fdeb 100644 --- a/pkg/workflow/mcp_renderer.go +++ b/pkg/workflow/mcp_renderer.go @@ -49,6 +49,7 @@ import ( "os" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -86,7 +87,7 @@ func HandleCustomMCPToolInSwitch( if toolConfig, ok := tools[toolName].(map[string]any); ok { if hasMcp, _ := hasMCPConfig(toolConfig); hasMcp { if err := renderFunc(yaml, toolName, toolConfig, isLast); err != nil { - fmt.Fprintf(os.Stderr, "Error generating custom MCP configuration for %s: %v\n", toolName, err) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Error generating custom MCP configuration for %s: %v", toolName, err))) } return true } diff --git a/pkg/workflow/push_to_pull_request_branch.go b/pkg/workflow/push_to_pull_request_branch.go index bc58b6b50cf..387b29f4a42 100644 --- a/pkg/workflow/push_to_pull_request_branch.go +++ b/pkg/workflow/push_to_pull_request_branch.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/logger" ) @@ -109,7 +110,7 @@ func (c *Compiler) parsePushToPullRequestBranchConfig(outputMap map[string]any) default: // Invalid value, use default and log warning if c.verbose { - fmt.Fprintf(os.Stderr, "Warning: invalid if-no-changes value '%s', using default 'warn'\n", ifNoChangesStr) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("invalid if-no-changes value '%s', using default 'warn'", ifNoChangesStr))) } pushToBranchConfig.IfNoChanges = "warn" } diff --git a/pkg/workflow/side_repo_maintenance.go b/pkg/workflow/side_repo_maintenance.go index 40870864e1e..152eade1f24 100644 --- a/pkg/workflow/side_repo_maintenance.go +++ b/pkg/workflow/side_repo_maintenance.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/stringutil" ) @@ -106,7 +107,7 @@ func generateAllSideRepoMaintenanceWorkflows( if err := generateSideRepoMaintenanceWorkflow(target, outPath, version, actionMode, actionTag, runsOnValue, resolver, hasExpires, minExpiresDays); err != nil { return fmt.Errorf("failed to generate side-repo maintenance workflow for %s: %w", target.Repository, err) } - fmt.Fprintf(os.Stderr, " Generated side-repo maintenance workflow: %s\n", filename) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(" Generated side-repo maintenance workflow: "+filename)) } // Remove stale side-repo maintenance workflows that are no longer referenced. @@ -130,7 +131,7 @@ func generateAllSideRepoMaintenanceWorkflows( if err := os.Remove(stalePath); err != nil { return fmt.Errorf("failed to remove stale side-repo maintenance workflow %s: %w", stalePath, err) } - fmt.Fprintf(os.Stderr, " Removed stale side-repo maintenance workflow: %s\n", name) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(" Removed stale side-repo maintenance workflow: "+name)) } return nil diff --git a/process_file.py b/process_file.py new file mode 100755 index 00000000000..4f813572dbf --- /dev/null +++ b/process_file.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +""" +Script to migrate fmt.Fprintf/Fprintln(os.Stderr) calls to console helpers. +This processes one file at a time and outputs the modified content. +""" + +import re +import sys + +def should_skip_conversion(line, prev_lines): + """Determine if this line should be skipped.""" + # Skip blank lines + if 'fmt.Fprintln(os.Stderr, "")' in line or line.strip() == 'fmt.Fprintln(os.Stderr)': + return True + if 'fmt.Fprintf(os.Stderr, "\\n")' in line and 'fmt.Fprintf(os.Stderr, "\\n")' == line.strip(): + return True + # Skip complex formatting with padding/alignment + if re.search(r'%-?\d+[sd]', line): + return True + # Skip if already using console.Format + if 'console.Format' in line: + return True + return False + +def determine_helper(message_content): + """Determine the appropriate console helper based on message content.""" + msg_lower = message_content.lower() + + # Error patterns + if any(p in msg_lower for p in ['error:', 'failed', 'failure', 'cannot', 'unable', 'err.error()']): + return 'FormatErrorMessage' + + # Success patterns + if any(p in msg_lower for p in ['success', 'done!', 'complete', ' enabled', ' disabled', 'created', 'updated', 'removed']): + return 'FormatSuccessMessage' + + # Progress patterns (present continuous) + if any(p in msg_lower for p in ['downloading', 'processing', 'compiling', 'running', 'checking', 'fetching', 'loading', 'building', 'installing', 'analyzing', 'creating']): + return 'FormatProgressMessage' + + # Warning patterns + if any(p in msg_lower for p in ['warning:', 'note:', 'deprecated', 'caution']): + return 'FormatWarningMessage' + + # Command patterns + if 'gh aw ' in message_content or message_content.strip().startswith('`'): + return 'FormatCommandMessage' + + # List item (starts with spaces/indent) + if message_content.lstrip().startswith(' ') and not message_content.lstrip().startswith(' '): + return 'FormatListItem' + + # Default to info + return 'FormatInfoMessage' + +def clean_prefix(msg, helper): + """Remove redundant prefixes based on the helper.""" + if helper == 'FormatErrorMessage': + msg = re.sub(r'^Error:\s*', '', msg, flags=re.IGNORECASE) + msg = re.sub(r'^Failed:\s*', '', msg, flags=re.IGNORECASE) + elif helper == 'FormatWarningMessage': + msg = re.sub(r'^Warning:\s*', '', msg, flags=re.IGNORECASE) + msg = re.sub(r'^Note:\s*', '', msg, flags=re.IGNORECASE) + elif helper == 'FormatSuccessMessage': + msg = re.sub(r'^Success:\s*', '', msg, flags=re.IGNORECASE) + msg = re.sub(r'^✓\s*', '', msg) + return msg + +def convert_fprintln(line): + """Convert fmt.Fprintln(os.Stderr, msg) to use console helper.""" + # Pattern: fmt.Fprintln(os.Stderr, "literal message") + match = re.search(r'fmt\.Fprintln\(os\.Stderr,\s*"([^"]+)"\)', line) + if match: + msg = match.group(1) + helper = determine_helper(msg) + msg = clean_prefix(msg, helper) + # Remove \n if present at the end + msg = msg.rstrip('\\n') + replacement = f'fmt.Fprintln(os.Stderr, console.{helper}("{msg}"))' + return line.replace(match.group(0), replacement) + + # Pattern: fmt.Fprintln(os.Stderr, console.FormatXXX(...)) + # Already converted, skip + if 'console.Format' in line: + return line + + return line + +def convert_fprintf(line): + """Convert fmt.Fprintf(os.Stderr, format, args...) to use console helper.""" + # Simple pattern: fmt.Fprintf(os.Stderr, "message\n") + match = re.search(r'fmt\.Fprintf\(os\.Stderr,\s*"([^"]+)\\n"\)', line) + if match: + msg = match.group(1) + # Skip if it has format verbs + if '%' in msg: + return line + helper = determine_helper(msg) + msg = clean_prefix(msg, helper) + replacement = f'fmt.Fprintln(os.Stderr, console.{helper}("{msg}"))' + return line.replace(match.group(0), replacement) + + return line + +def process_file(content): + """Process file content and return modified version.""" + lines = content.split('\n') + modified_lines = [] + needs_console_import = False + has_console_import = False + + # Check if console is already imported + in_import_block = False + for line in lines: + if 'import (' in line: + in_import_block = True + elif in_import_block and ')' in line: + in_import_block = False + + if 'github.com/github/gh-aw/pkg/console' in line: + has_console_import = True + break + + for i, line in enumerate(lines): + prev_lines = lines[max(0, i-3):i] + + # Skip if shouldn't convert + if should_skip_conversion(line, prev_lines): + modified_lines.append(line) + continue + + # Try conversions + original = line + if 'fmt.Fprintln(os.Stderr' in line: + line = convert_fprintln(line) + elif 'fmt.Fprintf(os.Stderr' in line: + line = convert_fprintf(line) + + if line != original: + needs_console_import = True + + modified_lines.append(line) + + # Add console import if needed and not present + if needs_console_import and not has_console_import: + # Find import block and add console + for i, line in enumerate(modified_lines): + if 'import (' in line: + # Find a good place to insert (after other pkg imports) + j = i + 1 + while j < len(modified_lines) and modified_lines[j].strip() and ')' not in modified_lines[j]: + j += 1 + # Insert before the closing paren or at the end of imports + insert_pos = j + for k in range(i+1, j): + if 'github.com/github/gh-aw/pkg/' in modified_lines[k]: + insert_pos = k + 1 + modified_lines.insert(insert_pos, '\t"github.com/github/gh-aw/pkg/console"') + break + + return '\n'.join(modified_lines) + +if __name__ == '__main__': + if len(sys.argv) != 2: + print("Usage: process_file.py ", file=sys.stderr) + sys.exit(1) + + filepath = sys.argv[1] + with open(filepath, 'r') as f: + content = f.read() + + modified = process_file(content) + + with open(filepath, 'w') as f: + f.write(modified) + + print(f"Processed: {filepath}")