|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +HSCTL is a command-line interface (CLI) tool written in Go for managing HubSpot contacts. It provides CRUD operations on HubSpot contacts via the HubSpot API v3, eliminating the need to navigate through the HubSpot web interface. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Building |
| 12 | +```bash |
| 13 | +# Build the binary |
| 14 | +go build -o hsctl . |
| 15 | + |
| 16 | +# Build with version information (as done by GoReleaser) |
| 17 | +go build -ldflags "-s -w -X github.com/obay/hsctl/cmd.version=dev -X github.com/obay/hsctl/cmd.commit=$(git rev-parse HEAD) -X github.com/obay/hsctl/cmd.buildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o hsctl . |
| 18 | +``` |
| 19 | + |
| 20 | +### Testing |
| 21 | +```bash |
| 22 | +# Run all tests |
| 23 | +go test -v ./... |
| 24 | + |
| 25 | +# Run tests with coverage |
| 26 | +go test -v -cover ./... |
| 27 | + |
| 28 | +# Run tests for a specific package |
| 29 | +go test -v ./internal/hubspot/... |
| 30 | +``` |
| 31 | + |
| 32 | +### Running |
| 33 | +```bash |
| 34 | +# Run without building |
| 35 | +go run . contacts list --api-key YOUR_API_KEY |
| 36 | + |
| 37 | +# Run after building |
| 38 | +./hsctl contacts list --api-key YOUR_API_KEY |
| 39 | +``` |
| 40 | + |
| 41 | +### Dependencies |
| 42 | +```bash |
| 43 | +# Download dependencies |
| 44 | +go mod download |
| 45 | + |
| 46 | +# Clean up dependencies |
| 47 | +go mod tidy |
| 48 | +``` |
| 49 | + |
| 50 | +## Architecture |
| 51 | + |
| 52 | +### Project Structure |
| 53 | + |
| 54 | +- **[main.go](main.go)**: Entry point that calls `cmd.Execute()` |
| 55 | +- **[cmd/](cmd/)**: Cobra command definitions |
| 56 | + - **[root.go](cmd/root.go)**: Root command setup, configuration initialization (Viper), and API key management |
| 57 | + - **[contacts.go](cmd/contacts.go)**: All contact-related subcommands (list, create, update, delete, query, properties) |
| 58 | + - **[version.go](cmd/version.go)**: Version command with build metadata |
| 59 | +- **[internal/hubspot/](internal/hubspot/)**: HubSpot API client implementation |
| 60 | + - **[client.go](internal/hubspot/client.go)**: HTTP client wrapper, data structures (Contact, Property), and all API operations |
| 61 | + - **[client_test.go](internal/hubspot/client_test.go)**: Unit tests for the client |
| 62 | + |
| 63 | +### Key Design Patterns |
| 64 | + |
| 65 | +**Command Pattern (Cobra)** |
| 66 | +- Each operation is a separate Cobra command |
| 67 | +- Commands are registered in `init()` functions |
| 68 | +- All commands share the root command's persistent flags (api-key, config) |
| 69 | + |
| 70 | +**Client Wrapper** |
| 71 | +- The `hubspot.Client` struct encapsulates all API interactions |
| 72 | +- `doRequest()` method handles common HTTP logic (authentication, error handling, JSON marshaling) |
| 73 | +- Each API operation (ListContacts, CreateContact, etc.) is a separate method |
| 74 | + |
| 75 | +**Configuration Priority (Viper)** |
| 76 | +1. Command-line flags (--api-key) |
| 77 | +2. Environment variables (HUBSPOT_API_KEY) |
| 78 | +3. Config file (~/.hsctl.yaml) |
| 79 | + |
| 80 | +**Output Formatting** |
| 81 | +- Two output modes: `table` (human-readable) and `json` (machine-readable) |
| 82 | +- Implemented in `printContacts()` and `printProperties()` functions in [cmd/contacts.go](cmd/contacts.go) |
| 83 | + |
| 84 | +### API Integration |
| 85 | + |
| 86 | +All HubSpot API calls use v3 endpoints: |
| 87 | +- Base URL: `https://api.hubapi.com` |
| 88 | +- Authentication: Bearer token in Authorization header |
| 89 | +- Standard properties retrieved: `email`, `firstname`, `lastname`, `hs_lead_status`, `lifecyclestage` |
| 90 | + |
| 91 | +Search functionality supports two query formats: |
| 92 | +- Property-based: `property=value` (e.g., `email=john@example.com`) |
| 93 | +- Text search: searches in email field using CONTAINS_TOKEN operator |
| 94 | + |
| 95 | +### Release Process |
| 96 | + |
| 97 | +The project uses GoReleaser for automated releases: |
| 98 | +- **Triggered by**: Git tags matching `v*` pattern |
| 99 | +- **Builds for**: Linux, macOS, Windows (amd64, arm64) |
| 100 | +- **Outputs**: |
| 101 | + - Versioned archives (e.g., `hsctl_v1.0.0_darwin_amd64.tar.gz`) |
| 102 | + - Versionless archives (e.g., `hsctl_darwin_amd64.tar.gz`) for package managers |
| 103 | + - Debian (.deb) and RPM (.rpm) packages |
| 104 | + - Homebrew cask formula (pushed to obay/homebrew-tap) |
| 105 | + - Scoop manifest (pushed to obay/scoop-bucket) |
| 106 | + |
| 107 | +Configuration: [.goreleaser.yml](.goreleaser.yml) |
| 108 | + |
| 109 | +### Testing Strategy |
| 110 | + |
| 111 | +Tests are minimal but cover core functionality: |
| 112 | +- Client initialization |
| 113 | +- HTTP request/response handling with mock servers |
| 114 | +- Data structure validation |
| 115 | + |
| 116 | +When adding new API operations, follow the pattern in [internal/hubspot/client_test.go](internal/hubspot/client_test.go). |
| 117 | + |
| 118 | +## Common Development Tasks |
| 119 | + |
| 120 | +### Adding a New Contact Property to Default Retrieval |
| 121 | + |
| 122 | +Update the `properties` query parameter in: |
| 123 | +- `ListContacts()` method ([internal/hubspot/client.go:123](internal/hubspot/client.go#L123)) |
| 124 | +- `GetContact()` method ([internal/hubspot/client.go:146](internal/hubspot/client.go#L146)) |
| 125 | +- `SearchContacts()` method ([internal/hubspot/client.go:258](internal/hubspot/client.go#L258)) |
| 126 | + |
| 127 | +### Adding a New Command |
| 128 | + |
| 129 | +1. Create a new `cobra.Command` in the appropriate file under [cmd/](cmd/) |
| 130 | +2. Register it in the `init()` function |
| 131 | +3. Implement the command logic in the `RunE` function |
| 132 | +4. Add any necessary API methods to [internal/hubspot/client.go](internal/hubspot/client.go) |
| 133 | + |
| 134 | +### Extending to Support Other HubSpot Objects |
| 135 | + |
| 136 | +The current architecture is ready for extension: |
| 137 | +1. Add new object types (Deal, Company, etc.) to [internal/hubspot/client.go](internal/hubspot/client.go) |
| 138 | +2. Create new command files under [cmd/](cmd/) (e.g., `deals.go`, `companies.go`) |
| 139 | +3. Follow the same pattern as [cmd/contacts.go](cmd/contacts.go) |
| 140 | + |
| 141 | +## Configuration Management |
| 142 | + |
| 143 | +The project uses Viper for unified configuration management, eliminating the need to pass the API key with every command. |
| 144 | + |
| 145 | +### Configuration Priority |
| 146 | + |
| 147 | +Viper checks for the API key in the following order (highest to lowest priority): |
| 148 | +1. **Command-line flag**: `--api-key YOUR_KEY` |
| 149 | +2. **Environment variable**: `HUBSPOT_API_KEY` |
| 150 | +3. **Config file**: `~/.hsctl.yaml` |
| 151 | + |
| 152 | +### Configuration File Format |
| 153 | + |
| 154 | +Create `~/.hsctl.yaml` with: |
| 155 | +```yaml |
| 156 | +api-key: your-hubspot-api-key-here |
| 157 | +``` |
| 158 | +
|
| 159 | +### Implementation Details |
| 160 | +
|
| 161 | +Configuration is initialized in [cmd/root.go](cmd/root.go): |
| 162 | +- `viper.BindPFlag("api-key", ...)` binds the CLI flag |
| 163 | +- `viper.BindEnv("api-key", "HUBSPOT_API_KEY")` binds the environment variable |
| 164 | +- `viper.SetConfigName(".hsctl")` sets the config file name |
| 165 | +- `viper.ReadInConfig()` reads the config file from `$HOME/.hsctl.yaml` |
| 166 | + |
| 167 | +All commands use `viper.GetString("api-key")` to retrieve the API key from any source (see `getAPIKey()` in [cmd/contacts.go:315](cmd/contacts.go#L315)). |
| 168 | + |
| 169 | +### Required HubSpot Scopes |
| 170 | + |
| 171 | +When creating a HubSpot private app, ensure it has these scopes: |
| 172 | +- `crm.objects.contacts.read` |
| 173 | +- `crm.objects.contacts.write` |
0 commit comments