Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5a29351
Update install instructions for goreleaser
azzazzel Jul 29, 2025
7552185
Fix deprecated format
azzazzel Jul 29, 2025
53d5844
Update `goreleaser` install command
azzazzel Sep 19, 2025
52ba06c
Standard unit testing utilities for commands
azzazzel Sep 2, 2025
b97659d
`index describe`: make index name an argument (not a flag)
azzazzel Sep 2, 2025
ea51f0e
`index delete`: make index name an argument (not a flag)
azzazzel Sep 2, 2025
8d98364
`index configure`: make index name an argument (not a flag)
azzazzel Sep 2, 2025
cf04241
`index create`: make index name an argument (not a flag)
azzazzel Sep 2, 2025
86c779c
Extract `ValidateIndexNameArgs` util function
azzazzel Sep 2, 2025
fb61afe
Improve error message styling
azzazzel Sep 2, 2025
eace9b0
User-friendly error messages and `verbose` flag for details
azzazzel Sep 2, 2025
69a72ae
Add preview of what index will be created
azzazzel Aug 5, 2025
1f581e5
Extract a color/typography scheme for messages
azzazzel Sep 3, 2025
9774f43
Confirmation component
azzazzel Sep 3, 2025
d130b32
Add multi-line message boxes
azzazzel Sep 4, 2025
be29228
Add table rendering util
azzazzel Sep 4, 2025
dc6a81d
Don't use `pcio` to silence explicitly requested output
azzazzel Sep 5, 2025
e558610
Dedicated style for resource names
azzazzel Sep 5, 2025
62c658c
Index column mapping update/cleanup
azzazzel Sep 5, 2025
985a31f
Validation rules and respective presentation adjustments
azzazzel Sep 5, 2025
ce320ad
remove dead code
azzazzel Sep 5, 2025
bdcdba3
Fix validation issues with assumed values and improve error formatting
azzazzel Sep 8, 2025
8396c9e
Infer missing create index values based on provided ones
azzazzel Sep 10, 2025
8b9c26c
Add `models` command
azzazzel Sep 10, 2025
287d7e7
Introduce a local cache and use it to store models
azzazzel Sep 10, 2025
49d7015
Use the available models from the API for index creation and validation
azzazzel Sep 10, 2025
a704842
Implement a global `assume yes` option
azzazzel Sep 10, 2025
e582ac1
Add interactive mode for index creation
azzazzel Sep 10, 2025
601bbc2
Fix the bug causing cloud and environment to not be displayed
azzazzel Sep 10, 2025
d853bc7
remove needless info messages
azzazzel Sep 10, 2025
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
47 changes: 16 additions & 31 deletions internal/pkg/cli/command/apiKey/delete.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
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"
)

type DeleteApiKeyOptions struct {
apiKeyId string
skipConfirmation bool
apiKeyId string
}

func NewDeleteKeyCmd() *cobra.Command {
Expand All @@ -30,7 +26,8 @@ func NewDeleteKeyCmd() *cobra.Command {
GroupID: help.GROUP_API_KEYS.ID,
Example: heredoc.Doc(`
$ pc target -o "my-org" -p "my-project"
$ pc api-key delete -i "api-key-id"
$ pc api-key delete -i "api-key-id"
$ pc api-key delete -i "api-key-id" -y
`),
Run: func(cmd *cobra.Command, args []string) {
ac := sdk.NewPineconeAdminClient()
Expand All @@ -44,7 +41,9 @@ func NewDeleteKeyCmd() *cobra.Command {
exit.Error(err)
}

if !options.skipConfirmation {
// Check if -y flag is set
assumeYes, _ := cmd.Flags().GetBool("assume-yes")
if !assumeYes {
confirmDeleteApiKey(keyToDelete.Name)
}

Expand All @@ -60,34 +59,20 @@ func NewDeleteKeyCmd() *cobra.Command {
cmd.Flags().StringVarP(&options.apiKeyId, "id", "i", "", "The ID of the API key to delete")
_ = cmd.MarkFlagRequired("id")

cmd.Flags().BoolVar(&options.skipConfirmation, "skip-confirmation", false, "Skip deletion confirmation prompt")
return cmd
}

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
Loading
Loading