Skip to content

Commit 835d46a

Browse files
authored
Merge branch 'main' into mason/pool-column-browsers-list
2 parents c5d5516 + d1e6260 commit 835d46a

File tree

12 files changed

+167
-31
lines changed

12 files changed

+167
-31
lines changed

AGENTS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Kernel CLI
2+
3+
## Cursor Cloud specific instructions
4+
5+
This is a Go CLI application (no long-running services). Development commands are in the `Makefile`:
6+
7+
- **Build:** `make build` → produces `./bin/kernel`
8+
- **Test:** `make test` → runs `go vet` + `go test ./...`
9+
- **Lint:** `make lint` → runs `golangci-lint run` (requires `golangci-lint` on `PATH`)
10+
11+
### Testing against production
12+
13+
When `KERNEL_API_KEY` is set (provided as a secret), the CLI hits the **production** Kernel API. After `make build`, test with `./bin/kernel`. Useful smoke-test sequence:
14+
15+
1. `./bin/kernel auth status` — verify auth
16+
2. `./bin/kernel app list` — list deployed apps
17+
3. `./bin/kernel browsers create` — create a browser session (remember to delete it after)
18+
4. `./bin/kernel browsers delete <session_id>` — clean up
19+
20+
Be mindful that these operations affect production resources.
21+
22+
### Gotchas
23+
24+
- `golangci-lint` is installed via `go install` to `$(go env GOPATH)/bin`. This directory must be on `PATH` (the update script handles this via `.bashrc`).
25+
- The Makefile's `lint` target uses `|| true`, so it always exits 0 even when lint issues exist. Pre-existing lint warnings (errcheck, staticcheck) are present in the codebase and expected.
26+
- The `go-keyring` dependency requires D-Bus and `libsecret` on Linux. These are pre-installed in the Cloud VM.
27+
- `kernel create` works locally without authentication. Most other commands (`deploy`, `invoke`, `browsers`, etc.) require a `KERNEL_API_KEY` env var or `kernel login` OAuth flow.
28+
- Go module path is `github.com/kernel/cli`. The project requires Go 1.25.0 (specified in `go.mod`).

cmd/proxies/check.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func (p ProxyCmd) Check(ctx context.Context, in ProxyCheckInput) error {
4040
}
4141
rows = append(rows, []string{"Name", name})
4242
rows = append(rows, []string{"Type", string(proxy.Type)})
43+
rows = append(rows, []string{"Bypass Hosts", formatBypassHosts(proxy.BypassHosts)})
4344

4445
// Display protocol (default to https if not set)
4546
protocol := string(proxy.Protocol)

cmd/proxies/check_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package proxies
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/kernel/kernel-go-sdk"
8+
"github.com/kernel/kernel-go-sdk/option"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestProxyCheck_ShowsBypassHosts(t *testing.T) {
13+
buf := captureOutput(t)
14+
15+
fake := &FakeProxyService{
16+
CheckFunc: func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyCheckResponse, error) {
17+
return &kernel.ProxyCheckResponse{
18+
ID: id,
19+
Name: "Proxy 1",
20+
Type: kernel.ProxyCheckResponseTypeDatacenter,
21+
BypassHosts: []string{"localhost", "internal.service.local"},
22+
Status: kernel.ProxyCheckResponseStatusAvailable,
23+
}, nil
24+
},
25+
}
26+
27+
p := ProxyCmd{proxies: fake}
28+
err := p.Check(context.Background(), ProxyCheckInput{ID: "proxy-1"})
29+
30+
assert.NoError(t, err)
31+
output := buf.String()
32+
assert.Contains(t, output, "Bypass Hosts")
33+
assert.Contains(t, output, "localhost")
34+
assert.Contains(t, output, "internal.service.local")
35+
assert.Contains(t, output, "Proxy health check passed")
36+
}

