Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ universal_binaries:
- replace: true

archives:
- format: tar.gz
- formats: ["tar.gz"]
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
Expand All @@ -42,7 +42,7 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
formats: ["zip"]

report_sizes: true

Expand Down
159 changes: 158 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
2. Install goreleaser

```
brew install goreleaser/tap/goreleaser
brew install --cask goreleaser/tap/goreleaser
```

3. Build the CLI
Expand Down Expand Up @@ -68,6 +68,163 @@ Some facts that could be useful:
- You can enable debug output with the `PINECONE_LOG_LEVEL=DEBUG` env var
- Are you pointed at the correct environment? The current value of the environment setting (i.e. prod or staging) is controlled through `pc config set-environment staging` is not clearly surfaced through the printed output. If things aren't working as you expect, you might be pointed in the wrong place. See `cat ~/.config/pinecone/config.yaml` to confirm.

## Development Practices & Tools

This project follows several established patterns and provides utilities to ensure consistency across the codebase.

### Output Functions & Quiet Mode

The CLI supports a `-q` (quiet) flag that suppresses non-essential output while preserving essential data. Follow these guidelines:

**Use `pcio` functions for:**

- User-facing messages (success, error, warning, info)
- Progress indicators and status updates
- Interactive prompts and confirmations
- Help text and documentation
- Any output that should be suppressed with `-q` flag

**Use `fmt` functions for:**

- Data output from informational commands (list, describe)
- JSON output that should always be displayed
- Table rendering and structured data display
- Any output that should NOT be suppressed with `-q` flag

```go
// ✅ Correct usage
pcio.Println("Creating index...") // User message - suppressed with -q
msg.SuccessMsg("Index created!") // User message - suppressed with -q
fmt.Println(jsonData) // Data output - always displayed

// ❌ Incorrect usage
pcio.Println(jsonData) // Wrong! Data would be suppressed
fmt.Println("Creating index...") // Wrong! Ignores quiet mode
```

### Error Handling

Use the centralized error handling utilities:

```go
// For API errors with structured responses
errorutil.HandleIndexAPIError(err, cmd, args)

// For program termination
exit.Error(err) // Logs error and exits with code 1
exit.ErrorMsg("msg") // Logs message and exits with code 1
exit.Success() // Logs success and exits with code 0
```

### User Messages & Styling

Use the `msg` package for consistent user messaging:

```go
msg.SuccessMsg("Operation completed successfully!")
msg.FailMsg("Operation failed: %s", err)
msg.WarnMsg("This will delete the resource")
msg.InfoMsg("Processing...")
msg.HintMsg("Use --help for more options")

// Multi-line messages
msg.WarnMsgMultiLine("Warning 1", "Warning 2", "Warning 3")
```

Use the `style` package for consistent text formatting:

```go
style.Heading("Section Title")
style.Emphasis("important text")
style.Code("command-name")
style.URL("https://example.com")
```

### Interactive Components

For user confirmations, use the interactive package:

```go
result := interactive.AskForConfirmation("Delete this resource?")
switch result {
case interactive.ConfirmationYes:
// Proceed with deletion
case interactive.ConfirmationNo:
// Cancel operation
case interactive.ConfirmationQuit:
// Exit program
}
```

### Table Rendering

Use the `presenters` package for consistent table output:

```go
// For data tables (always displayed, not suppressed by -q)
presenters.PrintTable(presenters.TableOptions{
Columns: []presenters.Column{{Title: "Name", Width: 20}},
Rows: []presenters.Row{{"example"}},
})

// For index-specific tables
presenters.PrintIndexTableWithIndexAttributesGroups(indexes, groups)
```

### Testing Utilities

Use the `testutils` package for consistent command testing:

```go
// Test command arguments and flags
tests := []testutils.CommandTestConfig{
{
Name: "valid arguments",
Args: []string{"my-arg"},
Flags: map[string]string{"json": "true"},
ExpectError: false,
ExpectedArgs: []string{"my-arg"},
},
}
testutils.TestCommandArgsAndFlags(t, cmd, tests)

// Test JSON flag configuration
testutils.AssertJSONFlag(t, cmd)
```

### Validation Utilities

Use centralized validation functions:

