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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion pkg/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var initLog = logger.New("cli:init")

// InitRepository initializes the repository for agentic workflows
func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine string, codespaceRepos []string, codespaceEnabled bool) error {
func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine string, codespaceRepos []string, codespaceEnabled bool, completions bool, rootCmd interface{}) error {
initLog.Print("Starting repository initialization for agentic workflows")

// Ensure we're in a git repository
Expand Down Expand Up @@ -172,6 +172,19 @@ func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine s
fmt.Fprintln(os.Stderr, "")
}

// Install shell completions if requested
if completions {
initLog.Print("Installing shell completions")
fmt.Fprintln(os.Stderr, "")

if err := InstallShellCompletion(verbose, rootCmd); err != nil {
initLog.Printf("Shell completion installation failed: %v", err)
// Don't fail init if completion installation has issues
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Shell completion installation encountered an issue: %v", err)))
}
fmt.Fprintln(os.Stderr, "")
}

initLog.Print("Repository initialization completed successfully")

// Display success message with next steps
Expand Down
14 changes: 11 additions & 3 deletions pkg/cli/init_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ With --codespaces flag:
With --campaign flag:
- Creates .github/agents/agentic-campaign-designer.agent.md with the Agentic Campaign Designer agent for gh-aw campaigns

With --completions flag:
- Automatically detects your shell (bash, zsh, fish, or PowerShell)
- Installs shell completion configuration for the CLI
- Provides instructions for enabling completions in your shell

After running this command, you can:
- Use GitHub Copilot Chat: type /agent and select create-agentic-workflow to create workflows interactively
- Use GitHub Copilot Chat: type /agent and select debug-agentic-workflow to debug existing workflows
Expand All @@ -60,7 +65,8 @@ Examples:
` + string(constants.CLIExtensionPrefix) + ` init --no-mcp
` + string(constants.CLIExtensionPrefix) + ` init --tokens --engine copilot
` + string(constants.CLIExtensionPrefix) + ` init --codespaces
` + string(constants.CLIExtensionPrefix) + ` init --codespaces repo1,repo2`,
` + string(constants.CLIExtensionPrefix) + ` init --codespaces repo1,repo2
` + string(constants.CLIExtensionPrefix) + ` init --completions`,
RunE: func(cmd *cobra.Command, args []string) error {
verbose, _ := cmd.Flags().GetBool("verbose")
mcpFlag, _ := cmd.Flags().GetBool("mcp")
Expand All @@ -70,6 +76,7 @@ Examples:
engine, _ := cmd.Flags().GetString("engine")
codespaceReposStr, _ := cmd.Flags().GetString("codespaces")
codespaceEnabled := cmd.Flags().Changed("codespaces")
completions, _ := cmd.Flags().GetBool("completions")

// Determine MCP state: default true, unless --no-mcp is specified
// --mcp flag is kept for backward compatibility (hidden from help)
Expand All @@ -92,8 +99,8 @@ Examples:
}
}

