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
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.1.1"
".": "0.2.0"
}
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.2.0 (2026-03-03)

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

### Features

* add support for file downloads from binary response endpoints ([2e0b0a7](https://github.com/beeper/desktop-api-cli/commit/2e0b0a7080adef772b1c2b9f33d957f267d354ad))
* improved documentation and flags for client options ([46e772c](https://github.com/beeper/desktop-api-cli/commit/46e772c72af1ea406d17ddaa7aeaf58e8b917a16))


### Bug Fixes

* avoid printing usage errors twice ([62fbdae](https://github.com/beeper/desktop-api-cli/commit/62fbdaedc3cf1724fc9102eae7dcb87e148aabb7))
* more gracefully handle empty stdin input ([d758018](https://github.com/beeper/desktop-api-cli/commit/d75801861b11dfd1cb5568e2b5da3eb0176b7c21))


### Chores

* **internal:** codegen related update ([2fcd536](https://github.com/beeper/desktop-api-cli/commit/2fcd53660f6195309b786d5b50aaeb22595a5404))
* zip READMEs as part of build artifact ([d1a1267](https://github.com/beeper/desktop-api-cli/commit/d1a12679c7c0366a8a50972010874bc9e9a6d643))

## 0.1.1 (2026-02-25)

Full Changelog: [v0.1.0...v0.1.1](https://github.com/beeper/desktop-api-cli/compare/v0.1.0...v0.1.1)
Expand Down
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,22 @@ beeper-desktop-cli [resource] <command> [flags...]

```sh
beeper-desktop-cli chats search \
--account-id local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc \
--account-id local-telegram_ba_QFrb5lrLPhO3OT5MFBeTWv0x4BI \
--cursor '1725489123456|c29tZUltc2dQYWdl' \
--direction before \
--inbox primary \
--include-muted \
--last-activity-after 2019-12-27T18:11:19.117Z \
--last-activity-before 2019-12-27T18:11:19.117Z \
--limit 3 \
--query x \
--scope titles \
--type single \
--unread-only
--type single
```

For details about specific commands, use the `--help` flag.

### Global Flags
### Environment variables

| Environment variable | Description | Required |
| --------------------- | ----------------------------------------------------------------------------------------------------- | -------- |
| `BEEPER_ACCESS_TOKEN` | Bearer access token obtained via OAuth2 PKCE flow or created in-app. Required for all API operations. | yes |

### Global flags

- `--access-token` - Bearer access token obtained via OAuth2 PKCE flow or created in-app. Required for all API operations. (can also be set with `BEEPER_ACCESS_TOKEN` env var)
- `--help` - Show command line usage
- `--debug` - Enable debug logging (includes HTTP request/response details)
- `--version`, `-v` - Show the CLI version
Expand Down
6 changes: 5 additions & 1 deletion cmd/beeper-desktop-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ func main() {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
} else {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
if cmd.CommandErrorBuffer.Len() > 0 {
os.Stderr.Write(cmd.CommandErrorBuffer.Bytes())
} else {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
}
os.Exit(exitCode)
}
Expand Down
40 changes: 29 additions & 11 deletions internal/requestflag/requestflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ type Flag[
[]float64 | []int64 | []bool | any | map[string]any | DateTimeValue | DateValue | TimeValue |
string | float64 | int64 | bool,
] struct {
Name string // name of the flag
Category string // category of the flag, if any
DefaultText string // default text of the flag for usage purposes
HideDefault bool // whether to hide the default value in output
Usage string // usage string for help output
Required bool // whether the flag is required or not
Hidden bool // whether to hide the flag in help output
Default T // default value for this flag if not set by from any source
Aliases []string // aliases that are allowed for this flag
Validator func(T) error // custom function to validate this flag value
Name string // name of the flag
Category string // category of the flag, if any
DefaultText string // default text of the flag for usage purposes
HideDefault bool // whether to hide the default value in output
Usage string // usage string for help output
Sources cli.ValueSourceChain // sources to load flag value from
Required bool // whether the flag is required or not
Hidden bool // whether to hide the flag in help output
Default T // default value for this flag if not set by from any source
Aliases []string // aliases that are allowed for this flag
Validator func(T) error // custom function to validate this flag value

QueryPath string // location in the request query string to put this flag's value
HeaderPath string // location in the request header to put this flag's value
Expand Down Expand Up @@ -127,6 +128,22 @@ func (f *Flag[T]) PreParse() error {
}

func (f *Flag[T]) PostParse() error {
if !f.hasBeenSet {
if val, source, found := f.Sources.LookupWithSource(); found {
if val != "" || reflect.TypeOf(f.value).Kind() == reflect.String {
if err := f.Set(f.Name, val); err != nil {
return fmt.Errorf(
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
val, f.value, source, f.Name, err,
)
}
} else if val == "" && reflect.TypeOf(f.value).Kind() == reflect.Bool {
_ = f.Set(f.Name, "false")
}

f.hasBeenSet = true
}
}
return nil
}

Expand Down Expand Up @@ -230,8 +247,9 @@ func (f *Flag[T]) GetDefaultText() string {
return f.DefaultText
}

// GetEnvVars returns the env vars for this flag
func (f *Flag[T]) GetEnvVars() []string {
return nil
return f.Sources.EnvKeys()
}

func (f *Flag[T]) IsDefaultVisible() bool {
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ func TestAccountsList(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"accounts", "list",
"--access-token", "string",
)
}
2 changes: 2 additions & 0 deletions pkg/cmd/accountcontact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestAccountsContactsList(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"accounts:contacts", "list",
"--access-token", "string",
"--account-id", "accountID",
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
"--direction", "before",
Expand All @@ -24,6 +25,7 @@ func TestAccountsContactsSearch(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"accounts:contacts", "search",
"--access-token", "string",
"--account-id", "accountID",
"--query", "x",
)
Expand Down
6 changes: 5 additions & 1 deletion pkg/cmd/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestAssetsDownload(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"assets", "download",
"--access-token", "string",
"--url", "mxc://example.org/Q4x9CqGz1pB3Oa6XgJ",
)
}
Expand All @@ -20,6 +21,7 @@ func TestAssetsServe(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"assets", "serve",
"--access-token", "string",
"--url", "x",
)
}
Expand All @@ -28,7 +30,8 @@ func TestAssetsUpload(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"assets", "upload",
"--file", "",
"--access-token", "string",
"--file", "...",
"--file-name", "fileName",
"--mime-type", "mimeType",
)
Expand All @@ -38,6 +41,7 @@ func TestAssetsUploadBase64(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"assets", "upload-base64",
"--access-token", "string",
"--content", "x",
"--file-name", "fileName",
"--mime-type", "mimeType",
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/beeperdesktopapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestFocus(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"focus",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--draft-attachment-path", "draftAttachmentPath",
"--draft-text", "draftText",
Expand All @@ -23,6 +24,7 @@ func TestSearch(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"search",
"--access-token", "string",
"--query", "x",
)
}
9 changes: 7 additions & 2 deletions pkg/cmd/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestChatsCreate(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats", "create",
"--access-token", "string",
"--account-id", "accountID",
"--allow-invite=true",
"--message-text", "messageText",
Expand Down Expand Up @@ -49,6 +50,7 @@ func TestChatsRetrieve(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats", "retrieve",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--max-participant-count", "50",
)
Expand All @@ -58,6 +60,7 @@ func TestChatsList(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats", "list",
"--access-token", "string",
"--account-id", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
"--account-id", "local-instagram_ba_eRfQMmnSNy_p7Ih7HL7RduRpKFU",
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
Expand All @@ -69,6 +72,7 @@ func TestChatsArchive(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats", "archive",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--archived=true",
)
Expand All @@ -78,14 +82,15 @@ func TestChatsSearch(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats", "search",
"--access-token", "string",
"--account-id", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
"--account-id", "local-telegram_ba_QFrb5lrLPhO3OT5MFBeTWv0x4BI",
"--cursor", "1725489123456|c29tZUltc2dQYWdl",
"--direction", "before",
"--inbox", "primary",
"--include-muted=true",
"--last-activity-after", "2019-12-27T18:11:19.117Z",
"--last-activity-before", "2019-12-27T18:11:19.117Z",
"--last-activity-after", "'2019-12-27T18:11:19.117Z'",
"--last-activity-before", "'2019-12-27T18:11:19.117Z'",
"--limit", "1",
"--query", "x",
"--scope", "titles",
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/chatmessagereaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func TestChatsMessagesReactionsDelete(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats:messages:reactions", "delete",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--message-id", "messageID",
"--reaction-key", "x",
Expand All @@ -22,6 +23,7 @@ func TestChatsMessagesReactionsAdd(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats:messages:reactions", "add",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--message-id", "messageID",
"--reaction-key", "x",
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/chatreminder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestChatsRemindersCreate(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats:reminders", "create",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
"--reminder", "{remindAtMs: 0, dismissOnIncomingMessage: true}",
)
Expand All @@ -34,6 +35,7 @@ func TestChatsRemindersDelete(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"chats:reminders", "delete",
"--access-token", "string",
"--chat-id", "!NCdzlIaMjZUmvmvyHU:beeper.com",
)
}
19 changes: 14 additions & 5 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package cmd

import (
"bytes"
"compress/gzip"
"context"
"fmt"
Expand All @@ -12,20 +13,23 @@ import (
"strings"

"github.com/beeper/desktop-api-cli/internal/autocomplete"
"github.com/beeper/desktop-api-cli/internal/requestflag"
docs "github.com/urfave/cli-docs/v3"
"github.com/urfave/cli/v3"
)

var (
Command *cli.Command
Command *cli.Command
CommandErrorBuffer bytes.Buffer
)

func init() {
Command = &cli.Command{
Name: "beeper-desktop-cli",
Usage: "CLI for the beeperdesktop API",
Suggest: true,
Version: Version,
Name: "beeper-desktop-cli",
Usage: "CLI for the beeperdesktop API",
Suggest: true,
Version: Version,
ErrWriter: &CommandErrorBuffer,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
Expand Down Expand Up @@ -66,6 +70,11 @@ func init() {
Name: "transform-error",
Usage: "The GJSON transformation for errors.",
},
&requestflag.Flag[string]{
Name: "access-token",
Usage: "Bearer access token obtained via OAuth2 PKCE flow or created in-app. Required for all API operations.",
Sources: cli.EnvVars("BEEPER_ACCESS_TOKEN"),
},
},
Commands: []*cli.Command{
&focus,
Expand Down
Loading
Loading