```go
// For index name validation
index.ValidateIndexNameArgs(cmd, args)

// For other validations, check the respective utility packages
```

### Logging

Use structured logging with the `log` package:

```go
log.Debug().Str("index", name).Msg("Creating index")
log.Error().Err(err).Msg("Failed to create index")
log.Info().Msg("Operation completed")
```

### Configuration Management

Use the configuration utilities for consistent config handling:

```go
// Get current state
org := state.TargetOrg.Get()
proj := state.TargetProj.Get()

// Configuration files are managed through the config package
```

## Making a Pull Request

Please fork this repo and make a PR with your changes. Run `gofmt` and `goimports` on all proposed
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.23.0

require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/briandowns/spinner v1.23.0
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.10.0
Expand All @@ -13,6 +12,7 @@ require (
github.com/pinecone-io/go-pinecone/v4 v4.1.4
github.com/rs/zerolog v1.32.0
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.18.2
golang.org/x/oauth2 v0.30.0
golang.org/x/term v0.33.0
Expand Down Expand Up @@ -49,7 +49,6 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
Expand Down
36 changes: 10 additions & 26 deletions internal/pkg/cli/command/apiKey/delete.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package apiKey

import (
"bufio"
"fmt"
"os"
"strings"

"github.com/MakeNowJust/heredoc"
"github.com/pinecone-io/cli/internal/pkg/utils/configuration/state"
"github.com/pinecone-io/cli/internal/pkg/utils/exit"
"github.com/pinecone-io/cli/internal/pkg/utils/help"
"github.com/pinecone-io/cli/internal/pkg/utils/interactive"
"github.com/pinecone-io/cli/internal/pkg/utils/msg"
"github.com/pinecone-io/cli/internal/pkg/utils/pcio"
"github.com/pinecone-io/cli/internal/pkg/utils/sdk"
"github.com/pinecone-io/cli/internal/pkg/utils/style"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -65,29 +62,16 @@ func NewDeleteKeyCmd() *cobra.Command {
}

func confirmDeleteApiKey(apiKeyName string) {
msg.WarnMsg("This operation will delete API Key %s from project %s.", style.Emphasis(apiKeyName), style.Emphasis(state.TargetProj.Get().Name))
msg.WarnMsg("Any integrations you have that auth with this API Key will stop working.")
msg.WarnMsg("This action cannot be undone.")

// Prompt the user
fmt.Print("Do you want to continue? (y/N): ")

// Read the user's input
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("Error reading input:", err)
return
}

// Trim any whitespace from the input and convert to lowercase
input = strings.TrimSpace(strings.ToLower(input))
msg.WarnMsgMultiLine(
pcio.Sprintf("This operation will delete API Key %s from project %s.", style.Emphasis(apiKeyName), style.Emphasis(state.TargetProj.Get().Name)),
"Any integrations you have that auth with this API Key will stop working.",
"This action cannot be undone.",
)

// Check if the user entered "y" or "yes"
if input == "y" || input == "yes" {
msg.InfoMsg("You chose to continue delete.")
} else {
question := "Are you sure you want to proceed with deleting this API key?"
if !interactive.GetConfirmation(question) {
msg.InfoMsg("Operation canceled.")
exit.Success()
}
msg.InfoMsg("You chose to continue delete.")
}
18 changes: 9 additions & 9 deletions internal/pkg/cli/command/apiKey/list.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package apiKey

import (
"fmt"
"sort"
"strings"

Expand All @@ -9,7 +10,6 @@ import (
"github.com/pinecone-io/cli/internal/pkg/utils/exit"
"github.com/pinecone-io/cli/internal/pkg/utils/help"
"github.com/pinecone-io/cli/internal/pkg/utils/msg"
"github.com/pinecone-io/cli/internal/pkg/utils/pcio"
"github.com/pinecone-io/cli/internal/pkg/utils/presenters"
"github.com/pinecone-io/cli/internal/pkg/utils/sdk"
"github.com/pinecone-io/cli/internal/pkg/utils/style"
Expand Down Expand Up @@ -61,7 +61,7 @@ func NewListKeysCmd() *cobra.Command {

if options.json {
json := text.IndentJSON(sortedKeys)
pcio.Println(json)
fmt.Println(json)
} else {
printTable(sortedKeys)
}
Expand All @@ -74,17 +74,17 @@ func NewListKeysCmd() *cobra.Command {
}

func printTable(keys []*pinecone.APIKey) {
pcio.Printf("Organization: %s (ID: %s)\n", style.Emphasis(state.TargetOrg.Get().Name), style.Emphasis(state.TargetOrg.Get().Id))
pcio.Printf("Project: %s (ID: %s)\n", style.Emphasis(state.TargetProj.Get().Name), style.Emphasis(state.TargetProj.Get().Id))
pcio.Println()
pcio.Println(style.Heading("API Keys"))
pcio.Println()
fmt.Printf("Organization: %s (ID: %s)\n", style.Emphasis(state.TargetOrg.Get().Name), style.Emphasis(state.TargetOrg.Get().Id))
fmt.Printf("Project: %s (ID: %s)\n", style.Emphasis(state.TargetProj.Get().Name), style.Emphasis(state.TargetProj.Get().Id))
fmt.Println()
fmt.Println(style.Heading("API Keys"))
fmt.Println()

writer := presenters.NewTabWriter()

columns := []string{"NAME", "ID", "PROJECT ID", "ROLES"}
header := strings.Join(columns, "\t") + "\n"
pcio.Fprint(writer, header)
fmt.Fprint(writer, header)

for _, key := range keys {
values := []string{
Expand All @@ -93,7 +93,7 @@ func printTable(keys []*pinecone.APIKey) {
key.ProjectId,
strings.Join(key.Roles, ", "),
}
pcio.Fprintf(writer, strings.Join(values, "\t")+"\n")
fmt.Fprintf(writer, strings.Join(values, "\t")+"\n")
}

writer.Flush()
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/cli/command/collection/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package collection

import (
"context"
"fmt"

"github.com/pinecone-io/cli/internal/pkg/utils/exit"
"github.com/pinecone-io/cli/internal/pkg/utils/msg"
"github.com/pinecone-io/cli/internal/pkg/utils/pcio"
"github.com/pinecone-io/cli/internal/pkg/utils/presenters"
"github.com/pinecone-io/cli/internal/pkg/utils/sdk"
"github.com/pinecone-io/cli/internal/pkg/utils/text"
Expand Down Expand Up @@ -35,7 +35,7 @@ func NewDescribeCollectionCmd() *cobra.Command {

if options.json {
json := text.IndentJSON(collection)
pcio.Println(json)
fmt.Println(json)
} else {
presenters.PrintDescribeCollectionTable(collection)
}
Expand Down
8 changes: 4 additions & 4 deletions internal/pkg/cli/command/collection/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package collection

import (
"context"
"fmt"
"os"
"sort"
"strconv"
Expand All @@ -10,7 +11,6 @@ import (

"github.com/pinecone-io/cli/internal/pkg/utils/exit"
"github.com/pinecone-io/cli/internal/pkg/utils/msg"
"github.com/pinecone-io/cli/internal/pkg/utils/pcio"
"github.com/pinecone-io/cli/internal/pkg/utils/sdk"
"github.com/pinecone-io/cli/internal/pkg/utils/text"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -45,7 +45,7 @@ func NewListCollectionsCmd() *cobra.Command {

if options.json {
json := text.IndentJSON(collections)
pcio.Println(json)
fmt.Println(json)
} else {
printTable(collections)
}
Expand All @@ -63,11 +63,11 @@ func printTable(collections []*pinecone.Collection) {

columns := []string{"NAME", "DIMENSION", "SIZE", "STATUS", "VECTORS", "ENVIRONMENT"}
header := strings.Join(columns, "\t") + "\n"
pcio.Fprint(writer, header)
fmt.Fprint(writer, header)

for _, coll := range collections {
values := []string{coll.Name, string(coll.Dimension), strconv.FormatInt(coll.Size, 10), string(coll.Status), string(coll.VectorCount), coll.Environment}
pcio.Fprintf(writer, strings.Join(values, "\t")+"\n")
fmt.Fprintf(writer, strings.Join(values, "\t")+"\n")
}
writer.Flush()
}
Loading
Loading