initCommandLog.Printf("Executing init command: verbose=%v, mcp=%v, campaign=%v, tokens=%v, engine=%v, codespaces=%v, codespaceEnabled=%v", verbose, mcp, campaign, tokens, engine, codespaceRepos, codespaceEnabled)
if err := InitRepository(verbose, mcp, campaign, tokens, engine, codespaceRepos, codespaceEnabled); err != nil {
initCommandLog.Printf("Executing init command: verbose=%v, mcp=%v, campaign=%v, tokens=%v, engine=%v, codespaces=%v, codespaceEnabled=%v, completions=%v", verbose, mcp, campaign, tokens, engine, codespaceRepos, codespaceEnabled, completions)
if err := InitRepository(verbose, mcp, campaign, tokens, engine, codespaceRepos, codespaceEnabled, completions, cmd.Root()); err != nil {
initCommandLog.Printf("Init command failed: %v", err)
return err
}
Expand All @@ -110,6 +117,7 @@ Examples:
cmd.Flags().String("codespaces", "", "Create devcontainer.json for GitHub Codespaces with agentic workflows support. Specify comma-separated repository names in the same organization (e.g., repo1,repo2), or use without value for current repo only")
// NoOptDefVal allows using --codespaces without a value (returns empty string when no value provided)
cmd.Flags().Lookup("codespaces").NoOptDefVal = " "
cmd.Flags().Bool("completions", false, "Install shell completion for the detected shell (bash, zsh, fish, or PowerShell)")

// Hide the deprecated --mcp flag from help (kept for backward compatibility)
_ = cmd.Flags().MarkHidden("mcp")
Expand Down
58 changes: 29 additions & 29 deletions pkg/cli/init_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ func TestInitRepositoryBasic(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test basic init with MCP enabled by default (mcp=true, noMcp=false behavior)
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() failed: %v", err)
t.Fatalf("InitRepository(, false, nil) failed: %v", err)
}

// Verify .gitattributes was created/updated
Expand Down Expand Up @@ -197,9 +197,9 @@ func TestInitRepositoryWithMCP(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test init with MCP explicitly enabled (same as default)
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with MCP failed: %v", err)
t.Fatalf("InitRepository(, false, nil) with MCP failed: %v", err)
}

// Verify .vscode/mcp.json was created
Expand Down Expand Up @@ -240,9 +240,9 @@ func TestInitRepositoryWithNoMCP(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test init with --no-mcp flag (mcp=false)
err = InitRepository(false, false, false, false, "", []string{}, false)
err = InitRepository(false, false, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with --no-mcp failed: %v", err)
t.Fatalf("InitRepository(, false, nil) with --no-mcp failed: %v", err)
}

// Verify .vscode/mcp.json was NOT created
Expand Down Expand Up @@ -288,9 +288,9 @@ func TestInitRepositoryWithMCPBackwardCompatibility(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test init with deprecated --mcp flag for backward compatibility (mcp=true)
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with deprecated --mcp flag failed: %v", err)
t.Fatalf("InitRepository(, false, nil) with deprecated --mcp flag failed: %v", err)
}

// Verify .vscode/mcp.json was created
Expand Down Expand Up @@ -331,9 +331,9 @@ func TestInitRepositoryVerbose(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test verbose mode with MCP enabled by default (should not error, just produce more output)
err = InitRepository(true, true, false, false, "", []string{}, false)
err = InitRepository(true, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() in verbose mode failed: %v", err)
t.Fatalf("InitRepository(, false, nil) in verbose mode failed: %v", err)
}

// Verify basic files were still created
Expand All @@ -358,12 +358,12 @@ func TestInitRepositoryNotInGitRepo(t *testing.T) {
}

// Don't initialize git repo - should fail for some operations
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)

// The function should handle this gracefully or return an error
// Based on the implementation, ensureGitAttributes requires git
if err == nil {
t.Log("InitRepository() succeeded despite not being in a git repo")
t.Log("InitRepository(, false, nil) succeeded despite not being in a git repo")
}
}

Expand Down Expand Up @@ -392,15 +392,15 @@ func TestInitRepositoryIdempotent(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Run init twice with MCP enabled by default
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("First InitRepository() failed: %v", err)
t.Fatalf("First InitRepository(, false, nil) failed: %v", err)
}

// Second run should be idempotent
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("Second InitRepository() failed: %v", err)
t.Fatalf("Second InitRepository(, false, nil) failed: %v", err)
}

// Verify .gitattributes still correct
Expand Down Expand Up @@ -443,14 +443,14 @@ func TestInitRepositoryWithMCPIdempotent(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Run init with MCP twice
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("First InitRepository() with MCP failed: %v", err)
t.Fatalf("First InitRepository(, false, nil) with MCP failed: %v", err)
}

err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("Second InitRepository() with MCP failed: %v", err)
t.Fatalf("Second InitRepository(, false, nil) with MCP failed: %v", err)
}

// Verify files still exist and are correct
Expand Down Expand Up @@ -490,9 +490,9 @@ func TestInitRepositoryCreatesDirectories(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Run init with MCP
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() failed: %v", err)
t.Fatalf("InitRepository(, false, nil) failed: %v", err)
}

// Verify directory structure
Expand Down Expand Up @@ -558,7 +558,7 @@ func TestInitRepositoryErrorHandling(t *testing.T) {
}

// Test init without git repo (with MCP enabled by default)
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)

// Should handle error gracefully or return error
// The actual behavior depends on implementation
Expand Down Expand Up @@ -601,9 +601,9 @@ func TestInitRepositoryWithExistingFiles(t *testing.T) {
}

// Run init with MCP enabled by default
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() failed: %v", err)
t.Fatalf("InitRepository(, false, nil) failed: %v", err)
}