cmd/proxies/create.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package proxies
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"github.com/kernel/cli/pkg/table"
89
"github.com/kernel/cli/pkg/util"
@@ -40,6 +41,9 @@ func (p ProxyCmd) Create(ctx context.Context, in ProxyCreateInput) error {
4041
if in.Name != "" {
4142
params.Name = kernel.Opt(in.Name)
4243
}
44+
if len(in.BypassHosts) > 0 {
45+
params.BypassHosts = normalizeBypassHosts(in.BypassHosts)
46+
}
4347

4448
// Build config based on type
4549
switch proxyType {
@@ -189,6 +193,7 @@ func (p ProxyCmd) Create(ctx context.Context, in ProxyCreateInput) error {
189193
}
190194
rows = append(rows, []string{"Name", name})
191195
rows = append(rows, []string{"Type", string(proxy.Type)})
196+
rows = append(rows, []string{"Bypass Hosts", formatBypassHosts(proxy.BypassHosts)})
192197

193198
// Display protocol (default to https if not set)
194199
protocol := string(proxy.Protocol)
@@ -219,26 +224,40 @@ func runProxiesCreate(cmd *cobra.Command, args []string) error {
219224
port, _ := cmd.Flags().GetInt("port")
220225
username, _ := cmd.Flags().GetString("username")
221226
password, _ := cmd.Flags().GetString("password")
227+
bypassHosts, _ := cmd.Flags().GetStringSlice("bypass-host")
222228

223229
output, _ := cmd.Flags().GetString("output")
224230

225231
svc := client.Proxies
226232
p := ProxyCmd{proxies: &svc}
227233
return p.Create(cmd.Context(), ProxyCreateInput{
228-
Name: name,
229-
Type: proxyType,
230-
Protocol: protocol,
231-
Country: country,
232-
City: city,
233-
State: state,
234-
Zip: zip,
235-
ASN: asn,
236-
OS: os,
237-
Carrier: carrier,
238-
Host: host,
239-
Port: port,
240-
Username: username,
241-
Password: password,
242-
Output: output,
234+
Name: name,
235+
Type: proxyType,
236+
Protocol: protocol,
237+
BypassHosts: bypassHosts,
238+
Country: country,
239+
City: city,
240+
State: state,
241+
Zip: zip,
242+
ASN: asn,
243+
OS: os,
244+
Carrier: carrier,
245+
Host: host,
246+
Port: port,
247+
Username: username,
248+
Password: password,
249+
Output: output,
243250
})
244251
}
252+
253+
func normalizeBypassHosts(hosts []string) []string {
254+
normalized := make([]string, 0, len(hosts))
255+
for _, host := range hosts {
256+
trimmed := strings.TrimSpace(host)
257+
if trimmed != "" {
258+
normalized = append(normalized, trimmed)
259+
}
260+
}
261+
262+
return normalized
263+
}

cmd/proxies/create_test.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,28 @@ func TestProxyCreate_Datacenter_Success(t *testing.T) {
1818
// Verify the request
1919
assert.Equal(t, kernel.ProxyNewParamsTypeDatacenter, body.Type)
2020
assert.Equal(t, "My DC Proxy", body.Name.Value)
21+
assert.Equal(t, []string{"localhost", "internal.service.local"}, body.BypassHosts)
2122

2223
// Check config
2324
dcConfig := body.Config.OfProxyNewsConfigDatacenterProxyConfig
2425
assert.NotNil(t, dcConfig)
2526
assert.Equal(t, "US", dcConfig.Country.Value)
2627

2728
return &kernel.ProxyNewResponse{
28-
ID: "dc-new",
29-
Name: "My DC Proxy",
30-
Type: kernel.ProxyNewResponseTypeDatacenter,
29+
ID: "dc-new",
30+
Name: "My DC Proxy",
31+
Type: kernel.ProxyNewResponseTypeDatacenter,
32+
BypassHosts: []string{"localhost", "internal.service.local"},
3133
}, nil
3234
},
3335
}
3436

3537
p := ProxyCmd{proxies: fake}
3638
err := p.Create(context.Background(), ProxyCreateInput{
37-
Name: "My DC Proxy",
38-
Type: "datacenter",
39-
Country: "US",
39+
Name: "My DC Proxy",
40+
Type: "datacenter",
41+
Country: "US",
42+
BypassHosts: []string{"localhost", "internal.service.local"},
4043
})
4144

4245
assert.NoError(t, err)
@@ -46,6 +49,9 @@ func TestProxyCreate_Datacenter_Success(t *testing.T) {
4649
assert.Contains(t, output, "Successfully created proxy")
4750
assert.Contains(t, output, "dc-new")
4851
assert.Contains(t, output, "My DC Proxy")
52+
assert.Contains(t, output, "Bypass Hosts")
53+
assert.Contains(t, output, "localhost")
54+
assert.Contains(t, output, "internal.service.local")
4955
}
5056

5157
func TestProxyCreate_Datacenter_WithoutCountry(t *testing.T) {
@@ -306,6 +312,27 @@ func TestProxyCreate_Protocol_Invalid(t *testing.T) {
306312
assert.Contains(t, err.Error(), "invalid protocol: ftp")
307313
}
308314

315+
func TestProxyCreate_BypassHosts_Normalized(t *testing.T) {
316+
fake := &FakeProxyService{
317+
NewFunc: func(ctx context.Context, body kernel.ProxyNewParams, opts ...option.RequestOption) (*kernel.ProxyNewResponse, error) {
318+
assert.Equal(t, []string{"localhost", "internal.service.local"}, body.BypassHosts)
319+
return &kernel.ProxyNewResponse{
320+
ID: "test-proxy",
321+
Type: kernel.ProxyNewResponseTypeDatacenter,
322+
}, nil
323+
},
324+
}
325+
326+
p := ProxyCmd{proxies: fake}
327+
err := p.Create(context.Background(), ProxyCreateInput{
328+
Type: "datacenter",
329+
Country: "US",
330+
BypassHosts: []string{" localhost ", "", "internal.service.local"},
331+
})
332+
333+
assert.NoError(t, err)
334+
}
335+
309336
func TestProxyCreate_APIError(t *testing.T) {
310337
_ = captureOutput(t)
311338

cmd/proxies/format.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package proxies
2+
3+
import "strings"
4+
5+
func formatBypassHosts(hosts []string) string {
6+
if len(hosts) == 0 {
7+
return "-"
8+
}
9+
10+
return strings.Join(hosts, ", ")
11+
}

cmd/proxies/get.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func (p ProxyCmd) Get(ctx context.Context, in ProxyGetInput) error {
3636
}
3737
rows = append(rows, []string{"Name", name})
3838
rows = append(rows, []string{"Type", string(item.Type)})
39+
rows = append(rows, []string{"Bypass Hosts", formatBypassHosts(item.BypassHosts)})
3940

4041
// Display protocol (default to https if not set)
4142
protocol := string(item.Protocol)

cmd/proxies/get_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ func TestProxyGet_Datacenter(t *testing.T) {
1717
fake := &FakeProxyService{
1818
GetFunc: func(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.ProxyGetResponse, error) {
1919
return &kernel.ProxyGetResponse{
20-
ID: "dc-1",
21-
Name: "US Datacenter",
22-
Type: kernel.ProxyGetResponseTypeDatacenter,
20+
ID: "dc-1",
21+
Name: "US Datacenter",
22+
Type: kernel.ProxyGetResponseTypeDatacenter,
23+
BypassHosts: []string{"localhost", "internal.service.local"},
2324
Config: kernel.ProxyGetResponseConfigUnion{
2425
Country: "US",
2526
},
@@ -40,6 +41,9 @@ func TestProxyGet_Datacenter(t *testing.T) {
4041
assert.Contains(t, output, "US Datacenter")
4142
assert.Contains(t, output, "Type")
4243
assert.Contains(t, output, "datacenter")
44+
assert.Contains(t, output, "Bypass Hosts")
45+
assert.Contains(t, output, "localhost")
46+
assert.Contains(t, output, "internal.service.local")
4347
assert.Contains(t, output, "Country")
4448
assert.Contains(t, output, "US")
4549
}

cmd/proxies/list.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (p ProxyCmd) List(ctx context.Context, in ProxyListInput) error {
4141

4242
// Prepare table data
4343
tableData := pterm.TableData{
44-
{"ID", "Name", "Type", "Protocol", "Config", "Status", "Last Checked"},
44+
{"ID", "Name", "Type", "Protocol", "Bypass Hosts", "Config", "Status", "Last Checked"},
4545
}
4646

4747
for _, proxy := range *items {
@@ -77,6 +77,7 @@ func (p ProxyCmd) List(ctx context.Context, in ProxyListInput) error {
7777
name,
7878
string(proxy.Type),
7979
protocol,
80+
formatBypassHosts(proxy.BypassHosts),
8081
configStr,
8182
status,
8283
lastChecked,

cmd/proxies/list_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ func TestProxyList_WithProxies(t *testing.T) {
3434
createResidentialProxy("res-1", "SF Residential", "US", "sanfrancisco", "CA"),
3535
createCustomProxy("custom-1", "My Proxy", "proxy.example.com", 8080),
3636
{
37-
ID: "mobile-1",
38-
Name: "Mobile Proxy",
39-
Type: kernel.ProxyListResponseTypeMobile,
37+
ID: "mobile-1",
38+
Name: "Mobile Proxy",
39+
Type: kernel.ProxyListResponseTypeMobile,
40+
BypassHosts: []string{"abc"},
4041
Config: kernel.ProxyListResponseConfigUnion{
4142
Country: "US",
4243
Carrier: "verizon",
@@ -85,6 +86,7 @@ func TestProxyList_WithProxies(t *testing.T) {
8586

8687
assert.Contains(t, output, "mobile-1")
8788
assert.Contains(t, output, "mobile")
89+
assert.Contains(t, output, "abc")
8890

8991
assert.Contains(t, output, "isp-1")
9092
assert.Contains(t, output, "-") // Empty name shows as "-"

0 commit comments

Comments
 (0)