diff --git a/docs/src/content/docs/setup/cli.md b/docs/src/content/docs/setup/cli.md index dd4e5a2aa2f..f03c66cd951 100644 --- a/docs/src/content/docs/setup/cli.md +++ b/docs/src/content/docs/setup/cli.md @@ -177,7 +177,7 @@ gh aw new my-workflow --force # Overwrite if exists gh aw new my-workflow --engine claude # Inject engine into frontmatter ``` -**Options:** `--force`, `--engine/-e` +**Options:** `--force`, `--engine/-e`, `--interactive/-i` When `--engine` is specified, the engine is injected into the generated frontmatter template: diff --git a/pkg/cli/audit_diff_command.go b/pkg/cli/audit_diff_command.go index 1e8f10031fd..097fa97e00c 100644 --- a/pkg/cli/audit_diff_command.go +++ b/pkg/cli/audit_diff_command.go @@ -16,7 +16,7 @@ import ( func NewAuditDiffSubcommand() *cobra.Command { cmd := &cobra.Command{ Use: "diff ", - Short: "Compare workflow run behavior across two workflow runs", + Short: "Compare behavior across two workflow runs", Long: `Compare workflow run behavior between two workflow runs to detect policy regressions, new unauthorized domains, behavioral drift, and changes in MCP tool usage or run metrics. diff --git a/pkg/cli/init_command.go b/pkg/cli/init_command.go index bc10034cdba..275e5e8eeb2 100644 --- a/pkg/cli/init_command.go +++ b/pkg/cli/init_command.go @@ -58,7 +58,7 @@ After running this command, you can: - Add workflows from the catalog with: ` + string(constants.CLIExtensionPrefix) + ` add - Create new workflows from scratch with: ` + string(constants.CLIExtensionPrefix) + ` new -To create, update or debug automated agentic actions using github, playwright, and other tools, load the .github/agents/agentic-workflows.agent.md (applies to .github/workflows/*.md) +To create, update or debug automated agentic actions using GitHub, playwright, and other tools, load the .github/agents/agentic-workflows.agent.md (applies to .github/workflows/*.md) Examples: ` + string(constants.CLIExtensionPrefix) + ` init # Interactive mode diff --git a/pkg/cli/logs_command.go b/pkg/cli/logs_command.go index 9117b308047..e8564953d6e 100644 --- a/pkg/cli/logs_command.go +++ b/pkg/cli/logs_command.go @@ -196,7 +196,7 @@ Examples: logsCmd.Flags().Bool("filtered-integrity", false, "Filter to runs with DIFC (data integrity flow control) integrity-filtered items in the gateway logs") logsCmd.Flags().Bool("parse", false, "Run JavaScript parsers on agent logs and firewall logs, writing Markdown to log.md and firewall.md") addJSONFlag(logsCmd) - logsCmd.Flags().Int("timeout", 0, "Download timeout in seconds (0 = no timeout)") + logsCmd.Flags().Int("timeout", 0, "Download timeout in minutes (0 = no timeout)") logsCmd.Flags().String("summary-file", "summary.json", "Path to write the summary JSON file relative to output directory (use empty string to disable)") logsCmd.MarkFlagsMutuallyExclusive("firewall", "no-firewall") diff --git a/pkg/cli/logs_orchestrator.go b/pkg/cli/logs_orchestrator.go index 4363ce86ad8..55c9d801134 100644 --- a/pkg/cli/logs_orchestrator.go +++ b/pkg/cli/logs_orchestrator.go @@ -72,7 +72,7 @@ func DownloadWorkflowLogs(ctx context.Context, workflowName string, count int, s if timeout > 0 { startTime = time.Now() if verbose { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Timeout set to %d seconds", timeout))) + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Timeout set to %d minutes", timeout))) } } @@ -98,7 +98,7 @@ func DownloadWorkflowLogs(ctx context.Context, workflowName string, count int, s // Check timeout if specified if timeout > 0 { elapsed := time.Since(startTime).Seconds() - if elapsed >= float64(timeout) { + if elapsed >= float64(timeout)*60 { timeoutReached = true if verbose { fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Timeout reached after %.1f seconds, stopping download", elapsed))) diff --git a/pkg/cli/logs_timeout_test.go b/pkg/cli/logs_timeout_test.go index 3c0aa03a690..f78fc358b1c 100644 --- a/pkg/cli/logs_timeout_test.go +++ b/pkg/cli/logs_timeout_test.go @@ -13,25 +13,25 @@ func TestTimeoutFlagParsing(t *testing.T) { name string timeout int expectTimeout bool - expectedSeconds int + expectedMinutes int }{ { name: "no timeout specified", timeout: 0, expectTimeout: false, - expectedSeconds: 0, + expectedMinutes: 0, }, { - name: "timeout of 50 seconds", - timeout: 50, + name: "timeout of 5 minutes", + timeout: 5, expectTimeout: true, - expectedSeconds: 50, + expectedMinutes: 5, }, { - name: "timeout of 120 seconds", - timeout: 120, + name: "timeout of 30 minutes", + timeout: 30, expectTimeout: true, - expectedSeconds: 120, + expectedMinutes: 30, }, } @@ -44,8 +44,8 @@ func TestTimeoutFlagParsing(t *testing.T) { if !tt.expectTimeout && tt.timeout != 0 { t.Errorf("Expected no timeout but got %d", tt.timeout) } - if tt.expectTimeout && tt.timeout != tt.expectedSeconds { - t.Errorf("Expected timeout of %d seconds but got %d", tt.expectedSeconds, tt.timeout) + if tt.expectTimeout && tt.timeout != tt.expectedMinutes { + t.Errorf("Expected timeout of %d minutes but got %d", tt.expectedMinutes, tt.timeout) } }) } @@ -62,41 +62,47 @@ func TestTimeoutLogic(t *testing.T) { { name: "no timeout set", timeout: 0, - elapsed: 100 * time.Second, + elapsed: 100 * time.Minute, shouldTimeout: false, }, { name: "timeout not reached", timeout: 60, - elapsed: 30 * time.Second, + elapsed: 30 * time.Minute, + shouldTimeout: false, + }, + { + name: "just under boundary", + timeout: 1, + elapsed: 59 * time.Second, shouldTimeout: false, }, { name: "timeout exactly reached", - timeout: 50, - elapsed: 50 * time.Second, + timeout: 1, + elapsed: 60 * time.Second, shouldTimeout: true, }, { name: "timeout exceeded", - timeout: 30, - elapsed: 45 * time.Second, + timeout: 1, + elapsed: 90 * time.Second, shouldTimeout: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Simulate the timeout check logic + // Simulate the timeout check logic (timeout is in minutes, elapsed in seconds) var timeoutReached bool if tt.timeout > 0 { - if tt.elapsed.Seconds() >= float64(tt.timeout) { + if tt.elapsed.Seconds() >= float64(tt.timeout)*60 { timeoutReached = true } } if timeoutReached != tt.shouldTimeout { - t.Errorf("Expected timeout reached=%v but got %v (timeout=%d, elapsed=%.1fs)", + t.Errorf("Expected timeout reached=%v but got %v (timeout=%d min, elapsed=%.1fs)", tt.shouldTimeout, timeoutReached, tt.timeout, tt.elapsed.Seconds()) } }) @@ -105,23 +111,23 @@ func TestTimeoutLogic(t *testing.T) { // TestMCPServerDefaultTimeout tests that the MCP server sets a default timeout func TestMCPServerDefaultTimeout(t *testing.T) { - // Test that when no timeout is specified, MCP server uses 50 seconds + // Test that when no timeout is specified, MCP server uses 1 minute timeoutValue := 0 if timeoutValue == 0 { - timeoutValue = 50 + timeoutValue = 1 } - if timeoutValue != 50 { - t.Errorf("Expected MCP server default timeout to be 50 but got %d", timeoutValue) + if timeoutValue != 1 { + t.Errorf("Expected MCP server default timeout to be 1 but got %d", timeoutValue) } // Test that explicit timeout overrides the default - timeoutValue = 120 + timeoutValue = 5 if timeoutValue == 0 { - timeoutValue = 50 + timeoutValue = 1 } - if timeoutValue != 120 { + if timeoutValue != 5 { t.Errorf("Expected explicit timeout to be preserved but got %d", timeoutValue) } } diff --git a/pkg/cli/mcp_tools_privileged.go b/pkg/cli/mcp_tools_privileged.go index 0a3de14732f..efb5294e172 100644 --- a/pkg/cli/mcp_tools_privileged.go +++ b/pkg/cli/mcp_tools_privileged.go @@ -28,7 +28,7 @@ func registerLogsTool(server *mcp.Server, execCmd execCmdFunc, actor string, val Branch string `json:"branch,omitempty" jsonschema:"Filter runs by branch name"` AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"` BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"` - Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in seconds to spend downloading logs (default: 50 for MCP server)"` + Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in minutes to spend downloading logs (default: 1 for MCP server)"` MaxTokens int `json:"max_tokens,omitempty" jsonschema:"Maximum number of tokens in output before triggering guardrail (default: 12000)"` } @@ -42,7 +42,7 @@ func registerLogsTool(server *mcp.Server, execCmd execCmdFunc, actor string, val if err := AddSchemaDefault(logsSchema, "count", 100); err != nil { mcpLog.Printf("Failed to add default for count: %v", err) } - if err := AddSchemaDefault(logsSchema, "timeout", 50); err != nil { + if err := AddSchemaDefault(logsSchema, "timeout", 1); err != nil { mcpLog.Printf("Failed to add default for timeout: %v", err) } if err := AddSchemaDefault(logsSchema, "max_tokens", 12000); err != nil { @@ -150,10 +150,10 @@ return a schema description instead of the full output. Adjust the 'max_tokens' cmdArgs = append(cmdArgs, "--before-run-id", strconv.FormatInt(args.BeforeRunID, 10)) } - // Set timeout to 50 seconds for MCP server if not explicitly specified + // Set timeout to 1 minute for MCP server if not explicitly specified timeoutValue := args.Timeout if timeoutValue == 0 { - timeoutValue = 50 + timeoutValue = 1 } cmdArgs = append(cmdArgs, "--timeout", strconv.Itoa(timeoutValue)) diff --git a/pkg/cli/project_command.go b/pkg/cli/project_command.go index 81b54dea8a9..6a17efa20da 100644 --- a/pkg/cli/project_command.go +++ b/pkg/cli/project_command.go @@ -65,7 +65,7 @@ The project can optionally be linked to a specific repository. Authentication Requirements: The default GITHUB_TOKEN cannot create projects. You must use additional authentication. - See https://github.github.com/gh-aw/reference/auth-projects/. + See https://github.github.io/gh-aw/reference/auth-projects/. Project Setup: Use --with-project-setup to automatically create: diff --git a/pkg/cli/tokens_bootstrap.go b/pkg/cli/tokens_bootstrap.go index 248a3f72607..7c3252e9047 100644 --- a/pkg/cli/tokens_bootstrap.go +++ b/pkg/cli/tokens_bootstrap.go @@ -33,7 +33,7 @@ This command: Only required secrets are prompted for. Optional secrets are not shown. For full details, including precedence rules, see the GitHub Tokens -reference in the documentation. +reference at https://github.github.io/gh-aw/reference/tokens/. Examples: gh aw secrets bootstrap # Check and set up all required secrets diff --git a/pkg/cli/validate_command.go b/pkg/cli/validate_command.go index b037c513812..00e39ffdc1f 100644 --- a/pkg/cli/validate_command.go +++ b/pkg/cli/validate_command.go @@ -76,10 +76,10 @@ Examples: cmd.Flags().StringP("engine", "e", "", "Override AI engine (claude, codex, copilot, custom)") cmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)") - cmd.Flags().Bool("strict", false, "Enforce strict mode validation for all workflows") + cmd.Flags().Bool("strict", false, "Override frontmatter to enforce strict mode validation for all workflows (enforces action pinning, network config, safe-outputs, refuses write permissions and deprecated fields). Note: Workflows default to strict mode unless frontmatter sets strict: false") cmd.Flags().BoolP("json", "j", false, "Output results in JSON format") cmd.Flags().Bool("fail-fast", false, "Stop at the first validation error instead of collecting all errors") - cmd.Flags().Bool("stats", false, "Display statistics table sorted by file size") + cmd.Flags().Bool("stats", false, "Display statistics table sorted by file size (shows jobs, steps, scripts, and shells)") cmd.Flags().Bool("no-check-update", false, "Skip checking for gh-aw updates") // Register completions