// Verify existing content is preserved and new entry is added
Expand Down Expand Up @@ -651,9 +651,9 @@ func TestInitRepositoryWithCodespace(t *testing.T) {

// Test init with --codespaces flag (with MCP enabled by default and additional repos)
additionalRepos := []string{"org/repo1", "owner/repo2"}
err = InitRepository(false, true, false, false, "", additionalRepos, true)
err = InitRepository(false, true, false, false, "", additionalRepos, true, false, nil)
if err != nil {
t.Fatalf("InitRepository() with codespaces failed: %v", err)
t.Fatalf("InitRepository(, false, nil) with codespaces failed: %v", err)
}

// Verify .devcontainer/devcontainer.json was created at default location
Expand Down Expand Up @@ -716,9 +716,9 @@ func TestInitCommandWithCodespacesNoArgs(t *testing.T) {
exec.Command("git", "config", "user.email", "test@example.com").Run()

// Test init with --codespaces flag (no additional repos, MCP enabled by default)
err = InitRepository(false, true, false, false, "", []string{}, true)
err = InitRepository(false, true, false, false, "", []string{}, true, false, nil)
if err != nil {
t.Fatalf("InitRepository() with codespaces (no args) failed: %v", err)
t.Fatalf("InitRepository(, false, nil) with codespaces (no args) failed: %v", err)
}

// Verify .devcontainer/devcontainer.json was created at default location
Expand Down
12 changes: 6 additions & 6 deletions pkg/cli/init_mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ func TestInitRepository_WithMCP(t *testing.T) {
}

// Call the function with MCP flag (no campaign agent)
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with MCP returned error: %v", err)
t.Fatalf("InitRepository(, false, nil) with MCP returned error: %v", err)
}

// Verify standard files were created
Expand Down Expand Up @@ -133,15 +133,15 @@ func TestInitRepository_MCP_Idempotent(t *testing.T) {
}

// Call the function first time with MCP
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with MCP returned error on first call: %v", err)
t.Fatalf("InitRepository(, false, nil) with MCP returned error on first call: %v", err)
}

// Call the function second time with MCP
err = InitRepository(false, true, false, false, "", []string{}, false)
err = InitRepository(false, true, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() with MCP returned error on second call: %v", err)
t.Fatalf("InitRepository(, false, nil) with MCP returned error on second call: %v", err)
}

// Verify files still exist
Expand Down
22 changes: 11 additions & 11 deletions pkg/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ func TestInitRepository(t *testing.T) {
}

// Call the function (no MCP or campaign)
err = InitRepository(false, false, false, false, "", []string{}, false)
err = InitRepository(false, false, false, false, "", []string{}, false, false, nil)

// Check error expectation
if tt.wantError {
if err == nil {
t.Errorf("InitRepository() expected error, got nil")
t.Errorf("InitRepository(, false, nil) expected error, got nil")
}
return
}

if err != nil {
t.Fatalf("InitRepository() returned unexpected error: %v", err)
t.Fatalf("InitRepository(, false, nil) returned unexpected error: %v", err)
}

// Verify .gitattributes was created
Expand Down Expand Up @@ -149,15 +149,15 @@ func TestInitRepository_Idempotent(t *testing.T) {
}

// Call the function first time
err = InitRepository(false, false, false, false, "", []string{}, false)
err = InitRepository(false, false, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() returned error on first call: %v", err)
t.Fatalf("InitRepository(, false, nil) returned error on first call: %v", err)
}

// Call the function second time
err = InitRepository(false, false, false, false, "", []string{}, false)
err = InitRepository(false, false, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() returned error on second call: %v", err)
t.Fatalf("InitRepository(, false, nil) returned error on second call: %v", err)
}

// Verify files still exist and are correct
Expand Down Expand Up @@ -211,9 +211,9 @@ func TestInitRepository_Verbose(t *testing.T) {
}

// Call the function with verbose=true (should not error)
err = InitRepository(true, false, false, false, "", []string{}, false)
err = InitRepository(true, false, false, false, "", []string{}, false, false, nil)
if err != nil {
t.Fatalf("InitRepository() returned error with verbose=true: %v", err)
t.Fatalf("InitRepository(, false, nil) returned error with verbose=true: %v", err)
}

// Verify files were created
Expand Down Expand Up @@ -245,8 +245,8 @@ func TestInitRepository_WithCampaignDesignerAgent(t *testing.T) {
}

// Call InitRepository with campaign flag enabled
if err := InitRepository(false, false, true, false, "", []string{}, false); err != nil {
t.Fatalf("InitRepository() with campaign flag returned error: %v", err)
if err := InitRepository(false, false, true, false, "", []string{}, false, false, nil); err != nil {
t.Fatalf("InitRepository(, false, nil) with campaign flag returned error: %v", err)
}

agentPath := filepath.Join(tempDir, ".github", "agents", "agentic-campaign-designer.agent.md")
Expand Down
Loading