Skip to content

Commit d80adb8

Browse files
committed
chore: add a shortcut to copy
1 parent 78b4094 commit d80adb8

5 files changed

Lines changed: 94 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ nuon api --list
8080
| Key | Action |
8181
| --------- | ------------------------------------- |
8282
| **enter** | Select endpoint - print to screen |
83+
| **c** | Copy endpoint path for CLI reuse |
8384
| **x** | Execute endpoint (only GET supported) |
8485
| **B** | Open Swagger docs in browser |
8586
| **/** | Filter/Fuzzy-Search |

cmd/root.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"strings"
77

8+
"github.com/atotto/clipboard"
89
"github.com/spf13/cobra"
910

1011
"github.com/nuonco/nuon-ext-api/internal/client"
@@ -96,9 +97,23 @@ func runAPI(cmd *cobra.Command, args []string) error {
9697
}
9798
switch result.Action {
9899
case browser.ActionSelect:
99-
fmt.Println(result.Route.DisplayName())
100+
if result.Route != nil {
101+
fmt.Println(result.Route.DisplayName())
102+
}
103+
return nil
104+
case browser.ActionCopy:
105+
if result.Route == nil {
106+
return nil
107+
}
108+
if err := clipboard.WriteAll(result.Route.Path); err != nil {
109+
fmt.Fprintf(os.Stderr, "warning: failed to copy path to clipboard: %v\n", err)
110+
}
111+
fmt.Println(result.Route.Path)
100112
return nil
101113
case browser.ActionExecute:
114+
if result.Route == nil {
115+
return nil
116+
}
102117
// Fall through to execute the GET request
103118
args = []string{result.Route.Path}
104119
default:

internal/config/config_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package config
2+
3+
import "testing"
4+
5+
func TestLoadReadsInstallIDFromEnv(t *testing.T) {
6+
t.Setenv("NUON_INSTALL_ID", "inst_123")
7+
8+
cfg := Load()
9+
if cfg.InstallID != "inst_123" {
10+
t.Fatalf("expected InstallID to be %q, got %q", "inst_123", cfg.InstallID)
11+
}
12+
}

internal/pkg/tui/browser/browser.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
ActionNone Action = iota // user quit without selecting
2222
ActionSelect // user pressed enter — print the route
2323
ActionExecute // user pressed x — execute the GET endpoint
24+
ActionCopy // user pressed c — copy the route path
2425
)
2526

2627
// Result is returned after the browser exits.
@@ -58,6 +59,10 @@ func Run(api *spec.API, apiURL string) (*Result, error) {
5859
key.WithKeys("B"),
5960
key.WithHelp("B", "open docs"),
6061
),
62+
Copy: key.NewBinding(
63+
key.WithKeys("c"),
64+
key.WithHelp("c", "copy path"),
65+
),
6166
Execute: key.NewBinding(
6267
key.WithKeys("x"),
6368
key.WithHelp("x", "execute GET"),
@@ -69,10 +74,10 @@ func Run(api *spec.API, apiURL string) (*Result, error) {
6974
}
7075

7176
l.AdditionalShortHelpKeys = func() []key.Binding {
72-
return []key.Binding{keys.Select, keys.Open, keys.Execute}
77+
return []key.Binding{keys.Select, keys.Copy, keys.Open, keys.Execute}
7378
}
7479
l.AdditionalFullHelpKeys = func() []key.Binding {
75-
return []key.Binding{keys.Select, keys.Open, keys.Execute}
80+
return []key.Binding{keys.Select, keys.Copy, keys.Open, keys.Execute}
7681
}
7782

7883
m := model{list: l, keys: keys, apiURL: apiURL}
@@ -91,6 +96,7 @@ func Run(api *spec.API, apiURL string) (*Result, error) {
9196

9297
type keyMap struct {
9398
Open key.Binding
99+
Copy key.Binding
94100
Execute key.Binding
95101
Select key.Binding
96102
}
@@ -131,6 +137,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
131137
}
132138
}
133139
return m, nil
140+
case key.Matches(msg, m.keys.Copy):
141+
if item, ok := m.list.SelectedItem().(routeItem); ok {
142+
r := item.route
143+
m.result = &Result{Route: &r, Action: ActionCopy}
144+
return m, tea.Quit
145+
}
146+
return m, nil
134147
case msg.String() == "enter":
135148
if item, ok := m.list.SelectedItem().(routeItem); ok {
136149
r := item.route
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package browser
2+
3+
import (
4+
"testing"
5+
6+
"github.com/charmbracelet/bubbles/key"
7+
"github.com/charmbracelet/bubbles/list"
8+
tea "github.com/charmbracelet/bubbletea"
9+
10+
"github.com/nuonco/nuon-ext-api/internal/spec"
11+
)
12+
13+
func TestModelUpdateCopyAction(t *testing.T) {
14+
route := spec.Route{Method: "GET", Path: "/v1/apps"}
15+
items := []list.Item{routeItem{route: route}}
16+
17+
l := list.New(items, list.NewDefaultDelegate(), 80, 24)
18+
m := model{
19+
list: l,
20+
keys: keyMap{
21+
Open: key.NewBinding(key.WithKeys("B")),
22+
Copy: key.NewBinding(key.WithKeys("c")),
23+
Execute: key.NewBinding(key.WithKeys("x")),
24+
Select: key.NewBinding(key.WithKeys("enter")),
25+
},
26+
}
27+
28+
updatedModel, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'c'}})
29+
if cmd == nil {
30+
t.Fatal("expected copy action to return a quit command")
31+
}
32+
33+
updated, ok := updatedModel.(model)
34+
if !ok {
35+
t.Fatalf("expected model type, got %T", updatedModel)
36+
}
37+
38+
if updated.result == nil {
39+
t.Fatal("expected result to be set")
40+
}
41+
if updated.result.Action != ActionCopy {
42+
t.Fatalf("expected action %v, got %v", ActionCopy, updated.result.Action)
43+
}
44+
if updated.result.Route == nil {
45+
t.Fatal("expected route to be set")
46+
}
47+
if updated.result.Route.Path != route.Path {
48+
t.Fatalf("expected copied path %q, got %q", route.Path, updated.result.Route.Path)
49+
}
50+
}

0 commit comments

Comments
 (0)