Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d18d972
chore(ci): skip lint on metadata-only changes
stainless-app[bot] Mar 25, 2026
c7c519c
chore(tests): bump steady to v0.19.7
stainless-app[bot] Mar 25, 2026
03e1c53
codegen metadata
stainless-app[bot] Mar 25, 2026
8ccee5c
codegen metadata
stainless-app[bot] Mar 25, 2026
42310f5
codegen metadata
stainless-app[bot] Mar 25, 2026
c332ed8
feat: set CLI flag constant values automatically where `x-stainless-c…
stainless-app[bot] Mar 27, 2026
8b749cf
chore: omit full usage information when missing required CLI parameters
stainless-app[bot] Mar 27, 2026
180bf35
chore(internal): update multipart form array serialization
stainless-app[bot] Mar 27, 2026
16566c5
fix: fix for off-by-one error in pagination logic
stainless-app[bot] Mar 28, 2026
533501d
chore(tests): bump steady to v0.20.1
stainless-app[bot] Apr 1, 2026
3a7b085
chore(tests): bump steady to v0.20.2
stainless-app[bot] Apr 1, 2026
6df0024
fix: handle empty data set using `--format explore`
stainless-app[bot] Apr 2, 2026
4af189d
fix: use `RawJSON` when iterating items with `--format explore` in th…
stainless-app[bot] Apr 2, 2026
ed64986
feat: binary-only parameters become CLI flags that take filenames only
stainless-app[bot] Apr 3, 2026
8d976d4
feat: better error message if scheme forgotten in CLI `*_BASE_URL`/`-…
stainless-app[bot] Apr 3, 2026
eacfea0
feat: allow `-` as value representing stdin to binary-only file param…
stainless-app[bot] Apr 3, 2026
c0de07e
chore: switch some CLI Go tests from `os.Chdir` to `t.Chdir`
stainless-app[bot] Apr 3, 2026
59678e1
chore: mark all CLI-related tests in Go with `t.Parallel()`
stainless-app[bot] Apr 3, 2026
9c4fd93
release: 0.4.0
stainless-app[bot] Apr 3, 2026
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
timeout-minutes: 10
name: lint
runs-on: ${{ github.repository == 'stainless-sdks/beeper-desktop-api-cli' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')

steps:
- uses: actions/checkout@v6
Expand All @@ -49,7 +49,7 @@ jobs:
contents: read
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/beeper-desktop-api-cli' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@v6

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.3.0"
".": "0.4.0"
}
2 changes: 1 addition & 1 deletion .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 23
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-5a8ac7b545c48dc892e5c680303e305254921554dabee848e40a808659dbcf1e.yml
openapi_spec_hash: 0103975601aac1445d3a4ef418c5d17a
config_hash: ca148af6be59ec54295b2c5f852a38d1
config_hash: 7d85c0b454fc78a59db6474c5c4d73c6
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## 0.4.0 (2026-04-03)

Full Changelog: [v0.3.0...v0.4.0](https://github.com/beeper/desktop-api-cli/compare/v0.3.0...v0.4.0)

### Features

* allow `-` as value representing stdin to binary-only file parameters in CLIs ([eacfea0](https://github.com/beeper/desktop-api-cli/commit/eacfea0f881332d854999d73092a4786d0b3ae33))
* better error message if scheme forgotten in CLI `*_BASE_URL`/`--base-url` ([8d976d4](https://github.com/beeper/desktop-api-cli/commit/8d976d441c86ddfaa67c8fad50a156f9374e0d16))
* binary-only parameters become CLI flags that take filenames only ([ed64986](https://github.com/beeper/desktop-api-cli/commit/ed64986c234a83e6c875a1202a7c217df9efa206))
* set CLI flag constant values automatically where `x-stainless-const` is set ([c332ed8](https://github.com/beeper/desktop-api-cli/commit/c332ed822bc6e94cb521620032bdcf941d4b7593))


### Bug Fixes

* fix for off-by-one error in pagination logic ([16566c5](https://github.com/beeper/desktop-api-cli/commit/16566c5d80a77de14f33e1895ec72cfa89da99c7))
* handle empty data set using `--format explore` ([6df0024](https://github.com/beeper/desktop-api-cli/commit/6df00248f562bbbd98e7cbccf8ddd82b35a40ae3))
* use `RawJSON` when iterating items with `--format explore` in the CLI ([4af189d](https://github.com/beeper/desktop-api-cli/commit/4af189dc0e5add5c654204e0e90c91e199de479a))


### Chores

* **ci:** skip lint on metadata-only changes ([d18d972](https://github.com/beeper/desktop-api-cli/commit/d18d972e88c1a03890fcbe3ba2f0dbffb06f5e48))
* **internal:** update multipart form array serialization ([180bf35](https://github.com/beeper/desktop-api-cli/commit/180bf353b36de3cfee9e9497dca4558622654e18))
* mark all CLI-related tests in Go with `t.Parallel()` ([59678e1](https://github.com/beeper/desktop-api-cli/commit/59678e17b692460f3fdc03ca82ba6270d13ed460))
* omit full usage information when missing required CLI parameters ([8b749cf](https://github.com/beeper/desktop-api-cli/commit/8b749cf09b595e66687d4030cee0791aff7e14c4))
* switch some CLI Go tests from `os.Chdir` to `t.Chdir` ([c0de07e](https://github.com/beeper/desktop-api-cli/commit/c0de07ef35b6d776781b0347a102c3855445f0cb))
* **tests:** bump steady to v0.19.7 ([c7c519c](https://github.com/beeper/desktop-api-cli/commit/c7c519cf80adffc7cebbd3ec220227baf48672d7))
* **tests:** bump steady to v0.20.1 ([533501d](https://github.com/beeper/desktop-api-cli/commit/533501d2960fc565d5891d5f610a7feef458d0b2))
* **tests:** bump steady to v0.20.2 ([3a7b085](https://github.com/beeper/desktop-api-cli/commit/3a7b085b2130d21ee0a2b434dbaa3efada1e1bb3))

## 0.3.0 (2026-03-24)

Full Changelog: [v0.2.0...v0.3.0](https://github.com/beeper/desktop-api-cli/compare/v0.2.0...v0.3.0)
Expand Down
7 changes: 7 additions & 0 deletions cmd/beeper-desktop-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ func main() {
prepareForAutocomplete(app)
}

if baseURL, ok := os.LookupEnv("BEEPER_DESKTOP_BASE_URL"); ok {
if err := cmd.ValidateBaseURL(baseURL, "BEEPER_DESKTOP_BASE_URL"); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}

if err := app.Run(context.Background(), os.Args); err != nil {
exitCode := 1

Expand Down
4 changes: 4 additions & 0 deletions internal/apiform/form_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ var tests = map[string]struct {
}

func TestEncode(t *testing.T) {
t.Parallel()

for name, test := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
writer.SetBoundary("xxx")
Expand Down
4 changes: 4 additions & 0 deletions internal/apiquery/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
)

func TestEncode(t *testing.T) {
t.Parallel()

tests := map[string]struct {
val any
settings QuerySettings
Expand Down Expand Up @@ -114,6 +116,8 @@ func TestEncode(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

query := map[string]any{"query": test.val}
values, err := MarshalWithSettings(query, test.settings)
if err != nil {
Expand Down
40 changes: 40 additions & 0 deletions internal/autocomplete/autocomplete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
)

func TestGetCompletions_EmptyArgs(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -26,6 +28,8 @@ func TestGetCompletions_EmptyArgs(t *testing.T) {
}

func TestGetCompletions_SubcommandPrefix(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -43,6 +47,8 @@ func TestGetCompletions_SubcommandPrefix(t *testing.T) {
}

func TestGetCompletions_HiddenCommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "visible", Usage: "Visible command"},
Expand All @@ -57,6 +63,8 @@ func TestGetCompletions_HiddenCommand(t *testing.T) {
}

func TestGetCompletions_NestedSubcommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -79,6 +87,8 @@ func TestGetCompletions_NestedSubcommand(t *testing.T) {
}

func TestGetCompletions_FlagCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -102,6 +112,8 @@ func TestGetCompletions_FlagCompletion(t *testing.T) {
}

func TestGetCompletions_ShortFlagCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -123,6 +135,8 @@ func TestGetCompletions_ShortFlagCompletion(t *testing.T) {
}

func TestGetCompletions_FileFlagBehavior(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -142,6 +156,8 @@ func TestGetCompletions_FileFlagBehavior(t *testing.T) {
}

func TestGetCompletions_NonBoolFlagValue(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -161,6 +177,8 @@ func TestGetCompletions_NonBoolFlagValue(t *testing.T) {
}

func TestGetCompletions_BoolFlagDoesNotBlockCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -185,6 +203,8 @@ func TestGetCompletions_BoolFlagDoesNotBlockCompletion(t *testing.T) {
}

func TestGetCompletions_ColonCommands_NoColonTyped(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -202,6 +222,8 @@ func TestGetCompletions_ColonCommands_NoColonTyped(t *testing.T) {
}

func TestGetCompletions_ColonCommands_ColonTyped_Bash(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -221,6 +243,8 @@ func TestGetCompletions_ColonCommands_ColonTyped_Bash(t *testing.T) {
}

func TestGetCompletions_ColonCommands_ColonTyped_Zsh(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -240,6 +264,8 @@ func TestGetCompletions_ColonCommands_ColonTyped_Zsh(t *testing.T) {
}

func TestGetCompletions_BashStyleColonCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -257,6 +283,8 @@ func TestGetCompletions_BashStyleColonCompletion(t *testing.T) {
}

func TestGetCompletions_BashStyleColonCompletion_NoMatch(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -271,6 +299,8 @@ func TestGetCompletions_BashStyleColonCompletion_NoMatch(t *testing.T) {
}

func TestGetCompletions_ZshStyleColonCompletion(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "config:get", Usage: "Get config value"},
Expand All @@ -287,6 +317,8 @@ func TestGetCompletions_ZshStyleColonCompletion(t *testing.T) {
}

func TestGetCompletions_MixedColonAndRegularCommands(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Usage: "Generate SDK"},
Expand All @@ -305,6 +337,8 @@ func TestGetCompletions_MixedColonAndRegularCommands(t *testing.T) {
}

func TestGetCompletions_FlagWithBoolFlagSkipsValue(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -329,6 +363,8 @@ func TestGetCompletions_FlagWithBoolFlagSkipsValue(t *testing.T) {
}

func TestGetCompletions_MultipleFlagsBeforeSubcommand(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand All @@ -353,6 +389,8 @@ func TestGetCompletions_MultipleFlagsBeforeSubcommand(t *testing.T) {
}

func TestGetCompletions_CommandAliases(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{Name: "generate", Aliases: []string{"gen", "g"}, Usage: "Generate SDK"},
Expand All @@ -372,6 +410,8 @@ func TestGetCompletions_CommandAliases(t *testing.T) {
}

func TestGetCompletions_AllFlagsWhenNoPrefix(t *testing.T) {
t.Parallel()

root := &cli.Command{
Commands: []*cli.Command{
{
Expand Down
38 changes: 35 additions & 3 deletions internal/jsonview/explorer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonview

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -309,6 +310,10 @@ func ExploreJSON(title string, json gjson.Result) error {
return err
}

type hasRawJSON interface {
RawJSON() string
}

// ExploreJSONStream explores JSON data loaded incrementally via an iterator
func ExploreJSONStream[T any](title string, it Iterator[T]) error {
anyIt := genericToAnyIterator(it)
Expand All @@ -327,12 +332,12 @@ func ExploreJSONStream[T any](title string, it Iterator[T]) error {
return err
}

// Convert items to JSON array
jsonBytes, err := json.Marshal(items)
arrayJSONBytes, err := marshalItemsToJSONArray(items)
if err != nil {
return err
}
arrayJSON := gjson.ParseBytes(jsonBytes)

arrayJSON := gjson.ParseBytes(arrayJSONBytes)
view, err := newTableView("", arrayJSON, false)
if err != nil {
return err
Expand All @@ -352,6 +357,29 @@ func ExploreJSONStream[T any](title string, it Iterator[T]) error {
return err
}

func marshalItemsToJSONArray(items []any) ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('[')

for i, item := range items {
if i > 0 {
buf.WriteByte(',')
}
if hasRaw, ok := item.(hasRawJSON); ok {
buf.WriteString(hasRaw.RawJSON())
} else {
jsonData, err := json.Marshal(item)
if err != nil {
return nil, err
}
buf.Write(jsonData)
}
}

buf.WriteByte(']')
return buf.Bytes(), nil
}

func (v *JSONViewer) current() JSONView { return v.stack[len(v.stack)-1] }
func (v *JSONViewer) Init() tea.Cmd { return nil }

Expand Down Expand Up @@ -406,6 +434,10 @@ func (v *JSONViewer) navigateForward() (tea.Model, tea.Cmd) {
return v, nil
}

if len(tableView.rowData) < 1 {
return v, nil
}

cursor := tableView.table.Cursor()
selected := tableView.rowData[cursor]
if !v.canNavigateInto(selected) {
Expand Down
Loading
Loading