Skip to content

Commit 8ff223a

Browse files
obayclaude
andcommitted
refactor: enhance Viper configuration management for seamless API key handling
Improve configuration handling to fully leverage Viper's capabilities, eliminating the need to pass API key with every command. Changes: - Add explicit binding of api-key to HUBSPOT_API_KEY environment variable - Simplify getAPIKey() to use single viper.GetString() call - Remove redundant manual checking of flags and environment variables - Remove unused os import from contacts.go - Add comprehensive CLAUDE.md documentation for future development Configuration now works seamlessly with three methods (in priority order): 1. Command-line flag: --api-key 2. Environment variable: HUBSPOT_API_KEY 3. Config file: ~/.hsctl.yaml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3a1743f commit 8ff223a

3 files changed

Lines changed: 182 additions & 11 deletions

File tree

CLAUDE.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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`

cmd/contacts.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"encoding/json"
55
"fmt"
6-
"os"
76
"strconv"
87
"strings"
98

@@ -313,15 +312,11 @@ func init() {
313312
}
314313

315314
func getAPIKey() string {
316-
// Check flag first
317-
if apiKey := viper.GetString("api-key"); apiKey != "" {
318-
return apiKey
319-
}
320-
// Check environment variable
321-
if apiKey := os.Getenv("HUBSPOT_API_KEY"); apiKey != "" {
322-
return apiKey
323-
}
324-
return ""
315+
// Viper automatically checks in this order:
316+
// 1. Command-line flag (--api-key)
317+
// 2. Environment variable (HUBSPOT_API_KEY)
318+
// 3. Config file (~/.hsctl.yaml)
319+
return viper.GetString("api-key")
325320
}
326321

327322
func printContacts(contacts []hubspot.Contact, format string) error {

cmd/root.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ func initConfig() {
4848
viper.SetConfigName(".hsctl")
4949
}
5050

51-
viper.AutomaticEnv() // read in environment variables that match
5251
viper.SetEnvPrefix("HUBSPOT")
52+
viper.AutomaticEnv() // read in environment variables that match
53+
54+
// Bind the api-key to the HUBSPOT_API_KEY environment variable
55+
viper.BindEnv("api-key", "HUBSPOT_API_KEY")
5356

5457
// If a config file is found, read it in.
5558
if err := viper.ReadInConfig(); err == nil {

0 commit comments

Comments
 (0)