Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Link staging branch
if: github.repository == 'stainless-sdks/agentmail-cli'
run: |
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/agentmail-go'
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || true

- name: Bootstrap
run: ./scripts/bootstrap
Expand All @@ -60,7 +60,7 @@ jobs:
- name: Link staging branch
if: github.repository == 'stainless-sdks/agentmail-cli'
run: |
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/agentmail-go'
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || true

- name: Bootstrap
run: ./scripts/bootstrap
Expand Down Expand Up @@ -107,7 +107,7 @@ jobs:
- name: Link staging branch
if: github.repository == 'stainless-sdks/agentmail-cli'
run: |
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/agentmail-go'
./scripts/link 'github.com/stainless-sdks/agentmail-go@${{ github.ref_name }}' || true

- name: Bootstrap
run: ./scripts/bootstrap
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.7.8"
".": "0.7.9"
}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 0.7.9 (2026-04-10)

Full Changelog: [v0.7.8...v0.7.9](https://github.com/agentmail-to/agentmail-cli/compare/v0.7.8...v0.7.9)

### Bug Fixes

* **cli:** fix incompatible Go types for flag generated as array of maps ([18a9ddb](https://github.com/agentmail-to/agentmail-cli/commit/18a9ddb0f6a51f90aea0d205e26bd55a479d79ec))
* decouple npm publish from goreleaser so homebrew failures don't block npm ([3373dbc](https://github.com/agentmail-to/agentmail-cli/commit/3373dbc5b00b491ae265908e2e9d3b9fbe17d0a8))
* fix for failing to drop invalid module replace in link script ([d1c7780](https://github.com/agentmail-to/agentmail-cli/commit/d1c7780f5f80b69200626a96cb832bcd3a2c455a))


### Chores

* **cli:** additional test cases for `ShowJSONIterator` ([e96692a](https://github.com/agentmail-to/agentmail-cli/commit/e96692a5a3d9051faea76bfaf25889243f70ab0d))
* **cli:** let `--format raw` be used in conjunction with `--transform` ([cfdc42c](https://github.com/agentmail-to/agentmail-cli/commit/cfdc42cafa845ee617f35166aeeae6ad6a381c2a))

## 0.7.8 (2026-04-08)

Full Changelog: [v0.7.7...v0.7.8](https://github.com/agentmail-to/agentmail-cli/compare/v0.7.7...v0.7.8)
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var apiKeysCreate = requestflag.WithInnerFlags(cli.Command{
Usage: "Name of api key.",
BodyPath: "name",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "permissions",
Usage: "Granular permissions for the API key. When ommitted all permissions are granted. Otherwise, only permissions set to true are granted.",
BodyPath: "permissions",
Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/cmdutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func shouldUseColors(w io.Writer) bool {
}

func formatJSON(expectedOutput *os.File, title string, res gjson.Result, format string, transform string) ([]byte, error) {
if format != "raw" && transform != "" {
if transform != "" {
transformed := res.Get(transform)
if transformed.Exists() {
res = transformed
Expand Down Expand Up @@ -368,7 +368,7 @@ func formatJSON(expectedOutput *os.File, title string, res gjson.Result, format

// Display JSON to the user in various different formats
func ShowJSON(out *os.File, title string, res gjson.Result, format string, transform string) error {
if format != "raw" && transform != "" {
if transform != "" {
transformed := res.Get(transform)
if transformed.Exists() {
res = transformed
Expand Down
120 changes: 120 additions & 0 deletions pkg/cmd/cmdutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

"github.com/agentmail-to/agentmail-cli/internal/jsonview"
)

func TestStreamOutput(t *testing.T) {
Expand Down Expand Up @@ -148,3 +151,120 @@ func TestValidateBaseURL(t *testing.T) {
assert.Contains(t, err.Error(), "--base-url")
})
}

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

t.Run("RawWithTransform", func(t *testing.T) {
t.Parallel()

res := gjson.Parse(`{"id":"abc123","name":"test"}`)
formatted, err := formatJSON(os.Stdout, "test", res, "raw", "id")
require.NoError(t, err)
require.Equal(t, `"abc123"`+"\n", string(formatted))
})

t.Run("RawWithoutTransform", func(t *testing.T) {
t.Parallel()

res := gjson.Parse(`{"id":"abc123","name":"test"}`)
formatted, err := formatJSON(os.Stdout, "test", res, "raw", "")
require.NoError(t, err)
require.Equal(t, `{"id":"abc123","name":"test"}`+"\n", string(formatted))
})

t.Run("RawWithNestedTransform", func(t *testing.T) {
t.Parallel()

res := gjson.Parse(`{"data":{"items":[1,2,3]}}`)
formatted, err := formatJSON(os.Stdout, "test", res, "raw", "data.items")
require.NoError(t, err)
require.Equal(t, "[1,2,3]\n", string(formatted))
})

t.Run("RawWithNonexistentTransform", func(t *testing.T) {
t.Parallel()

res := gjson.Parse(`{"id":"abc123"}`)
formatted, err := formatJSON(os.Stdout, "test", res, "raw", "missing")
require.NoError(t, err)
// Transform path doesn't exist, so original result is returned
require.Equal(t, `{"id":"abc123"}`+"\n", string(formatted))
})
}

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

t.Run("RawMultipleItems", func(t *testing.T) {
t.Parallel()

iter := &sliceIterator[map[string]any]{items: []map[string]any{
{"id": "abc", "name": "first"},
{"id": "def", "name": "second"},
}}
captured := captureShowJSONIterator(t, iter, "raw", "", -1)
assert.Equal(t, `{"id":"abc","name":"first"}`+"\n"+`{"id":"def","name":"second"}`+"\n", captured)
})

t.Run("RawWithTransform", func(t *testing.T) {
t.Parallel()

iter := &sliceIterator[map[string]any]{items: []map[string]any{
{"id": "abc", "name": "first"},
{"id": "def", "name": "second"},
}}
captured := captureShowJSONIterator(t, iter, "raw", "id", -1)
assert.Equal(t, `"abc"`+"\n"+`"def"`+"\n", captured)
})

t.Run("LimitItems", func(t *testing.T) {
t.Parallel()

iter := &sliceIterator[map[string]any]{items: []map[string]any{
{"id": "abc"},
{"id": "def"},
{"id": "ghi"},
}}
captured := captureShowJSONIterator(t, iter, "raw", "", 2)
assert.Equal(t, `{"id":"abc"}`+"\n"+`{"id":"def"}`+"\n", captured)
})
}

// sliceIterator is a simple iterator over a slice for testing.
type sliceIterator[T any] struct {
index int
items []T
}

func (it *sliceIterator[T]) Next() bool {
it.index++
return it.index <= len(it.items)
}

func (it *sliceIterator[T]) Current() T {
return it.items[it.index-1]
}

func (it *sliceIterator[T]) Err() error {
return nil
}

var _ jsonview.Iterator[any] = (*sliceIterator[any])(nil)

// captureShowJSONIterator runs ShowJSONIterator and captures the output written to a file.
func captureShowJSONIterator[T any](t *testing.T, iter jsonview.Iterator[T], format, transform string, itemsToDisplay int64) string {
t.Helper()

r, w, err := os.Pipe()
require.NoError(t, err)
defer r.Close()

err = ShowJSONIterator(w, "test", iter, format, transform, itemsToDisplay)
w.Close()
require.NoError(t, err)

var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
return buf.String()
}
2 changes: 1 addition & 1 deletion pkg/cmd/inboxapikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var inboxesAPIKeysCreate = requestflag.WithInnerFlags(cli.Command{
Usage: "Name of api key.",
BodyPath: "name",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "permissions",
Usage: "Granular permissions for the API key. When ommitted all permissions are granted. Otherwise, only permissions set to true are granted.",
BodyPath: "permissions",
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/inboxmessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ var inboxesMessagesForward = requestflag.WithInnerFlags(cli.Command{
Name: "cc",
BodyPath: "cc",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "headers",
Usage: "Headers to include in message.",
BodyPath: "headers",
Expand Down Expand Up @@ -297,7 +297,7 @@ var inboxesMessagesReply = requestflag.WithInnerFlags(cli.Command{
Name: "cc",
BodyPath: "cc",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "headers",
Usage: "Headers to include in message.",
BodyPath: "headers",
Expand Down Expand Up @@ -388,7 +388,7 @@ var inboxesMessagesReplyAll = requestflag.WithInnerFlags(cli.Command{
Usage: "Attachments to include in message.",
BodyPath: "attachments",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "headers",
Usage: "Headers to include in message.",
BodyPath: "headers",
Expand Down Expand Up @@ -473,7 +473,7 @@ var inboxesMessagesSend = requestflag.WithInnerFlags(cli.Command{
Name: "cc",
BodyPath: "cc",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "headers",
Usage: "Headers to include in message.",
BodyPath: "headers",
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/podapikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var podsAPIKeysCreate = requestflag.WithInnerFlags(cli.Command{
Usage: "Name of api key.",
BodyPath: "name",
},
&requestflag.Flag[any]{
&requestflag.Flag[map[string]any]{
Name: "permissions",
Usage: "Granular permissions for the API key. When ommitted all permissions are granted. Otherwise, only permissions set to true are granted.",
BodyPath: "permissions",
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package cmd

const Version = "0.7.8" // x-release-please-version
const Version = "0.7.9" // x-release-please-version
8 changes: 6 additions & 2 deletions scripts/link
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/agentmail-to/agentmail-go,

REPLACEMENT="${1:-"../agentmail-go"}"
echo "==> Replacing Go SDK with $REPLACEMENT"
go mod edit -replace github.com/agentmail-to/agentmail-go="$REPLACEMENT"
go mod tidy -e
if [[ -d "$REPLACEMENT" ]] || go list -m "$REPLACEMENT" >/dev/null; then
go mod edit -replace github.com/agentmail-to/agentmail-go="$REPLACEMENT"
go mod tidy -e
else
echo "Skipping Go SDK replacement (branch may not exist on Go SDK)"
fi
Loading