This project uses Conventional Commits for commit messages. This allows us to automatically generate changelogs and determine version bumps.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build system or external dependencies
- ci: Changes to our CI configuration files and scripts
- chore: Other changes that don't modify src or test files
- revert: Reverts a previous commit
feat: add chat command for interactive LLM sessionsfix: resolve memory leak in tool executiondocs: update README with installation instructionsfeat!: change default config file location(breaking change)fix(cli): handle missing config file gracefully
Breaking changes should be indicated by:
!after the type/scope:feat!: change API interface- Or a footer:
BREAKING CHANGE: API interface has changed
- Ensure you have flox installed and activated:
flox activate - Install pre-commit hooks:
flox activate -- task precommit:install - Make your changes following the code style guidelines in CLAUDE.md
- Run tests:
flox activate -- task test - Run quality checks:
flox activate -- task precommit:run - Commit with conventional commit messages (pre-commit hooks will run automatically)
- Push to your fork and create a pull request
This project uses pre-commit hooks to ensure code quality and consistent formatting:
- Setup:
flox activate -- task precommit:install - Run on all files:
flox activate -- task precommit:run
The hooks automatically:
- Add missing final newlines to files
- Remove trailing whitespace
- Validate YAML/JSON/TOML syntax
- Run golangci-lint on Go code
- Check for merge conflicts
# Development setup
flox activate -- task precommit:install # Install pre-commit hooks
flox activate -- task mod:download # Download Go modules
# Code quality
flox activate -- task fmt # Format Go code
flox activate -- task lint # Run golangci-lint
flox activate -- task vet # Run go vet
flox activate -- task precommit:run # Run all quality checks (pre-commit hooks)
# Testing
flox activate -- task test # Run tests
flox activate -- task test:verbose # Run tests with verbose output
flox activate -- task test:coverage # Run tests with coverage
# Building
flox activate -- task build # Build binary
flox activate -- task release:build # Build for all platformsThe CLI uses a modular tools architecture where each tool is implemented as a separate module in the
internal/services/tools/ package. This section describes how to add new tools for LLM integration.
internal/services/tools/
├── interfaces.go # Tool interface definitions
├── registry.go # Tool management and registration
├── bash.go # Example: Bash command execution tool
├── read.go # Example: File reading tool
├── grep.go # Example: Grep tool
├── fetch.go # Example: Content fetching tool
├── websearch.go # Example: Web search tool
└── [your-tool].go # Your new tool implementation
Create a new file internal/services/tools/your_tool.go:
package tools
import (
"context"
"fmt"
"github.com/inference-gateway/cli/config"
"github.com/inference-gateway/cli/internal/domain"
"github.com/inference-gateway/sdk"
)
// YourTool handles your specific functionality
type YourTool struct {
config *config.Config
enabled bool
// Add any additional dependencies here
}
// NewYourTool creates a new instance of your tool
func NewYourTool(cfg *config.Config /* add other dependencies */) *YourTool {
return &YourTool{
config: cfg,
enabled: cfg.Tools.Enabled, // or specific config section
// Initialize dependencies
}
}Your tool must implement the Tool interface defined in interfaces.go:
// Definition returns the tool definition for the LLM
func (t *YourTool) Definition() sdk.ChatCompletionTool {
description := "Description of what your tool does"
parameters := sdk.FunctionParameters(map[string]any{
"type": "object",
"properties": map[string]any{
"param1": map[string]any{
"type": "string",
"description": "Description of parameter 1",
},
"param2": map[string]any{
"type": "integer",
"description": "Description of parameter 2",
"minimum": 1,
},
},
"required": []string{"param1"},
})
return sdk.ChatCompletionTool{
Type: sdk.Function,
Function: sdk.FunctionObject{
Name: "YourTool",
Description: &description,
Parameters: ¶meters,
},
}
}
// Execute runs the tool with given arguments
func (t *YourTool) Execute(ctx context.Context, args map[string]any) (*domain.ToolExecutionResult, error) {
if !t.enabled {
return nil, fmt.Errorf("YourTool is not enabled")
}
// Validate and extract arguments
param1, ok := args["param1"].(string)
if !ok {
return nil, fmt.Errorf("param1 must be a string")
}
// Implement your tool logic here
result := fmt.Sprintf("Processing: %s", param1)
return &domain.ToolExecutionResult{
Output: result,
// Add other result fields as needed
}, nil
}
// Validate checks if the tool arguments are valid
func (t *YourTool) Validate(args map[string]any) error {
// Implement validation logic
if _, exists := args["param1"]; !exists {
return fmt.Errorf("param1 is required")
}
return nil
}
// IsEnabled returns whether this tool is enabled
func (t *YourTool) IsEnabled() bool {
return t.enabled
}Add your tool to the registry in internal/services/tools/registry.go:
// In the registerTools() method, add:
r.tools["YourTool"] = NewYourTool(r.config /* add dependencies */)For conditional tools (e.g., requiring external services or configuration):
// Example for conditional registration
if r.config.YourService.Enabled {
r.tools["YourTool"] = NewYourTool(r.config, r.yourService)
}If your tool needs configuration, add it to config/config.go:
// Add to the Config struct
type Config struct {
// ... existing fields
YourService YourServiceConfig `yaml:"your_service"`
}
type YourServiceConfig struct {
Enabled bool `yaml:"enabled"`
APIKey string `yaml:"api_key"`
// Add other config fields
}Create internal/services/tools/your_tool_test.go:
package tools
import (
"context"
"testing"
"github.com/inference-gateway/cli/config"
"github.com/stretchr/testify/assert"
)
func TestYourTool_Definition(t *testing.T) {
cfg := &config.Config{
Tools: config.ToolsConfig{Enabled: true},
}
tool := NewYourTool(cfg)
def := tool.Definition()
assert.Equal(t, "YourTool", def.Function.Name)
assert.Contains(t, *def.Function.Description, "your tool")
}
func TestYourTool_Execute(t *testing.T) {
cfg := &config.Config{
Tools: config.ToolsConfig{Enabled: true},
}
tool := NewYourTool(cfg)
args := map[string]any{
"param1": "test value",
}
result, err := tool.Execute(context.Background(), args)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Contains(t, result.Output, "test value")
}
func TestYourTool_Validate(t *testing.T) {
cfg := &config.Config{
Tools: config.ToolsConfig{Enabled: true},
}
tool := NewYourTool(cfg)
// Test valid args
validArgs := map[string]any{"param1": "value"}
assert.NoError(t, tool.Validate(validArgs))
// Test invalid args
invalidArgs := map[string]any{}
assert.Error(t, tool.Validate(invalidArgs))
}Run the test suite to ensure your tool works correctly:
# Run all tests
flox activate -- task test
# Run tests for your specific tool
flox activate -- go test ./internal/services/tools -run TestYourTool
# Run with verbose output
flox activate -- task test:verboseConsider adding usage examples to the main README.md if your tool adds significant functionality.
- Security: Always validate input parameters and implement proper error handling
- Configuration: Make tools configurable and respect the global
tools.enabledsetting - Error Handling: Return meaningful error messages that help users understand what went wrong
- Testing: Write comprehensive tests including edge cases and error conditions
- Documentation: Use clear, descriptive names and comprehensive parameter descriptions
- Dependencies: Minimize external dependencies and use dependency injection for services
- Context: Always respect the context for cancellation and timeouts
Study the existing tools for implementation patterns:
- BashTool (
bash.go): Shows command execution with security validation - ReadTool (
read.go): Demonstrates file system operations - GrepTool (
grep.go): Shows complex parameter handling with ripgrep integration - WebSearchTool (
websearch.go): Shows integration with external services
Releases are automated using semantic-release:
- Commits to
mainbranch trigger automatic releases - Version numbers are determined by commit types:
fix:→ patch version (1.0.1)feat:→ minor version (1.1.0)feat!:orBREAKING CHANGE:→ major version (2.0.0)
- Binaries are built for macOS (Intel/ARM64) and Linux (AMD64/ARM64)
- GitHub releases are created automatically with changelogs