Skip to content

Commit ea4fc5a

Browse files
authored
Merge pull request #64 from stainless-api/bruce/unified-init
Workflow improvements to `stl init` command
2 parents cdf2cf4 + 0fb7fa2 commit ea4fc5a

File tree

8 files changed

+468
-495
lines changed

8 files changed

+468
-495
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ For details about specific commands, use the `--help` flag.
6161
The CLI supports workspace configuration to avoid repeatedly specifying the project name. When you run a command, the CLI will:
6262

6363
1. Check if a project name is provided via command-line flag
64-
2. If not, look for a `stainless-workspace.json` file in the current directory or any parent directory
64+
2. If not, look for a `.stainless/workspace.json` file in the current directory or any parent directory
6565
3. Use the project name from the workspace configuration if found
6666

6767
### Initializing a Workspace

pkg/cmd/auth.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@ import (
1717
"github.com/stainless-api/stainless-api-go/option"
1818
"github.com/tidwall/gjson"
1919
"github.com/urfave/cli/v3"
20+
"golang.org/x/text/cases"
21+
"golang.org/x/text/language"
2022
)
2123

24+
const defaultClientID = "stl_client_0001u04Vo1IWoSe0Mwinw2SVuuO3hTkvL"
25+
2226
var authLogin = cli.Command{
2327
Name: "login",
2428
Usage: "Authenticate with Stainless API",
2529
Flags: []cli.Flag{
2630
&cli.StringFlag{
2731
Name: "client-id",
28-
Value: "stl_client_0001u04Vo1IWoSe0Mwinw2SVuuO3hTkvL",
32+
Value: defaultClientID,
2933
Usage: "OAuth client ID",
3034
},
3135
},
@@ -47,13 +51,9 @@ var authStatus = cli.Command{
4751
HideHelpCommand: true,
4852
}
4953

50-
func handleAuthLogin(ctx context.Context, cmd *cli.Command) error {
51-
cc := getAPICommandContext(cmd)
52-
clientID := cmd.String("client-id")
53-
scope := "openapi:read project:write project:read"
54-
authResult, err := startDeviceFlow(ctx, cmd, cc.client, clientID, scope)
55-
if err != nil {
56-
return err
54+
func authenticate(ctx context.Context, cmd *cli.Command, forceAuthentication bool) error {
55+
if apiKey := os.Getenv("STAINLESS_API_KEY"); apiKey != "" && !forceAuthentication {
56+
return nil
5757
}
5858

5959
config, err := NewAuthConfig()
@@ -62,6 +62,20 @@ func handleAuthLogin(ctx context.Context, cmd *cli.Command) error {
6262
return fmt.Errorf("authentication failed")
6363
}
6464

65+
if !forceAuthentication {
66+
if found, err := config.Find(); err == nil && found && config.AccessToken != "" {
67+
return nil
68+
}
69+
}
70+
71+
cc := getAPICommandContext(cmd)
72+
clientID := cmd.String("client-id")
73+
scope := "openapi:read project:write project:read"
74+
authResult, err := startDeviceFlow(ctx, cmd, cc.client, clientID, scope)
75+
if err != nil {
76+
return err
77+
}
78+
6579
config.AccessToken = authResult.AccessToken
6680
config.RefreshToken = authResult.RefreshToken
6781
config.TokenType = authResult.TokenType
@@ -70,10 +84,14 @@ func handleAuthLogin(ctx context.Context, cmd *cli.Command) error {
7084
Error("Failed to save authentication: %v", err)
7185
return fmt.Errorf("authentication failed")
7286
}
73-
Success("Authentication successful! Your credentials have been saved to " + config.ConfigPath)
87+
Success("Authentication successful! Your credentials have been saved to %s", config.ConfigPath)
7488
return nil
7589
}
7690

91+
func handleAuthLogin(ctx context.Context, cmd *cli.Command) error {
92+
return authenticate(ctx, cmd, true)
93+
}
94+
7795
func handleAuthLogout(ctx context.Context, cmd *cli.Command) error {
7896
config := &AuthConfig{}
7997
found, err := config.Find()
@@ -153,8 +171,7 @@ func startDeviceFlow(ctx context.Context, cmd *cli.Command, client stainless.Cli
153171
ok, _, err := group.Confirm(cmd, "browser", "Open browser?", "", true)
154172
if err != nil {
155173
return nil, err
156-
}
157-
if ok {
174+
} else if ok {
158175
if err := browser.OpenURL(deviceResponse.VerificationURIComplete); err == nil {
159176
group.Info("Opening browser...")
160177
} else {
@@ -244,7 +261,7 @@ func getDeviceName() string {
244261
case "linux":
245262
osName = "Linux"
246263
default:
247-
osName = strings.Title(osName)
264+
osName = cases.Title(language.English).String(osName)
248265
}
249266

250267
if username != "" {

pkg/cmd/authconfig.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,17 @@ func ConfigDir() (string, error) {
3434
// Returns (false, nil) if config file doesn't exist or is empty (not an error).
3535
// Returns (false, error) if config file exists but failed to load due to an error.
3636
func (config *AuthConfig) Find() (bool, error) {
37-
if config.ConfigPath != "" {
38-
return true, nil
39-
}
37+
if config.ConfigPath == "" {
38+
configDir, err := ConfigDir()
39+
if err != nil {
40+
return false, fmt.Errorf("failed to get config directory: %w", err)
41+
}
4042

41-
configDir, err := ConfigDir()
42-
if err != nil {
43-
return false, fmt.Errorf("failed to get config directory: %w", err)
43+
config.ConfigPath = filepath.Join(configDir, "auth.json")
4444
}
45-
46-
configPath := filepath.Join(configDir, "auth.json")
47-
if _, err := os.Stat(configPath); err == nil {
45+
if _, err := os.Stat(config.ConfigPath); err == nil {
4846
// Config file exists, attempt to load it
49-
err := config.Load(configPath)
47+
err := config.Load(config.ConfigPath)
5048
if err != nil {
5149
return false, err
5250
}
@@ -71,6 +69,7 @@ func (config *AuthConfig) Load(configPath string) error {
7169
if err != nil {
7270
if os.IsNotExist(err) {
7371
// File doesn't exist - this is not an error, just means no auth config
72+
fmt.Println("No config file!")
7473
return nil
7574
}
7675
return fmt.Errorf("failed to open auth config file %s: %w", configPath, err)
@@ -84,6 +83,7 @@ func (config *AuthConfig) Load(configPath string) error {
8483
}
8584
if info.Size() == 0 {
8685
// File exists but is empty - this is not an error, treat as no auth config
86+
fmt.Println("Empty config file!")
8787
return nil
8888
}
8989

pkg/cmd/form.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ func GetFormTheme(indent int) *huh.Theme {
1111
t := huh.ThemeBase()
1212

1313
grayBright := lipgloss.Color("251")
14-
gray := lipgloss.Color("8")
14+
gray := lipgloss.Color("243")
1515
primary := lipgloss.Color("6")
1616
primaryBright := lipgloss.Color("14")
1717
error := lipgloss.Color("1")
1818

1919
t.Form.Base = t.Form.Base.PaddingLeft(indent * 2)
20-
t.Group.Title = t.Group.Title.Foreground(gray).PaddingBottom(1)
20+
t.Group.Title = t.Group.Title.Foreground(primary).PaddingBottom(1)
21+
t.Group.Description = t.Group.Description.Foreground(gray)
2122

22-
t.Focused.Title = t.Focused.Title.Bold(true)
23+
t.Focused.Title = t.Focused.Title.Foreground(primary).Bold(true)
2324
t.Focused.Base = t.Focused.Base.
2425
BorderLeft(false).
2526
SetString("\b\b" + lipgloss.NewStyle().Foreground(primaryBright).Render("✱")).
2627
PaddingLeft(2)
2728
t.Focused.Description = t.Focused.Description.Foreground(gray).Width(70)
2829
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(gray)
2930
t.Focused.SelectedPrefix = lipgloss.NewStyle().SetString("[✓] ")
31+
t.Focused.SelectedOption = lipgloss.NewStyle().Foreground(lipgloss.Color("75"))
3032

3133
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(error)
3234
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(error)

0 commit comments

Comments
 (0)