From 2b868de5382aa31c2b61b3f0608bb0036a145ae2 Mon Sep 17 00:00:00 2001 From: "doneill@juniper.net" Date: Tue, 17 Feb 2026 19:21:43 +0000 Subject: [PATCH 1/2] feat: allow configurable command name (hardcoded "mcp" prevents renaming) Add CommandName field to Config (defaulting to "mcp" for backwards compatibility) and thread it through: - root.go: use config.commandName() for the command's Use field - config.go: cmdFilter uses configured name instead of hardcoded "mcp" - utils.go: GetCmdPath accepts commandName parameter - claude/cursor/vscode enable commands: pass commandName to GetCmdPath - test/tools.go: add GetToolsForCommand/ToolsForCommand for custom names, refactor Tools to delegate to ToolsForCommand Closes #39 --- README.md | 16 ++++++- config.go | 26 ++++++++--- config_test.go | 43 +++++++++++++++++- docs/config.md | 2 +- examples/make/go.mod | 2 +- internal/cfgmgr/cmd/claude/enable.go | 15 ++++--- internal/cfgmgr/cmd/claude/root.go | 6 ++- internal/cfgmgr/cmd/cursor/enable.go | 18 ++++---- internal/cfgmgr/cmd/cursor/root.go | 6 ++- internal/cfgmgr/cmd/vscode/enable.go | 18 ++++---- internal/cfgmgr/cmd/vscode/root.go | 6 ++- internal/cfgmgr/manager/utils.go | 21 ++++----- internal/cfgmgr/manager/utils_test.go | 37 ++++++++++++++- root.go | 9 ++-- test/tools.go | 30 +++++++++---- test/tools_test.go | 65 +++++++++++++++++++++++++++ 16 files changed, 259 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index b121d08..30ee7ff 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Expose your MCP server over HTTP for remote access: ## Commands -The `ophis.Command(nil)` adds these subcommands to your CLI: +The `ophis.Command(nil)` adds these subcommands to your CLI (the default command name is `mcp`, configurable via `Config.CommandName`): ``` mcp @@ -104,6 +104,20 @@ config := &ophis.Config{ rootCmd.AddCommand(ophis.Command(config)) ``` +### Custom Command Name + +By default the ophis command is named `mcp`. If your CLI already uses `mcp` for something else, set `CommandName` to avoid the collision: + +```go +config := &ophis.Config{ + CommandName: "agent", +} + +rootCmd.AddCommand(ophis.Command(config)) +``` + +The command tree, editor config (`enable`/`disable`), and internal filters all use the configured name automatically. + See [docs/config.md](docs/config.md) for detailed configuration options. ## How It Works diff --git a/config.go b/config.go index 6f6841f..e85f0d0 100644 --- a/config.go +++ b/config.go @@ -15,13 +15,19 @@ import ( // Config customizes MCP server behavior and command-to-tool conversion. type Config struct { + // CommandName is the Use name for the top-level command returned by Command(). + // It is also used by GetCmdPath to locate the ophis command in the Cobra tree + // and by cmdFilter to exclude ophis subcommands from tool exposure. + // Default: "mcp". + CommandName string + // Selectors defines rules for converting commands to MCP tools. // Each selector specifies which commands to match and which flags to include. // // Basic safety filters are always applied first: // - Hidden/deprecated commands and flags are excluded // - Non-runnable commands are excluded - // - Built-in commands (mcp, help, completion) are excluded + // - Built-in commands (the ophis command, help, completion) are excluded // // Then selectors are evaluated in order for each command: // 1. The first selector whose CmdSelector returns true is used @@ -54,6 +60,14 @@ type Config struct { toolNamePrefix string // resolved prefix (either ToolNamePrefix or root command name) } +// commandName returns the configured CommandName, defaulting to "mcp". +func (c *Config) commandName() string { + if c != nil && c.CommandName != "" { + return c.CommandName + } + return "mcp" +} + func (c *Config) serveStdio(cmd *cobra.Command) error { if c.Transport == nil { c.Transport = &mcp.StdioTransport{} @@ -136,7 +150,7 @@ func (c *Config) registerToolsRecursive(cmd *cobra.Command) { } // apply basic filters - if cmdFilter(cmd) { + if c.cmdFilter(cmd) { return } @@ -161,8 +175,10 @@ func (c *Config) registerToolsRecursive(cmd *cobra.Command) { } } -// cmdFilter returns true if cmd should be filtered out -func cmdFilter(cmd *cobra.Command) bool { +// cmdFilter returns true if cmd should be filtered out. +// It uses the configured CommandName (defaulting to "mcp") to exclude +// the ophis command group from being exposed as MCP tools. +func (c *Config) cmdFilter(cmd *cobra.Command) bool { if cmd.Hidden || cmd.Deprecated != "" { return true } @@ -171,5 +187,5 @@ func cmdFilter(cmd *cobra.Command) bool { return true } - return AllowCmdsContaining("mcp", "help", "completion")(cmd) + return AllowCmdsContaining(c.commandName(), "help", "completion")(cmd) } diff --git a/config_test.go b/config_test.go index b60d059..a0987f4 100644 --- a/config_test.go +++ b/config_test.go @@ -58,8 +58,49 @@ func TestCmdFilter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := cmdFilter(tt.cmd) + // Default config uses "mcp" as command name. + c := &Config{} + result := c.cmdFilter(tt.cmd) assert.Equal(t, tt.expected, result) }) } } + +func TestCmdFilterCustomCommandName(t *testing.T) { + c := &Config{CommandName: "agent"} + + // "agent" should be filtered out (it is the ophis command name). + agentCmd := &cobra.Command{ + Use: "agent", + Run: func(_ *cobra.Command, _ []string) {}, + } + assert.True(t, c.cmdFilter(agentCmd)) + + // "mcp" should NOT be filtered out (it is no longer the ophis command name). + mcpCmd := &cobra.Command{ + Use: "mcp", + Run: func(_ *cobra.Command, _ []string) {}, + } + assert.False(t, c.cmdFilter(mcpCmd)) + + // A normal command should pass through. + normalCmd := &cobra.Command{ + Use: "status", + Run: func(_ *cobra.Command, _ []string) {}, + } + assert.False(t, c.cmdFilter(normalCmd)) +} + +func TestCommandNameDefault(t *testing.T) { + // Empty CommandName defaults to "mcp". + c := &Config{} + assert.Equal(t, "mcp", c.commandName()) + + // Explicit CommandName is used as-is. + c = &Config{CommandName: "agent"} + assert.Equal(t, "agent", c.commandName()) + + // Nil receiver defaults to "mcp" (Command(nil) is a valid call). + var nilConfig *Config + assert.Equal(t, "mcp", nilConfig.commandName()) +} diff --git a/docs/config.md b/docs/config.md index a613423..85e1b7a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -16,7 +16,7 @@ Always filtered regardless of configuration: - Hidden and deprecated commands/flags - Non-runnable commands (no Run/RunE) -- Built-in commands (mcp, help, completion) +- Built-in commands (the ophis command, help, completion). The ophis command name defaults to "mcp" but can be changed via Config.CommandName ## Examples diff --git a/examples/make/go.mod b/examples/make/go.mod index 7b87728..d52f8ed 100644 --- a/examples/make/go.mod +++ b/examples/make/go.mod @@ -5,7 +5,7 @@ go 1.24.6 replace github.com/njayp/ophis => ../../ require ( - github.com/njayp/ophis v1.1.1 + github.com/njayp/ophis v1.1.2 github.com/spf13/cobra v1.10.2 ) diff --git a/internal/cfgmgr/cmd/claude/enable.go b/internal/cfgmgr/cmd/claude/enable.go index 05809fa..87f64d8 100644 --- a/internal/cfgmgr/cmd/claude/enable.go +++ b/internal/cfgmgr/cmd/claude/enable.go @@ -10,15 +10,16 @@ import ( ) type enableFlags struct { - configPath string - logLevel string - serverName string - env map[string]string + commandName string + configPath string + logLevel string + serverName string + env map[string]string } // enableCommand creates a Cobra command for adding an MCP server to Claude Desktop. -func enableCommand() *cobra.Command { - f := &enableFlags{} +func enableCommand(commandName string) *cobra.Command { + f := &enableFlags{commandName: commandName} cmd := &cobra.Command{ Use: "enable", Short: "Add server to Claude config", @@ -45,7 +46,7 @@ func (f *enableFlags) run(cmd *cobra.Command) error { } // Build server configuration - mcpPath, err := manager.GetCmdPath(cmd) + mcpPath, err := manager.GetCmdPath(cmd, f.commandName) if err != nil { return fmt.Errorf("failed to determine MCP command path: %w", err) } diff --git a/internal/cfgmgr/cmd/claude/root.go b/internal/cfgmgr/cmd/claude/root.go index 137a23c..f332bf6 100644 --- a/internal/cfgmgr/cmd/claude/root.go +++ b/internal/cfgmgr/cmd/claude/root.go @@ -5,7 +5,9 @@ import ( ) // Command creates a new Cobra command for managing Claude Desktop MCP servers. -func Command() *cobra.Command { +// commandName is the name of the ophis root command (e.g. "mcp" or "agent"), +// used by enable to build the correct command path for editor config files. +func Command(commandName string) *cobra.Command { cmd := &cobra.Command{ Use: "claude", Short: "Manage Claude Desktop MCP servers", @@ -13,6 +15,6 @@ func Command() *cobra.Command { } // Add subcommands - cmd.AddCommand(enableCommand(), disableCommand(), listCommand()) + cmd.AddCommand(enableCommand(commandName), disableCommand(), listCommand()) return cmd } diff --git a/internal/cfgmgr/cmd/cursor/enable.go b/internal/cfgmgr/cmd/cursor/enable.go index 205f15e..7268cde 100644 --- a/internal/cfgmgr/cmd/cursor/enable.go +++ b/internal/cfgmgr/cmd/cursor/enable.go @@ -10,16 +10,18 @@ import ( ) type enableFlags struct { - configPath string - logLevel string - serverName string - workspace bool - env map[string]string + commandName string + configPath string + logLevel string + serverName string + workspace bool + env map[string]string } // enableCommand creates a Cobra command for adding an MCP server to Cursor. -func enableCommand() *cobra.Command { - f := &enableFlags{} +// commandName is the name of the ophis root command in the Cobra tree. +func enableCommand(commandName string) *cobra.Command { + f := &enableFlags{commandName: commandName} cmd := &cobra.Command{ Use: "enable", Short: "Add server to Cursor config", @@ -48,7 +50,7 @@ func (f *enableFlags) run(cmd *cobra.Command) error { } // Build server configuration - mcpPath, err := manager.GetCmdPath(cmd) + mcpPath, err := manager.GetCmdPath(cmd, f.commandName) if err != nil { return fmt.Errorf("failed to determine MCP command path: %w", err) } diff --git a/internal/cfgmgr/cmd/cursor/root.go b/internal/cfgmgr/cmd/cursor/root.go index 16512fb..eb78584 100644 --- a/internal/cfgmgr/cmd/cursor/root.go +++ b/internal/cfgmgr/cmd/cursor/root.go @@ -5,7 +5,9 @@ import ( ) // Command creates a new Cobra command for managing Cursor MCP servers. -func Command() *cobra.Command { +// commandName is the Use name of the ophis root command (e.g. "mcp" or "agent"), +// threaded through to enableCommand so that GetCmdPath can locate it. +func Command(commandName string) *cobra.Command { cmd := &cobra.Command{ Use: "cursor", Short: "Manage Cursor MCP servers", @@ -13,6 +15,6 @@ func Command() *cobra.Command { } // Add subcommands - cmd.AddCommand(enableCommand(), disableCommand(), listCommand()) + cmd.AddCommand(enableCommand(commandName), disableCommand(), listCommand()) return cmd } diff --git a/internal/cfgmgr/cmd/vscode/enable.go b/internal/cfgmgr/cmd/vscode/enable.go index 7cd7b97..96553e0 100644 --- a/internal/cfgmgr/cmd/vscode/enable.go +++ b/internal/cfgmgr/cmd/vscode/enable.go @@ -10,16 +10,18 @@ import ( ) type enableFlags struct { - configPath string - logLevel string - serverName string - workspace bool - env map[string]string + commandName string + configPath string + logLevel string + serverName string + workspace bool + env map[string]string } // enableCommand creates a Cobra command for adding an MCP server to VSCode. -func enableCommand() *cobra.Command { - f := &enableFlags{} +// commandName is the Use name of the ophis root command (e.g. "mcp" or "agent"). +func enableCommand(commandName string) *cobra.Command { + f := &enableFlags{commandName: commandName} cmd := &cobra.Command{ Use: "enable", Short: "Add server to VSCode config", @@ -48,7 +50,7 @@ func (f *enableFlags) run(cmd *cobra.Command) error { } // Build server configuration - mcpPath, err := manager.GetCmdPath(cmd) + mcpPath, err := manager.GetCmdPath(cmd, f.commandName) if err != nil { return fmt.Errorf("failed to determine MCP command path: %w", err) } diff --git a/internal/cfgmgr/cmd/vscode/root.go b/internal/cfgmgr/cmd/vscode/root.go index 21c300f..a63f557 100644 --- a/internal/cfgmgr/cmd/vscode/root.go +++ b/internal/cfgmgr/cmd/vscode/root.go @@ -5,7 +5,9 @@ import ( ) // Command creates a new Cobra command for managing VSCode MCP servers. -func Command() *cobra.Command { +// commandName is the name of the ophis command in the Cobra tree (e.g. "mcp" or "agent"), +// used by enable to build the correct command path for editor config files. +func Command(commandName string) *cobra.Command { cmd := &cobra.Command{ Use: "vscode", Short: "Manage VSCode MCP servers", @@ -13,6 +15,6 @@ func Command() *cobra.Command { } // Add subcommands - cmd.AddCommand(enableCommand(), disableCommand(), listCommand()) + cmd.AddCommand(enableCommand(commandName), disableCommand(), listCommand()) return cmd } diff --git a/internal/cfgmgr/manager/utils.go b/internal/cfgmgr/manager/utils.go index 6c77181..5a1a606 100644 --- a/internal/cfgmgr/manager/utils.go +++ b/internal/cfgmgr/manager/utils.go @@ -19,24 +19,25 @@ func DeriveServerName(executablePath string) string { return serverName } -// GetCmdPath builds the command path to the MCP command. -// It returns the slice of command names from after the root command up to and including "mcp". +// GetCmdPath builds the command path to the ophis command. +// It returns the slice of command names from after the root command up to and +// including the command identified by commandName. // -// Example: for command path "myapp alpha mcp start", returns ["alpha", "mcp"]. +// Example: for command path "myapp agent claude enable" with commandName "agent", +// returns ["agent"]. // // The returned slice can be used as arguments when invoking the executable. -// Returns an error if "mcp" is not found in the command path. -func GetCmdPath(cmd *cobra.Command) ([]string, error) { +// Returns an error if commandName is not found in the command path. +func GetCmdPath(cmd *cobra.Command, commandName string) ([]string, error) { path := cmd.CommandPath() args := strings.Fields(path) - // Find the index of "mcp" in the command path - name := "mcp" - index := slices.Index(args, name) + // Find the index of the ophis command in the command path + index := slices.Index(args, commandName) if index == -1 { - return nil, fmt.Errorf("command %q not found in path %q", name, path) + return nil, fmt.Errorf("command %q not found in path %q", commandName, path) } - // Return the slice from after the root command to the MCP command + // Return the slice from after the root command to the ophis command return args[1 : index+1], nil } diff --git a/internal/cfgmgr/manager/utils_test.go b/internal/cfgmgr/manager/utils_test.go index df61f4a..04a7fc3 100644 --- a/internal/cfgmgr/manager/utils_test.go +++ b/internal/cfgmgr/manager/utils_test.go @@ -54,36 +54,71 @@ func TestGetCmdPath(t *testing.T) { tests := []struct { name string cmdPath string + commandName string expectedPath []string expectError bool }{ { name: "root mcp command", cmdPath: "myapp mcp", + commandName: "mcp", expectedPath: []string{"mcp"}, expectError: false, }, { name: "nested mcp command", cmdPath: "myapp alpha mcp", + commandName: "mcp", expectedPath: []string{"alpha", "mcp"}, expectError: false, }, { name: "deeply nested mcp command", cmdPath: "myapp alpha beta mcp start", + commandName: "mcp", expectedPath: []string{"alpha", "beta", "mcp"}, expectError: false, }, { name: "no mcp in path", cmdPath: "myapp start", + commandName: "mcp", expectedPath: nil, expectError: true, }, { name: "mcp as root returns empty slice", cmdPath: "mcp start", + commandName: "mcp", + expectedPath: []string{}, + expectError: false, + }, + // Custom command name tests + { + name: "renamed to agent", + cmdPath: "devenv agent claude enable", + commandName: "agent", + expectedPath: []string{"agent"}, + expectError: false, + }, + { + name: "renamed to agent nested", + cmdPath: "myapp sub agent vscode enable", + commandName: "agent", + expectedPath: []string{"sub", "agent"}, + expectError: false, + }, + { + name: "renamed but searching for mcp fails", + cmdPath: "devenv agent claude enable", + commandName: "mcp", + expectedPath: nil, + expectError: true, + }, + { + name: "custom name as root returns empty slice", + cmdPath: "agent start", + commandName: "agent", expectedPath: []string{}, expectError: false, }, @@ -94,7 +129,7 @@ func TestGetCmdPath(t *testing.T) { // Build a command tree that matches the path cmd := buildMockCommandTree(tt.cmdPath) - result, err := GetCmdPath(cmd) + result, err := GetCmdPath(cmd, tt.commandName) if tt.expectError { require.Error(t, err) diff --git a/root.go b/root.go index c23e56d..9d2113e 100644 --- a/root.go +++ b/root.go @@ -10,8 +10,9 @@ import ( // Command creates MCP server management commands for a Cobra CLI. // Pass nil for default configuration or provide a Config for customization. func Command(config *Config) *cobra.Command { + name := config.commandName() cmd := &cobra.Command{ - Use: "mcp", + Use: name, Short: "MCP server management", Long: `Manage MCP servers for AI assistants and code editors`, } @@ -21,9 +22,9 @@ func Command(config *Config) *cobra.Command { startCommand(config), toolCommand(config), streamCommand(config), - claude.Command(), - vscode.Command(), - cursor.Command(), + claude.Command(name), + vscode.Command(name), + cursor.Command(name), ) return cmd } diff --git a/test/tools.go b/test/tools.go index 2749260..9369774 100644 --- a/test/tools.go +++ b/test/tools.go @@ -12,10 +12,10 @@ import ( "github.com/spf13/cobra" ) -// GetTools runs `mcp tools` command and returns the parsed list of tools -// It fails if ophis.Command is not a root level subcommand -func GetTools(t *testing.T, cmd *cobra.Command) []*mcp.Tool { - cmd.SetArgs([]string{"mcp", "tools"}) +// GetToolsForCommand runs ` tools` and returns the parsed list of tools. +// commandName is the Use name of the ophis command in the Cobra tree (e.g. "mcp" or "agent"). +func GetToolsForCommand(t *testing.T, cmd *cobra.Command, commandName string) []*mcp.Tool { + cmd.SetArgs([]string{commandName, "tools"}) err := cmd.Execute() if err != nil { t.Fatalf("Failed to generate tools: %v", err) @@ -40,6 +40,13 @@ func GetTools(t *testing.T, cmd *cobra.Command) []*mcp.Tool { return tools } +// GetTools runs `mcp tools` command and returns the parsed list of tools. +// It assumes the ophis command uses the default name "mcp". +// For custom command names, use GetToolsForCommand. +func GetTools(t *testing.T, cmd *cobra.Command) []*mcp.Tool { + return GetToolsForCommand(t, cmd, "mcp") +} + // GetInputSchema extracts and returns the input schema from a tool func GetInputSchema(t *testing.T, tool *mcp.Tool) *jsonschema.Schema { if tool.InputSchema == nil { @@ -83,10 +90,10 @@ func CmdPathsToToolNames(paths []string) []string { return names } -// Tools runs `mcp tools` and checks the output tool names against expectedNames -// It fails if ophis.Command is not a root level subcommand -func Tools(t *testing.T, cmd *cobra.Command, expectedNames ...string) { - tools := GetTools(t, cmd) +// ToolsForCommand runs ` tools` and checks the output tool names against expectedNames. +// commandName is the Use name of the ophis command in the Cobra tree. +func ToolsForCommand(t *testing.T, cmd *cobra.Command, commandName string, expectedNames ...string) { + tools := GetToolsForCommand(t, cmd, commandName) t.Run("Expected Tools", func(t *testing.T) { ToolNames(t, tools, expectedNames...) @@ -121,3 +128,10 @@ func Tools(t *testing.T, cmd *cobra.Command, expectedNames ...string) { } }) } + +// Tools runs `mcp tools` and checks the output tool names against expectedNames. +// It assumes the ophis command uses the default name "mcp". +// For custom command names, use ToolsForCommand. +func Tools(t *testing.T, cmd *cobra.Command, expectedNames ...string) { + ToolsForCommand(t, cmd, "mcp", expectedNames...) +} diff --git a/test/tools_test.go b/test/tools_test.go index 7779529..a367413 100644 --- a/test/tools_test.go +++ b/test/tools_test.go @@ -68,3 +68,68 @@ func TestCmdNamesToToolNames(t *testing.T) { toolNames := CmdPathsToToolNames(cmdNames) assert.Equal(t, expectedToolNames, toolNames, "Tool names should match expected format") } + +// createCustomNameCommand creates a test command tree where the ophis command +// is named "agent" instead of the default "mcp". This mirrors the devenv use +// case where "mcp" is already taken by a business service. +func createCustomNameCommand() *cobra.Command { + root := &cobra.Command{ + Use: "myapp", + Short: "Test CLI with custom ophis command name", + } + + // A service called "mcp" — the reason we need to rename ophis. + mcpService := &cobra.Command{ + Use: "mcp", + Short: "MCP business service", + } + mcpInstall := &cobra.Command{ + Use: "install", + Short: "Install the mcp service", + Run: func(_ *cobra.Command, _ []string) {}, + } + mcpService.AddCommand(mcpInstall) + + status := &cobra.Command{ + Use: "status", + Short: "Show status", + Run: func(_ *cobra.Command, _ []string) {}, + } + + root.AddCommand(mcpService, status) + root.AddCommand(ophis.Command(&ophis.Config{CommandName: "agent"})) + + return root +} + +func TestCustomCommandName(t *testing.T) { + cmd := createCustomNameCommand() + + // Verify the ophis command is named "agent" in the tree. + var agentFound, mcpFound bool + for _, sub := range cmd.Commands() { + switch sub.Name() { + case "agent": + agentFound = true + case "mcp": + mcpFound = true + } + } + assert.True(t, agentFound, "expected 'agent' subcommand from ophis") + assert.True(t, mcpFound, "expected 'mcp' subcommand (business service)") + + // Get the tool list via the renamed command. + tools := GetToolsForCommand(t, cmd, "agent") + + // Should expose mcp_install and status — NOT ophis internals. + ToolNames(t, tools, "myapp_mcp_install", "myapp_status") + + // Explicitly verify no ophis subcommands leaked into the tool list. + for _, tool := range tools { + assert.NotContains(t, tool.Name, "agent", "ophis subcommand %q should not be exposed as a tool", tool.Name) + assert.NotContains(t, tool.Name, "start", "ophis 'start' should not be exposed as a tool") + assert.NotContains(t, tool.Name, "claude", "ophis 'claude' should not be exposed as a tool") + assert.NotContains(t, tool.Name, "cursor", "ophis 'cursor' should not be exposed as a tool") + assert.NotContains(t, tool.Name, "vscode", "ophis 'vscode' should not be exposed as a tool") + } +} From ae36ae89108d22e9e8499ef7e1d078b8ef0312c0 Mon Sep 17 00:00:00 2001 From: nick powell Date: Tue, 17 Feb 2026 21:13:41 -0800 Subject: [PATCH 2/2] chore: try existing github token --- .github/workflows/pull-request.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 49ff4e0..ee5cac6 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -13,7 +13,6 @@ jobs: permissions: contents: read pull-requests: write - id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 @@ -21,4 +20,5 @@ jobs: - name: Claude Code Review uses: anthropics/claude-code-action@v1 with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} \ No newline at end of file + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file