Skip to content

Commit 50836a9

Browse files
committed
Add register helper command for OAuth app setup
Adds a new `basecamp register` command that interactively collects application details and outputs the exact values needed for Basecamp OAuth app registration. Also updates README with clearer setup instructions explaining the port 3002 requirement and tunnel services (Tailscale, ngrok) needed for OAuth callback.
1 parent d28d2c7 commit 50836a9

File tree

3 files changed

+111
-4
lines changed

3 files changed

+111
-4
lines changed

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,35 @@ make build
3030

3131
## Setup
3232

33-
1. Create a Basecamp OAuth app at https://launchpad.37signals.com/integrations
34-
2. Run `basecamp init` to configure credentials
35-
3. Run `basecamp auth` to authenticate
33+
### Prerequisites
3634

37-
Configuration files (XDG Base Directory):
35+
During OAuth authentication, Basecamp redirects to your computer on **port 3002**. Your machine must be accessible via a URL for this to work. Use a service like:
36+
37+
- [Tailscale](https://tailscale.com/) - Recommended for persistent access
38+
- [ngrok](https://ngrok.com/) - Quick setup for temporary access
39+
- Any reverse proxy that exposes localhost:3002
40+
41+
### Registration
42+
43+
1. Start your tunnel service and note the public URL (e.g., `https://myhost.tailscale.ts.net`)
44+
45+
2. Run the registration helper to generate your OAuth app values:
46+
47+
```bash
48+
basecamp register
49+
```
50+
51+
This will ask for your application details and output the exact values to enter in the Basecamp registration form, including the correct redirect URI.
52+
53+
3. Visit https://launchpad.37signals.com/integrations and register your app using the generated values
54+
55+
4. Run `basecamp init` to configure your credentials (Client ID, Client Secret, and the same Redirect URI)
56+
57+
5. Run `basecamp auth` to authenticate (ensure your tunnel is running on port 3002)
58+
59+
### Configuration Files
60+
61+
Configuration follows XDG Base Directory specification:
3862
- `~/.config/basecamp/config.json` - client credentials
3963
- `~/.local/share/basecamp/token.json` - OAuth token
4064

@@ -350,6 +374,14 @@ basecamp card 44444444 # just need card_id
350374

351375
The CLI searches current directory and parent directories for `.basecamp.yml`.
352376

377+
## Agent Skills
378+
379+
Install the Basecamp skill for AI coding agents (Claude Code, OpenCode, and others):
380+
381+
```bash
382+
npx skills add robzolkos/basecamp-cli
383+
```
384+
353385
## Output
354386

355387
All commands output JSON for easy parsing with `jq`:

internal/commands/register.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package commands
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"strings"
8+
)
9+
10+
type RegisterCmd struct{}
11+
12+
func (c *RegisterCmd) Run(args []string) error {
13+
fmt.Fprintln(os.Stderr, "Basecamp OAuth App Registration Helper")
14+
fmt.Fprintln(os.Stderr, strings.Repeat("=", 40))
15+
fmt.Fprintln(os.Stderr)
16+
fmt.Fprintln(os.Stderr, "This helper will generate the values you need to register")
17+
fmt.Fprintln(os.Stderr, "your Basecamp OAuth application.")
18+
fmt.Fprintln(os.Stderr)
19+
20+
reader := bufio.NewReader(os.Stdin)
21+
22+
appName := prompt(reader, "Application name", "My Basecamp CLI")
23+
companyName := prompt(reader, "Company/Organization name", "")
24+
websiteURL := prompt(reader, "Website URL", "https://github.com/robzolkos/basecamp-cli")
25+
accessibleURL := prompt(reader, "URL where this computer is accessible (e.g., https://myhost.tailscale.ts.net)", "")
26+
27+
// Build redirect URI from accessible URL
28+
redirectURI := buildRedirectURI(accessibleURL)
29+
30+
fmt.Fprintln(os.Stderr)
31+
fmt.Fprintln(os.Stderr, strings.Repeat("=", 60))
32+
fmt.Fprintln(os.Stderr, "REGISTRATION INSTRUCTIONS")
33+
fmt.Fprintln(os.Stderr, strings.Repeat("=", 60))
34+
fmt.Fprintln(os.Stderr)
35+
fmt.Fprintln(os.Stderr, "1. Visit: https://launchpad.37signals.com/integrations")
36+
fmt.Fprintln(os.Stderr)
37+
fmt.Fprintln(os.Stderr, "2. Click 'Register another application'")
38+
fmt.Fprintln(os.Stderr)
39+
fmt.Fprintln(os.Stderr, "3. Fill out the form with these values:")
40+
fmt.Fprintln(os.Stderr)
41+
fmt.Fprintf(os.Stderr, " Name of your application: %s\n", appName)
42+
fmt.Fprintf(os.Stderr, " Your company/organization: %s\n", companyName)
43+
fmt.Fprintf(os.Stderr, " Website URL: %s\n", websiteURL)
44+
fmt.Fprintf(os.Stderr, " Redirect URI: %s\n", redirectURI)
45+
fmt.Fprintln(os.Stderr)
46+
fmt.Fprintln(os.Stderr, "4. After registering, copy your Client ID and Client Secret")
47+
fmt.Fprintln(os.Stderr)
48+
fmt.Fprintln(os.Stderr, "5. Run 'basecamp init' and enter the credentials when prompted")
49+
fmt.Fprintln(os.Stderr, " (use the same Redirect URI shown above)")
50+
fmt.Fprintln(os.Stderr)
51+
fmt.Fprintln(os.Stderr, "6. Run 'basecamp auth' to authenticate")
52+
fmt.Fprintln(os.Stderr, strings.Repeat("=", 60))
53+
54+
return PrintJSON(map[string]string{
55+
"application_name": appName,
56+
"company_name": companyName,
57+
"website_url": websiteURL,
58+
"redirect_uri": redirectURI,
59+
"registration_url": "https://launchpad.37signals.com/integrations",
60+
})
61+
}
62+
63+
func buildRedirectURI(accessibleURL string) string {
64+
if accessibleURL == "" {
65+
return "http://localhost:3002/callback"
66+
}
67+
68+
// Remove trailing slash if present
69+
accessibleURL = strings.TrimSuffix(accessibleURL, "/")
70+
71+
// Add port and callback path
72+
return accessibleURL + ":3002/callback"
73+
}

internal/commands/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Command interface {
1414
}
1515

1616
var commands = map[string]func() Command{
17+
"register": func() Command { return &RegisterCmd{} },
1718
"init": func() Command { return &InitCmd{} },
1819
"auth": func() Command { return &AuthCmd{} },
1920
"projects": func() Command { return &ProjectsCmd{} },
@@ -115,6 +116,7 @@ func printHelp(version string) {
115116
Usage: basecamp <command> [arguments] [flags]
116117
117118
Commands:
119+
register Generate OAuth app registration values
118120
init Configure credentials
119121
auth Authenticate with OAuth
120122
projects List all projects

0 commit comments

Comments
 (0)