This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build the main CLI binary
go build -o bin/tiger ./cmd/tiger
# Build from project root (creates bin/tiger)
go build -o bin/tiger ./cmd/tiger# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Load environment variables from .env file (note: source .env doesn't work)
export $(cat .env | xargs)
# Run integration tests with environment variables from .env file
export $(cat .env | xargs) && go test ./internal/tiger/cmd -v -run TestServiceLifecycleIntegration# After building, run the CLI
./bin/tiger --help
# Or run directly with go
go run ./cmd/tiger --help# Run all integration tests
./scripts/test-integration.sh
# Run specific test pattern
./scripts/test-integration.sh CreateRole
# Run with verbose output
./scripts/test-integration.sh -v CreateRole
# Run specific test
./scripts/test-integration.sh CreateRole_WithInheritedGrantsThe script automatically:
- Loads environment variables from
.envfile - Builds the tiger CLI binary
- Runs the specified integration tests
- Provides colored output for better readability
# Run all tests (integration tests will skip without credentials)
go test ./...
# Run only integration tests
go test ./internal/tiger/cmd -run Integration
# To run integration tests with real API calls, set environment variables:
export TIGER_PUBLIC_KEY_INTEGRATION=your-public-key
export TIGER_SECRET_KEY_INTEGRATION=your-secret-key
export TIGER_PROJECT_ID_INTEGRATION=your-project-id
export TIGER_API_URL_INTEGRATION=http://localhost:8080/public/api/v1
# Optional: Set this to test database commands with existing service
export TIGER_EXISTING_SERVICE_ID_INTEGRATION=existing-service-id
# Then run tests normally
go test ./internal/tiger/cmd -v -run Integration# Generate OpenAPI client code and mocks from openapi.yaml
go generate ./internal/tiger/api
# This runs automatically as part of normal Go tooling when needed
# Generates:
# - client.go: HTTP client implementation
# - types.go: Type definitions for API models
# - mocks/mock_client.go: Mock implementations for testing- Always use
go fmtafter making file changes and before committing - Run
go vet ./...to catch potential issues before committing - Run
go test ./...to ensure all tests pass
IMPORTANT: Follow these rules when working with configuration:
-
Always use the Config struct - Never read configuration values directly from the global viper instance. Always load a
Configstruct and use its fields. -
Load once, pass down - Load the config once at the start of a command or operation, then pass it down to functions that need it. Do not reload the config if one is already available higher in the call chain.
-
MCP tools reload per-call - In MCP tool implementations, always load a fresh config at the start of each tool call. This ensures that configuration changes made by the user (via
tiger config set) take effect immediately for the next tool call, without requiring the MCP server to be restarted.
Example:
// ✅ Good: Load config once and pass it down
func (s *Server) handleServiceList(ctx context.Context, req *mcp.CallToolRequest, input ServiceListInput) (*mcp.CallToolResult, ServiceListOutput, error) {
// Load fresh config at start of MCP tool call
cfg, err := s.loadConfigWithProjectID()
if err != nil {
return nil, ServiceListOutput{}, err
}
// Use cfg.ProjectID, cfg.ServiceID, etc.
return doWork(cfg)
}
// ❌ Bad: Reading from viper directly
func handleCommand() {
projectID := viper.GetString("project_id") // Don't do this
}
// ❌ Bad: Reloading config when already available
func processData(cfg *config.Config) {
freshCfg, _ := config.Load() // Don't reload if cfg is already available
// Use cfg instead
}When implementing or updating functionality:
-
Keep CLI commands and MCP tools in sync - When updating a CLI command, check if there's a corresponding MCP tool and apply the same changes to keep them aligned. Examples:
tiger service listcommand →tiger_service_listMCP tooltiger service createcommand →tiger_service_createMCP tool
-
Check for intentional differences - Some discrepancies between CLI and MCP are intentional (e.g., different default behaviors, different output formats). Before making changes to sync them, ask whether the difference is intentional. Document intentional differences in code comments.
-
Share code between CLI and MCP - Code that needs to be used by both CLI commands and MCP tools should be moved to a shared package (not in
internal/tiger/cmdorinternal/tiger/mcp). Current examples:internal/tiger/util/- Shared utility functionsinternal/tiger/password/- Password storage logic used by both CLI and MCPinternal/tiger/api/- API client used by both
After making changes to commands, tools, configuration, or flags, always check and update:
- README.md - User-facing documentation (installation, usage, configuration)
- CLAUDE.md - Developer guidance (this file)
- specs/spec.md - CLI specification and requirements
- specs/spec_mcp.md - MCP server specification and tool documentation
Keep these files in sync with the actual implementation. When adding a new flag, config option, or command, update all relevant documentation files.
Tiger CLI is a Go-based command-line interface for managing Tiger, the modern database cloud. The architecture follows standard Go CLI patterns using Cobra and Viper.
- Entry Point:
cmd/tiger/main.go- Simple main that delegates to cmd.Execute() - Command Structure:
internal/tiger/cmd/- Cobra-based command definitionsroot.go- Root command with global flags and configuration initializationauth.go- Authentication commands (login, logout, status)service.go- Service management commands (list, create, get, fork, delete, update-password)db.go- Database operation commands (connection-string, connect, test-connection)config.go- Configuration management commands (show, set, unset, reset)mcp.go- MCP server commands (install, start)version.go- Version command
- Configuration:
internal/tiger/config/config.go- Centralized config with Viper integration - Logging:
internal/tiger/logging/logging.go- Structured logging with zap - API Client:
internal/tiger/api/- Generated OpenAPI client with mocks - MCP Server:
internal/tiger/mcp/- Model Context Protocol server implementationserver.go- MCP server initialization, tool registration, and lifecycle managementservice_tools.go- Service management tools (list, get, create, update-password)db_tools.go- Database operation tools (execute-query)proxy.go- Proxy client that forwards tools/resources/prompts from remote docs MCP server
- Password Storage:
internal/tiger/password/- Secure password storage utilities
The CLI uses a layered configuration approach:
- Default values in code
- Configuration file at
~/.config/tiger/config.yaml - Environment variables with
TIGER_prefix - Command-line flags (highest precedence)
Key configuration values:
api_url: Tiger API endpointconsole_url: Tiger Console URLgateway_url: Tiger Gateway URLdocs_mcp: Enable/disable proxied docs MCP toolsdocs_mcp_url: URL for docs MCP serverservice_id: Default service IDoutput: Output format (json, yaml, table)analytics: Usage analytics togglepassword_storage: Password storage method (keyring, pgpass, none)debug: Debug logging toggle
The Tiger MCP server provides AI assistants with programmatic access to Tiger resources through the Model Context Protocol (MCP).
Two Types of Tools:
- Direct Tiger Tools - Native tools for Tiger operations
service_tools.go- Service management (list, get, create, update-password)db_tools.go- Database operations (execute-query)
- Proxied Documentation Tools (
proxy.go) - Tools forwarded from a remote docs MCP server (seeproxy.gofor implementation)
Tool Definition Pattern:
When defining MCP tools, we use a pattern that balances type safety with schema flexibility:
- Define input/output structs with JSON tags:
type ServiceCreateInput struct {
Name string `json:"name,omitempty"`
Region string `json:"region,omitempty"`
Replicas int `json:"replicas,omitempty"`
Wait bool `json:"wait,omitempty"`
}- Implement Schema() method that auto-generates base schema, then enhances it:
func (ServiceCreateInput) Schema() *jsonschema.Schema {
// Auto-generate schema from struct
schema := util.Must(jsonschema.For[ServiceCreateInput](nil))
// Add descriptions, examples, and validation
schema.Properties["name"].Description = "Human-readable name for the service (auto-generated if not provided)"
schema.Properties["name"].Examples = []any{"my-production-db", "analytics-service"}
schema.Properties["name"].MaxLength = util.Ptr(128)
schema.Properties["region"].Description = "AWS region where the service will be deployed"
schema.Properties["region"].Examples = []any{"us-east-1", "us-west-2"}
// Set defaults and constraints
schema.Properties["replicas"].Default = util.Must(json.Marshal(0))
schema.Properties["replicas"].Minimum = util.Ptr(0.0)
schema.Properties["replicas"].Maximum = util.Ptr(5.0)
// Define enums for constrained values
schema.Properties["cpu_memory"].Enum = util.AnySlice(cpuMemoryCombinations)
return schema
}- Register tool with enhanced schema:
mcp.AddTool(s.mcpServer, &mcp.Tool{
Name: "tiger_service_create",
Description: `Detailed multi-line description...`,
InputSchema: ServiceCreateInput{}.Schema(), // Uses our enhanced schema
}, s.handleServiceCreate)Key Benefits of This Pattern:
- Type safety: Schema automatically reflects struct fields
- Flexibility: Can add descriptions, examples, validation, enums after generation
- Maintainability: Struct changes automatically propagate to schema
- Rich documentation: AI assistants get detailed guidance on each field
- Fail-fast validation: If you try to access/modify a property that doesn't exist in the generated schema (e.g., typo in field name), the code will panic at runtime, ensuring the schema stays in sync with the struct
- LLM validation: Stricter JSON schema validations (min/max values, enums, patterns, etc.) prevent LLMs from sending invalid arguments in tool calls, catching errors before they reach the handler
Important Notes:
- Fields with
omitemptyare optional; fields without it are required - Always provide descriptions and examples for better AI assistant understanding
- Use JSON Schema properties to constrain and document values (e.g.,
Default,Minimum,Maximum,Enum,Pattern,MinLength, etc.)- See the jsonschema-go Schema type for all available properties
- The MCP SDK can infer schemas automatically, but explicit schemas provide better control
Two-mode logging system using zap:
- Production mode: Minimal output, warn level and above, clean formatting
- Debug mode: Full development logging with colors and debug level
Global flags available on all commands:
--config-dir: Path to configuration directory--debug: Enable debug logging--output/-o: Set output format--service-id: Override service ID--analytics: Toggle analytics--password-storage: Password storage method
- Cobra: CLI framework and command structure
- Viper: Configuration management with multiple sources
- Zap: Structured logging
- oapi-codegen: OpenAPI client generation (build-time dependency)
- gomock: Mock generation for testing (build-time dependency)
- go-sdk (MCP): Model Context Protocol SDK for AI assistant integration
- pgx/v5: PostgreSQL driver for database operations in MCP tools
- Go 1.25+: Required Go version
tiger-cli/
├── cmd/tiger/ # Main CLI entry point
├── internal/tiger/ # Internal packages
│ ├── api/ # Generated OpenAPI client (oapi-codegen)
│ │ └── mocks/ # Generated mocks for testing
│ ├── config/ # Configuration management
│ ├── logging/ # Structured logging utilities
│ ├── mcp/ # MCP server implementation
│ ├── password/ # Password storage utilities
│ ├── cmd/ # CLI commands (Cobra)
│ └── util/ # Shared utilities
├── docs/ # Documentation
│ └── development.md # Development guide (building, testing, contributing)
├── specs/ # CLI specifications and API documentation
│ ├── spec.md # Basic project specification
│ └── spec_mcp.md # MCP server specification
├── .github/workflows/ # GitHub Actions CI/CD
│ ├── test.yml # Test workflow (runs on PRs and main)
│ └── release.yml # Release workflow (runs on semver tags)
├── bin/ # Built binaries (created during build)
├── openapi.yaml # OpenAPI 3.0 specification for Tiger API
├── .goreleaser.yml # GoReleaser configuration for building releases
├── tools.go # Build-time dependencies
├── README.md # User-facing documentation
└── CLAUDE.md # Developer guidance for Claude Code
The internal/ directory follows Go conventions to prevent external imports of internal packages.
Additional Documentation:
- See
docs/development.mdfor detailed development information including building from source, running integration tests, and project structure details - See
README.mdfor user-facing documentation on installation, usage, and configuration
When implementing Cobra commands, use this pattern to control when usage information is displayed on errors:
RunE: func(cmd *cobra.Command, args []string) error {
// 1. Do argument validation first - errors here show usage
if len(args) < 1 {
return fmt.Errorf("service ID is required")
}
// 2. Set SilenceUsage = true after argument validation
cmd.SilenceUsage = true
// 3. Proceed with business logic - errors here don't show usage
if err := someAPICall(); err != nil {
return fmt.Errorf("operation failed: %w", err)
}
return nil
},Philosophy:
- Early argument/syntax errors → show usage (helps users learn command syntax)
- Operational errors after arguments are validated → don't show usage (avoids cluttering output with irrelevant usage info)
This provides fine-grained control over when usage is displayed, improving user experience by showing help when it's relevant and hiding it when it's not.
Tiger CLI uses a pure functional builder pattern with zero global command state. This architecture ensures perfect test isolation, eliminates shared state issues, and provides a clean, maintainable command structure.
- No global variables - All commands, flags, and state are locally scoped
- Functional builders - Every command is built by a dedicated function
- Complete tree building -
buildRootCmd()constructs the entire CLI structure - Perfect test isolation - Each test gets completely fresh command instances
- Self-contained commands - All dependencies passed explicitly via parameters
buildRootCmd() → Complete CLI with all commands and flags
├── buildVersionCmd()
├── buildConfigCmd()
│ ├── buildConfigShowCmd()
│ ├── buildConfigSetCmd()
│ ├── buildConfigUnsetCmd()
│ └── buildConfigResetCmd()
├── buildAuthCmd()
│ ├── buildLoginCmd()
│ ├── buildLogoutCmd()
│ └── buildStatusCmd()
├── buildServiceCmd()
│ ├── buildServiceListCmd()
│ ├── buildServiceGetCmd()
│ ├── buildServiceCreateCmd()
│ ├── buildServiceForkCmd()
│ ├── buildServiceDeleteCmd()
│ └── buildServiceUpdatePasswordCmd()
├── buildDbCmd()
│ ├── buildDbConnectionStringCmd()
│ ├── buildDbConnectCmd()
│ └── buildDbTestConnectionCmd()
└── buildMCPCmd()
├── buildMCPInstallCmd()
└── buildMCPStartCmd()
├── buildMCPStdioCmd()
└── buildMCPHTTPCmd()
The root command builder creates the complete CLI structure:
func buildRootCmd() *cobra.Command {
// Declare ALL flag variables locally within this function
var configDir string
var debug bool
var serviceID string
var analytics bool
var passwordStorage string
cmd := &cobra.Command{
Use: "tiger",
Short: "Tiger CLI - TigerData Cloud Platform command-line interface",
Long: `Complete CLI description...`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Use local flag variables in scope
if err := logging.Init(debug); err != nil {
return fmt.Errorf("failed to initialize logging: %w", err)
}
// ... rest of initialization
return nil
},
}
// Set up configuration and flags...
cobra.OnInitialize(initConfigFunc)
cmd.PersistentFlags().StringVar(&configDir, "config-dir", config.GetDefaultConfigDir(), "config directory")
cmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging")
cmd.PersistentFlags().StringVar(&serviceID, "service-id", "", "service ID")
cmd.PersistentFlags().BoolVar(&analytics, "analytics", true, "enable/disable usage analytics")
cmd.PersistentFlags().StringVar(&passwordStorage, "password-storage", config.DefaultPasswordStorage, "password storage method (keyring, pgpass, none)")
// Bind flags to viper
viper.BindPFlag("debug", cmd.PersistentFlags().Lookup("debug"))
// ... bind remaining flags
// Add all subcommands (complete tree building)
cmd.AddCommand(buildVersionCmd())
cmd.AddCommand(buildConfigCmd())
cmd.AddCommand(buildAuthCmd())
cmd.AddCommand(buildServiceCmd())
cmd.AddCommand(buildDbCmd())
cmd.AddCommand(buildMCPCmd())
return cmd
}For commands without flags:
func buildVersionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Show version information",
Long: `Display version, build time, and git commit information.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Tiger CLI %s\n", Version)
// ... version output
},
}
}For commands that need their own flags:
func buildMyFlaggedCmd() *cobra.Command {
// Declare flag variables locally (NEVER globally!)
var myFlag string
var enableFeature bool
var retryCount int
cmd := &cobra.Command{
Use: "my-command",
Short: "Command with local flags",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("argument required")
}
cmd.SilenceUsage = true
// Use flag variables (they're in scope)
fmt.Printf("Flag: %s, Feature: %t, Retries: %d\n",
myFlag, enableFeature, retryCount)
return nil
},
}
// Add flags - bound to local variables
cmd.Flags().StringVar(&myFlag, "flag", "", "My flag description")
cmd.Flags().BoolVar(&enableFeature, "enable", false, "Enable feature")
cmd.Flags().IntVar(&retryCount, "retries", 3, "Retry count")
return cmd
}For commands that contain subcommands, build the complete tree:
func buildParentCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "parent",
Short: "Parent command with subcommands",
Long: `Parent command containing multiple subcommands.`,
}
// Add all subcommands (builds complete subtree)
cmd.AddCommand(buildChild1Cmd())
cmd.AddCommand(buildChild2Cmd())
cmd.AddCommand(buildChild3Cmd())
return cmd
}The main application uses a single builder call:
func Execute() {
// Build complete command tree fresh each time
rootCmd := buildRootCmd()
err := rootCmd.Execute()
if err != nil {
if exitErr, ok := err.(interface{ ExitCode() int }); ok {
os.Exit(exitErr.ExitCode())
}
os.Exit(1)
}
}With this pattern, commands don't need init() functions because the root command builder handles complete tree construction:
// OLD PATTERN (don't do this):
func init() {
myCmd := buildMyCmd()
rootCmd.AddCommand(myCmd) // Global state dependency
}
// NEW PATTERN (do this):
// No init() function needed - buildRootCmd() handles everythingTests use the full root command builder:
func executeCommand(args ...string) (string, error) {
// Build complete CLI fresh for each test
rootCmd := buildRootCmd()
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.SetErr(buf)
rootCmd.SetArgs(args)
err := rootCmd.Execute()
return buf.String(), err
}
func TestMyCommand(t *testing.T) {
// Each test gets completely fresh CLI instance
output, err := executeCommand("my-command", "--flag", "value")
if err != nil {
t.Fatalf("Command failed: %v", err)
}
if !strings.Contains(output, "expected") {
t.Errorf("Expected 'expected' in output: %s", output)
}
}For tests that need to verify flag values:
func executeAndReturnRoot(args ...string) (*cobra.Command, string, error) {
rootCmd := buildRootCmd()
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.SetArgs(args)
err := rootCmd.Execute()
return rootCmd, buf.String(), err
}
func TestFlagValues(t *testing.T) {
rootCmd, output, err := executeAndReturnRoot("service", "create", "--name", "test")
// Navigate to specific command
serviceCmd, _, _ := rootCmd.Find([]string{"service"})
createCmd, _, _ := serviceCmd.Find([]string{"create"})
// Check flag value
nameFlag := createCmd.Flags().Lookup("name")
if nameFlag.Value.String() != "test" {
t.Errorf("Expected name=test, got %s", nameFlag.Value.String())
}
}- Zero Global State: No shared variables between commands or tests
- Perfect Test Isolation: Each test builds completely fresh command trees
- Simplified Initialization: Single entry point builds everything
- Maintainable Code: No complex global variable management
- Easy Development: Add new commands by creating builders and adding to root
- Predictable Behavior: No hidden dependencies or initialization order issues
- Memory Efficient: Commands built only when needed
When adding new commands to this architecture:
- Create a builder function following the
buildXXXCmd()pattern - Declare flags locally within the builder function scope
- Add to root command by calling
cmd.AddCommand(buildXXXCmd())inbuildRootCmd() - No init() function required - everything goes through the root builder
- Test with
buildRootCmd()instead of recreating flag setup
This architecture ensures Tiger CLI remains maintainable and testable as it grows.
Tiger CLI follows established command-line interface patterns, particularly inspired by the GitHub CLI (gh) for consistency with modern CLI tools.
When implementing boolean flags that can be enabled or disabled, follow the GitHub CLI pattern:
- Default Positive Behavior - The positive behavior is the default (no flag needed)
- Explicit Negative Override - Use
--no-<feature>to disable the default behavior - Avoid Mutually Exclusive Pairs - Don't create both
--enable-Xand--disable-Xflags
Example:
// ✅ Good: GitHub CLI pattern
var createNoSetDefault bool
cmd.Flags().BoolVar(&createNoSetDefault, "no-set-default", false, "Don't set this service as the default service")
// Default behavior: set as default
if !createNoSetDefault {
setAsDefault()
}
// ❌ Avoid: Mutually exclusive flags
var setDefault bool
var noSetDefault bool
cmd.Flags().BoolVar(&setDefault, "set-default", true, "Set service as default")
cmd.Flags().BoolVar(&noSetDefault, "no-set-default", false, "Don't set as default")
cmd.MarkFlagsMutuallyExclusive("set-default", "no-set-default")Real GitHub CLI Examples:
gh pr createhas--no-maintainer-editto disable the default maintainer edit behavior- Default behavior is implicit, override is explicit with
--no-prefix
For destructive operations (delete, remove, etc.), follow these safety patterns:
- Explicit Resource ID Required - No default fallback for destructive operations
- Interactive Confirmation - Require typing the resource ID to confirm
- Automation Override -
--confirmflag to skip prompts for scripts - AI Agent Warnings - Include warnings in help text for AI agents
Example:
// Require explicit service ID (no default)
if len(args) < 1 {
return fmt.Errorf("service ID is required")
}
// Interactive confirmation unless --confirm
if !confirmFlag {
fmt.Fprintf(cmd.ErrOrStderr(), "Type the service ID '%s' to confirm: ", serviceID)
var confirmation string
fmt.Scanln(&confirmation)
if confirmation != serviceID {
return fmt.Errorf("confirmation did not match")
}
}For asynchronous operations, provide consistent wait behavior:
- Default Wait - Wait for completion by default
- No-Wait Override -
--no-waitto return immediately - Timeout Control -
--wait-timeoutwith duration parsing - Exit Code 2 - Use exit code 2 for timeout scenarios
Example:
var noWait bool
var waitTimeout time.Duration
cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for operation to complete")
cmd.Flags().DurationVar(&waitTimeout, "wait-timeout", 30*time.Minute, "Wait timeout duration")
// Default: wait for completion
if !noWait {
if err := waitForCompletion(waitTimeout); err != nil {
if isTimeout(err) {
return exitWithCode(2, err) // Exit code 2 for timeouts
}
return err
}
}- Explain Default Behavior - Always document what happens by default
- Show Override Options - Explain how to change default behavior
- Include Examples - Show common usage patterns
- AI Agent Notes - Add warnings for destructive operations
Example:
Long: `Create a new database service in the current project.
By default, the newly created service will be set as your default service for future
commands. Use --no-set-default to prevent this behavior.
Note for AI agents: Always confirm with the user before performing this destructive operation.
Examples:
# Create service (sets as default by default)
tiger service create --name my-db
# Create service without setting as default
tiger service create --name temp-db --no-set-default`,- Kebab Case - Use
--kebab-casefor multi-word flags - Descriptive Names - Prefer clarity over brevity
- Consistent Prefixes - Use
--no-for negative overrides - Avoid Abbreviations - Prefer
--no-waitover--nowait
These patterns ensure Tiger CLI maintains consistency with modern CLI tools while providing a predictable user experience.
The project uses GitHub Actions for continuous integration and release automation. Workflows are defined in .github/workflows/:
Trigger: Runs on pull requests and pushes to main branch
Purpose: Validates code quality and ensures all tests pass
Note: Tests run with -p 1 to avoid parallel execution issues with keyring access.
Trigger: Runs when a semver tag (e.g., v1.2.3) is pushed to the repository
Purpose: Builds and publishes releases across multiple platforms
How to Trigger:
# Method 1: Via GitHub UI (recommended)
# Go to Releases → Draft a new release → Create tag (v1.2.3) → Publish
# Method 2: Via command line
VERSION=1.2.3 && git tag -a v${VERSION} -m "${VERSION}" && git push origin v${VERSION}Publishing Targets:
- GitHub Releases - Creates release with binaries for multiple platforms (macOS, Linux, Windows)
- Homebrew Tap - Updates
timescale/homebrew-tapwith new formula - S3 Bucket - Uploads binaries to
tiger-cli-releasesS3 bucket (behindhttps://cli.tigerdata.comCloudFront CDN) for install script and Homebrew downloads - PackageCloud - Publishes Debian (.deb) and RPM packages to
timescale/tiger-clirepository
Build Tool: Uses GoReleaser to build and publish across all platforms. Configuration is in .goreleaser.yml.
The project specifications are located in the specs/ directory:
spec.md- Basic project specification and CLI requirementsspec_mcp.md- MCP (Model Context Protocol) specification for integration
- Never accept a state where tests are failing