diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index df3058e..9c8076e 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -9,7 +9,7 @@ jobs: release_doctor: name: release doctor runs-on: ubuntu-latest - if: github.repository == 'stainless-sdks/cerca-cli' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + if: github.repository == 'matrices/cerca-cli' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - uses: actions/checkout@v6 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1332969..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 0287a32..57e5ba5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 66 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/calvinfo-o4h6u5%2Fcerca-1ca6943fcf7d9c2630357db20c3d1eb4495d4ecab907f4e20f62508175b8ae5c.yml -openapi_spec_hash: 9c2f51f5495c1b592e96a9e0c9969f46 -config_hash: 9105f134a32978d1d1bfd7c6b30c2a3b +configured_endpoints: 64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/calvinfo-o4h6u5/cerca-a3610b6045d1d701859276e09dff35829bdf50509b762116860d8005831b0297.yml +openapi_spec_hash: 2a240632b5ef4814881e36247ec3c8dc +config_hash: 5d687451efd1d2898e81fa3ddb25f8ed diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..179eb98 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +## 0.1.0 (2026-05-01) + +Full Changelog: [v0.0.1...v0.1.0](https://github.com/matrices/cerca-cli/compare/v0.0.1...v0.1.0) + +### Features + +* **api:** api update ([4e41a15](https://github.com/matrices/cerca-cli/commit/4e41a1527e6c64b0a0607c7673eec684ec07293e)) +* **api:** api update ([f21169a](https://github.com/matrices/cerca-cli/commit/f21169af884518f4542d13b8a7c070a37d8a3c54)) +* **api:** api update ([464690c](https://github.com/matrices/cerca-cli/commit/464690ce55951cd052fb2e01a76d28912c7289b7)) +* **api:** manual updates ([7806afd](https://github.com/matrices/cerca-cli/commit/7806afd2cb58f1438f6aca04476ac95ae07daf39)) +* support passing path and query params over stdin ([67a8c65](https://github.com/matrices/cerca-cli/commit/67a8c655fe8e124855d5c73332063d477a627cad)) + + +### Bug Fixes + +* **cli:** correctly load zsh autocompletion ([b20f691](https://github.com/matrices/cerca-cli/commit/b20f6914f3ad282d91a18bf7557a693e5944a5d5)) +* flags for nullable body scalar fields are strictly typed ([88408b9](https://github.com/matrices/cerca-cli/commit/88408b9360bfaab9ef94198b1c6bf81897c86aec)) + + +### Chores + +* configure new SDK language ([bc82af5](https://github.com/matrices/cerca-cli/commit/bc82af5f10ac0102069a4e25d294df09185accee)) +* configure new SDK language ([ee21394](https://github.com/matrices/cerca-cli/commit/ee2139491a83fa2aec8d66305839ec8fd274de5d)) +* configure new SDK language ([a5f666b](https://github.com/matrices/cerca-cli/commit/a5f666bd240a8f0471b410c17e5ec7a93ba9b428)) +* configure new SDK language ([8a2d277](https://github.com/matrices/cerca-cli/commit/8a2d277426b9b83d9d4b0ab6ce7c76d9447d5f7d)) +* update SDK settings ([8254e9b](https://github.com/matrices/cerca-cli/commit/8254e9b96718b206c4b067528e93365fdb79f89a)) diff --git a/README.md b/README.md index c5d3aa9..1bf69eb 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ The official CLI for the [Cerca REST API](https://docs.cerca.dev). It is generated with [Stainless](https://www.stainless.com/). + + ## Installation ### Installing with Go @@ -11,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/). To test or install the CLI locally, you need [Go](https://go.dev/doc/install) version 1.22 or later installed. ```sh -go install 'github.com/stainless-sdks/cerca-cli/cmd/cerca@latest' +go install 'github.com/matrices/cerca-cli/cmd/cerca@latest' ``` Once you have run `go install`, the binary is placed in your Go bin directory: @@ -26,6 +28,8 @@ If commands aren't found after installation, add the Go bin directory to your PA export PATH="$PATH:$(go env GOPATH)/bin" ``` + + ### Running Locally After cloning the git repository for this project, you can use the @@ -47,7 +51,7 @@ cerca [resource] [flags...] cerca threads create \ --api-key 'My API Key' \ --agent-id agent_abc123 \ - --user-message "What's on my calendar today?" + --message "What's on my calendar today?" ``` For details about specific commands, use the `--help` flag. diff --git a/cmd/cerca/main.go b/cmd/cerca/main.go index a5db262..5dc606e 100644 --- a/cmd/cerca/main.go +++ b/cmd/cerca/main.go @@ -10,8 +10,8 @@ import ( "os" "slices" + "github.com/matrices/cerca-cli/pkg/cmd" "github.com/matrices/cerca-go" - "github.com/stainless-sdks/cerca-cli/pkg/cmd" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) diff --git a/go.mod b/go.mod index ee93a84..dc60d5a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/stainless-sdks/cerca-cli +module github.com/matrices/cerca-cli go 1.25 diff --git a/internal/autocomplete/shellscripts/zsh_autocomplete.zsh b/internal/autocomplete/shellscripts/zsh_autocomplete.zsh index 4d4bdcd..d937171 100644 --- a/internal/autocomplete/shellscripts/zsh_autocomplete.zsh +++ b/internal/autocomplete/shellscripts/zsh_autocomplete.zsh @@ -1,5 +1,4 @@ -#!/bin/zsh -compdef ____APPNAME___zsh_autocomplete __APPNAME__ +#compdef __APPNAME__ ____APPNAME___zsh_autocomplete() { @@ -44,3 +43,14 @@ ____APPNAME___zsh_autocomplete() { ;; esac } + +# When installed in fpath (e.g., via Homebrew's zsh_completion stanza), this file +# is autoloaded as the function ___APPNAME__ and its body becomes that function's +# body. Detect that case via funcstack and dispatch to the completion function. +# When sourced (e.g., `source <(__APPNAME__ @completion zsh)`), register the +# function with compdef instead. +if [[ "${funcstack[1]}" = "___APPNAME__" ]]; then + ____APPNAME___zsh_autocomplete "$@" +else + compdef ____APPNAME___zsh_autocomplete __APPNAME__ +fi diff --git a/internal/requestflag/innerflag.go b/internal/requestflag/innerflag.go index eeeb8bc..528915f 100644 --- a/internal/requestflag/innerflag.go +++ b/internal/requestflag/innerflag.go @@ -14,7 +14,8 @@ import ( type InnerFlag[ T []any | []map[string]any | []DateTimeValue | []DateValue | []TimeValue | []string | []float64 | []int64 | []bool | any | map[string]any | DateTimeValue | DateValue | TimeValue | - string | float64 | int64 | bool, + string | float64 | int64 | bool | + *string | *float64 | *int64 | *bool | *DateTimeValue | *DateValue | *TimeValue, ] struct { Name string // name of the flag DefaultText string // default text of the flag for usage purposes @@ -25,6 +26,12 @@ type InnerFlag[ OuterFlag cli.Flag // The flag on which this inner flag will set values InnerField string // The inner field which this flag will set DataAliases []string // alternate names recognized in YAML values passed as the outer flag + + // OuterIsArrayOfObjects tells an untyped outer flag (Flag[any], used for nullable + // complex schemas) to seed its underlying value as []map[string]any rather than + // map[string]any before SetInnerField runs. The hint is ignored for typed outer + // flags whose zero value already carries a dispatchable reflect.Kind. + OuterIsArrayOfObjects bool } // GetDataAliases returns the aliases recognized when parsing inner field keys from piped or flag YAML. @@ -76,6 +83,10 @@ func (f *InnerFlag[T]) Set(name string, rawVal string) error { } } + if seeder, ok := f.OuterFlag.(InnerFieldSeeder); ok { + seeder.SeedInnerCollection(f.OuterIsArrayOfObjects) + } + if settableInnerField, ok := f.OuterFlag.(SettableInnerField); ok { settableInnerField.SetInnerField(f.InnerField, parsedValue) } else { @@ -136,6 +147,9 @@ func (f *InnerFlag[T]) TypeName() string { if ty == nil { return "" } + if ty.Kind() == reflect.Pointer { + ty = ty.Elem() + } // Get base type name with special handling for built-in types getTypeName := func(t reflect.Type) string { diff --git a/internal/requestflag/requestflag.go b/internal/requestflag/requestflag.go index bfaf064..77c4f1f 100644 --- a/internal/requestflag/requestflag.go +++ b/internal/requestflag/requestflag.go @@ -1,6 +1,7 @@ package requestflag import ( + "encoding/json" "fmt" "reflect" "strconv" @@ -12,13 +13,38 @@ import ( "github.com/urfave/cli/v3" ) +// formatForFlagSet converts a Go value parsed from YAML/JSON stdin data into a string +// that flag.Set (and thus parseCLIArg) can parse correctly for each flag type. +// Strings are returned as-is (parseCLIArg[string] assigns the raw value directly, so +// JSON-quoting must be avoided). Scalars use %v. Complex types (maps, slices) are +// JSON-encoded, which the yaml.Unmarshal default branch in parseCLIArg can parse. +func formatForFlagSet(val any) (string, error) { + switch v := val.(type) { + case string: + return v, nil + case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + return fmt.Sprintf("%v", val), nil + default: + b, err := json.Marshal(val) + if err != nil { + return "", fmt.Errorf("cannot format value %T for flag.Set: %w", val, err) + } + return string(b), nil + } +} + // Flag [T] is a generic flag base which can be used to implement the most // common interfaces used by urfave/cli. Additionally, it allows specifying // where in an HTTP request the flag values should be placed (e.g. query, body, etc.). +// +// Pointer-to-primitive type parameters (e.g. *string) are used for flags whose underlying +// schema is nullable. They give flags a tri-state: unset (excluded from the request), +// set to the literal "null" (nil pointer → JSON null), or set to a value (*v → JSON value). type Flag[ T []any | []map[string]any | []DateTimeValue | []DateValue | []TimeValue | []string | []float64 | []int64 | []bool | any | map[string]any | DateTimeValue | DateValue | TimeValue | - string | float64 | int64 | bool, + string | float64 | int64 | bool | + *string | *float64 | *int64 | *bool | *DateTimeValue | *DateValue | *TimeValue, ] struct { Name string // name of the flag Category string // category of the flag, if any @@ -36,6 +62,7 @@ type Flag[ HeaderPath string // location in the request header to put this flag's value BodyPath string // location in the request body to put this flag's value BodyRoot bool // if true, then use this value as the entire request body + PathParam string // name of the URL path parameter this flag's value maps to // Const, when true, marks this flag as a constant. The flag's Default value is used as the fixed value // and always included in the request (IsSet returns true). The user can still see and override the flag, @@ -67,6 +94,7 @@ type InRequest interface { GetQueryPath() string GetHeaderPath() string GetBodyPath() string + GetPathParam() string IsBodyRoot() bool IsFileInput() bool GetDataAliases() []string @@ -84,6 +112,10 @@ func (f Flag[T]) GetBodyPath() string { return f.BodyPath } +func (f Flag[T]) GetPathParam() string { + return f.PathParam +} + func (f Flag[T]) IsBodyRoot() bool { return f.BodyRoot } @@ -103,7 +135,91 @@ type RequestContents struct { Body any } -// Extract query parameters, headers, and body values from command flags. +// ApplyStdinDataToFlags sets flag values from a parsed stdin data map for flags that have not already been +// set via the command line. This allows piped YAML/JSON data to satisfy path, query, and header parameters. +// Body parameters are excluded: they are already handled by the maps.Copy merge in flagOptions. +// For each unset flag, if the parsed data map contains a key matching the flag's QueryPath, HeaderPath, or +// PathParam (or any of its DataAliases), the flag is set to that value via flag.Set. +// +// Inner flags (those with an outer flag) are also handled: if the outer flag's body path key exists in the +// data map and contains a nested map with a key matching the inner flag's field (or aliases), the inner +// flag is set from that nested value. +func ApplyStdinDataToFlags(cmd *cli.Command, data map[string]any) error { + for _, flag := range cmd.Flags { + if flag.IsSet() { + continue + } + + // Handle inner flags: look for their value nested under the outer flag's body path. + if inner, ok := flag.(HasOuterFlag); ok { + outer, outerOk := inner.GetOuterFlag().(InRequest) + if !outerOk || outer.GetBodyPath() == "" { + continue + } + nested, ok := data[outer.GetBodyPath()].(map[string]any) + if !ok { + continue + } + innerField := inner.GetInnerField() + val, found := nested[innerField] + if !found { + for _, alias := range inner.GetDataAliases() { + if alias != "" && alias != innerField { + if v, ok := nested[alias]; ok { + val, found = v, true + break + } + } + } + } + if !found { + continue + } + setVal, err := formatForFlagSet(val) + if err != nil { + return fmt.Errorf("cannot format piped value for flag %q: %w", flag.Names()[0], err) + } + if err := flag.Set(flag.Names()[0], setVal); err != nil { + return fmt.Errorf("cannot set flag %q from piped data: %w", flag.Names()[0], err) + } + continue + } + + inReq, ok := flag.(InRequest) + if !ok { + continue + } + + // Try each request location in turn, checking the canonical path key and all aliases. + // Body params are excluded: they are already handled by the maps.Copy merge in flagOptions. + for _, path := range []string{inReq.GetQueryPath(), inReq.GetHeaderPath(), inReq.GetPathParam()} { + if path == "" { + continue + } + var val any + var found bool + for _, key := range append([]string{path}, inReq.GetDataAliases()...) { + if v, ok := data[key]; ok { + val, found = v, true + break + } + } + if !found { + continue + } + setVal, err := formatForFlagSet(val) + if err != nil { + return fmt.Errorf("cannot format piped value for flag %q: %w", flag.Names()[0], err) + } + if err := flag.Set(flag.Names()[0], setVal); err != nil { + return fmt.Errorf("cannot set flag %q from piped data: %w", flag.Names()[0], err) + } + break + } + } + return nil +} + func ExtractRequestContents(cmd *cli.Command) RequestContents { bodyMap := make(map[string]any) res := RequestContents{ @@ -286,7 +402,7 @@ func (f *Flag[T]) IsRequired() bool { } // Intentionally don't use `f.Required`, because request flags may be passed // over stdin as well as by flag. - if f.BodyPath != "" || f.BodyRoot { + if f.BodyPath != "" || f.BodyRoot || f.PathParam != "" || f.QueryPath != "" || f.HeaderPath != "" { return false } return f.Required @@ -341,6 +457,11 @@ func (f *Flag[T]) TypeName() string { if ty == nil { return "" } + // Deref pointer-typed flags so --help surfaces the pointee kind (e.g. "string"), not + // Go's pointer syntax. + if ty.Kind() == reflect.Pointer { + ty = ty.Elem() + } // Get base type name with special handling for built-in types getTypeName := func(t reflect.Type) string { @@ -396,6 +517,8 @@ func (f *Flag[T]) IsMultiValueFlag() bool { } func (f *Flag[T]) IsBoolFlag() bool { + // Flag[*bool] is deliberately not treated as a bool flag — the pointer form needs an + // explicit value (`--foo true`, `--foo null`) to disambiguate the tri-state. _, isBool := any(f.Default).(bool) return isBool } @@ -419,7 +542,8 @@ func (f Flag[T]) IsLocal() bool { type cliValue[ T []any | []map[string]any | []DateTimeValue | []DateValue | []TimeValue | []string | []float64 | []int64 | []bool | any | map[string]any | DateTimeValue | DateValue | TimeValue | string | - float64 | int64 | bool, + float64 | int64 | bool | + *string | *float64 | *int64 | *bool | *DateTimeValue | *DateValue | *TimeValue, ] struct { value T } @@ -429,12 +553,27 @@ type cliValue[ func parseCLIArg[ T []any | []map[string]any | []DateTimeValue | []DateValue | []TimeValue | []string | []float64 | []int64 | []bool | any | map[string]any | DateTimeValue | DateValue | TimeValue | string | - float64 | int64 | bool, + float64 | int64 | bool | + *string | *float64 | *int64 | *bool | *DateTimeValue | *DateValue | *TimeValue, ](value string) (T, error) { var parsedValue any var err error var empty T + + if value == "null" { + switch any(empty).(type) { + // Pointer-to-primitive: explicit nil gives the tri-state its "null" state + // (unset / null / value). Without this, numeric flags would fail to parse + // "null" and string flags would accept the literal word as a raw value. + case *string, *int64, *float64, *bool, *DateValue, *DateTimeValue, *TimeValue: + return empty, nil + // Maps marshal nil as JSON null natively; short-circuit avoids a YAML round-trip. + case map[string]any: + return empty, nil + } + } + switch any(empty).(type) { case string: parsedValue = value @@ -465,6 +604,48 @@ func parseCLIArg[ parsedValue = t } + // Pointer-to-primitive flags reach here only when `value != "null"`; we parse the + // pointee type and return its address so JSON marshaling emits the underlying value. + case *string: + v := value + parsedValue = &v + case *int64: + var v int64 + v, err = strconv.ParseInt(value, 0, 64) + if err == nil { + parsedValue = &v + } + case *float64: + var v float64 + v, err = strconv.ParseFloat(value, 64) + if err == nil { + parsedValue = &v + } + case *bool: + var v bool + v, err = strconv.ParseBool(value) + if err == nil { + parsedValue = &v + } + case *DateTimeValue: + var dt DateTimeValue + err = (&dt).Parse(value) + if err == nil { + parsedValue = &dt + } + case *DateValue: + var d DateValue + err = (&d).Parse(value) + if err == nil { + parsedValue = &d + } + case *TimeValue: + var t TimeValue + err = (&t).Parse(value) + if err == nil { + parsedValue = &t + } + default: if strings.HasPrefix(value, "@") { // File literals like @file.txt should work here @@ -501,6 +682,13 @@ func parseCLIArg[ } +// Ptr returns a pointer to its argument. It is used to initialize `Default` on pointer-typed +// Flag values, since Go does not allow taking the address of a composite literal's element +// or of an untyped constant. +func Ptr[T any](v T) *T { + return &v +} + // Assuming this string failed to parse as valid YAML, this function will // return true for strings that can reasonably be interpreted as a string literal, // like identifiers (`foo_bar`), UUIDs (`945b2f0c-8e89-487a-b02c-f851c69ea459`), @@ -594,6 +782,15 @@ func (c *cliValue[T]) String() string { // For basic types, use standard string representation return fmt.Sprintf("%v", v) + case *string, *int64, *float64, *bool, *DateTimeValue, *DateValue, *TimeValue: + // Pointer-to-primitive: nil renders as "null" (the CLI literal that produces it); + // non-nil derefs to the pointee's standard representation. + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "null" + } + return fmt.Sprintf("%v", rv.Elem().Interface()) + default: // For complex types, convert to YAML yamlBytes, err := yaml.MarshalWithOptions(c.value, yaml.Flow(true)) @@ -705,6 +902,15 @@ type SettableInnerField interface { SetInnerField(string, any) } +// InnerFieldSeeder lets an InnerFlag prepare its outer flag's underlying value +// before dispatching SetInnerField. This is only meaningful for Flag[any] — +// the codegen output for nullable complex schemas — whose untyped-nil zero +// value would otherwise have no reflect.Kind for the inner-field switch to +// dispatch on. +type InnerFieldSeeder interface { + SeedInnerCollection(isArrayOfObjects bool) +} + func (f *Flag[T]) SetInnerField(field string, val any) { if f.value == nil { f.value = &cliValue[T]{} @@ -718,6 +924,33 @@ func (f *Flag[T]) SetInnerField(field string, val any) { } } +// SeedInnerCollection initializes a Flag[any]'s underlying value as an empty +// map[string]any or []map[string]any so subsequent SetInnerField calls have a +// dispatchable reflect.Kind. For typed Flag[T] this is a no-op: the type +// assertion fails and the existing reflect.Kind on the typed-nil zero value +// already routes correctly. +func (f *Flag[T]) SeedInnerCollection(isArrayOfObjects bool) { + if f.value == nil { + f.value = &cliValue[T]{} + } + cv, ok := f.value.(*cliValue[T]) + if !ok { + return + } + if reflect.ValueOf(cv.value).Kind() != reflect.Invalid { + return + } + if isArrayOfObjects { + if seed, ok := any([]map[string]any{}).(T); ok { + cv.value = seed + } + return + } + if seed, ok := any(map[string]any{}).(T); ok { + cv.value = seed + } +} + func (c *cliValue[T]) SetInnerField(field string, val any) { flagVal := c.value flagValReflect := reflect.ValueOf(flagVal) diff --git a/internal/requestflag/requestflag_test.go b/internal/requestflag/requestflag_test.go index 0e86e07..779bd57 100644 --- a/internal/requestflag/requestflag_test.go +++ b/internal/requestflag/requestflag_test.go @@ -1,6 +1,7 @@ package requestflag import ( + "encoding/json" "fmt" "testing" "time" @@ -616,6 +617,178 @@ func TestYamlHandling(t *testing.T) { }) } +// TestNullLiteralHandling pins how each Flag[T] type handles the literal value "null" +// when passed via the CLI. Pointer-typed flags serialize nil as JSON null, which is how +// nullable body fields (`anyOf: [T, null]` / `{nullable: true}`) let users clear a field +// via `--foo null`. Non-pointer primitive flags treat "null" as a raw value — these are +// non-nullable schemas where explicit null has no API semantics anyway. +func TestNullLiteralHandling(t *testing.T) { + t.Parallel() + + assertJSONBody := func(t *testing.T, value any, expected string) { + t.Helper() + body, err := json.Marshal(map[string]any{"foo": value}) + assert.NoError(t, err) + assert.JSONEq(t, expected, string(body)) + } + + t.Run("Flag[any] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[any]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[string] null is the raw string \"null\"", func(t *testing.T) { + t.Parallel() + cv := &cliValue[string]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":"null"}`) + }) + + t.Run("Flag[int64] null errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[int64]{} + assert.Error(t, cv.Set("null")) + }) + + t.Run("Flag[*string] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*string]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*string] value sends the string", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*string]{} + assert.NoError(t, cv.Set("1.1")) + assertJSONBody(t, cv.Get(), `{"foo":"1.1"}`) + }) + + t.Run("Flag[*int64] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*int64]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*int64] value sends the integer", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*int64]{} + assert.NoError(t, cv.Set("42")) + assertJSONBody(t, cv.Get(), `{"foo":42}`) + }) + + t.Run("Flag[*int64] invalid value errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*int64]{} + assert.Error(t, cv.Set("not-an-int")) + }) + + t.Run("Flag[*bool] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*bool]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*bool] value sends the boolean", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*bool]{} + assert.NoError(t, cv.Set("true")) + assertJSONBody(t, cv.Get(), `{"foo":true}`) + }) + + t.Run("Flag[*float64] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*float64]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*float64] value sends the float", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*float64]{} + assert.NoError(t, cv.Set("1.5")) + assertJSONBody(t, cv.Get(), `{"foo":1.5}`) + }) + + t.Run("Flag[*float64] invalid value errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*float64]{} + assert.Error(t, cv.Set("not-a-float")) + }) + + t.Run("Flag[*DateValue] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateValue]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*DateValue] value sends the date", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateValue]{} + assert.NoError(t, cv.Set("2023-05-15")) + assertJSONBody(t, cv.Get(), `{"foo":"2023-05-15"}`) + }) + + t.Run("Flag[*DateValue] invalid value errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateValue]{} + assert.Error(t, cv.Set("not-a-date")) + }) + + t.Run("Flag[*DateTimeValue] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateTimeValue]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*DateTimeValue] value sends the datetime", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateTimeValue]{} + assert.NoError(t, cv.Set("2023-05-15T14:30:45Z")) + assertJSONBody(t, cv.Get(), `{"foo":"2023-05-15T14:30:45Z"}`) + }) + + t.Run("Flag[*DateTimeValue] invalid value errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*DateTimeValue]{} + assert.Error(t, cv.Set("not-a-datetime")) + }) + + t.Run("Flag[*TimeValue] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*TimeValue]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) + + t.Run("Flag[*TimeValue] value sends the time", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*TimeValue]{} + assert.NoError(t, cv.Set("14:30:45")) + assertJSONBody(t, cv.Get(), `{"foo":"14:30:45"}`) + }) + + t.Run("Flag[*TimeValue] invalid value errors", func(t *testing.T) { + t.Parallel() + cv := &cliValue[*TimeValue]{} + assert.Error(t, cv.Set("not-a-time")) + }) + + // Nullable maps don't need pointer wrapping — a nil map already marshals as JSON null. + t.Run("Flag[map[string]any] null sends JSON null", func(t *testing.T) { + t.Parallel() + cv := &cliValue[map[string]any]{} + assert.NoError(t, cv.Set("null")) + assertJSONBody(t, cv.Get(), `{"foo":null}`) + }) +} + func TestFlagTypeNames(t *testing.T) { t.Parallel() @@ -646,3 +819,409 @@ func TestFlagTypeNames(t *testing.T) { }) } } + +// TestInnerFlagDispatchOnUntypedFlag pins inner-flag behavior for `Flag[any]`, +// which is the codegen output for nullable complex schemas (`anyOf: [T, null]` +// or `{nullable: true}`). The untyped-nil zero value carries no reflect.Kind, +// so SetInnerField has nowhere to dispatch the assignment — without explicit +// help the inner-field value silently drops. +func TestInnerFlagDispatchOnUntypedFlag(t *testing.T) { + t.Parallel() + + t.Run("nullable array of objects appends element from inner flag", func(t *testing.T) { + t.Parallel() + outer := &Flag[any]{Name: "mcp-server"} + assert.NoError(t, outer.PreParse()) + + nameFlag := &InnerFlag[string]{ + Name: "mcp-server.name", InnerField: "name", + OuterFlag: outer, OuterIsArrayOfObjects: true, + } + assert.NoError(t, nameFlag.Set("mcp-server.name", "first")) + + body, err := json.Marshal(map[string]any{"foo": outer.Get()}) + assert.NoError(t, err) + assert.JSONEq(t, `{"foo":[{"name":"first"}]}`, string(body)) + }) + + t.Run("nullable object sets field from inner flag", func(t *testing.T) { + t.Parallel() + outer := &Flag[any]{Name: "metadata"} + assert.NoError(t, outer.PreParse()) + + keyFlag := &InnerFlag[string]{ + Name: "metadata.key", InnerField: "key", OuterFlag: outer, + } + assert.NoError(t, keyFlag.Set("metadata.key", "value")) + + body, err := json.Marshal(map[string]any{"foo": outer.Get()}) + assert.NoError(t, err) + assert.JSONEq(t, `{"foo":{"key":"value"}}`, string(body)) + }) + + t.Run("multiple inner flags merge into the trailing element", func(t *testing.T) { + t.Parallel() + outer := &Flag[any]{Name: "mcp-server"} + assert.NoError(t, outer.PreParse()) + + nameFlag := &InnerFlag[string]{ + Name: "mcp-server.name", InnerField: "name", + OuterFlag: outer, OuterIsArrayOfObjects: true, + } + urlFlag := &InnerFlag[string]{ + Name: "mcp-server.url", InnerField: "url", + OuterFlag: outer, OuterIsArrayOfObjects: true, + } + assert.NoError(t, nameFlag.Set("mcp-server.name", "first")) + assert.NoError(t, urlFlag.Set("mcp-server.url", "https://example.com")) + + body, err := json.Marshal(map[string]any{"foo": outer.Get()}) + assert.NoError(t, err) + assert.JSONEq(t, `{"foo":[{"name":"first","url":"https://example.com"}]}`, string(body)) + }) +} + +func TestApplyStdinDataToFlags(t *testing.T) { + t.Parallel() + + t.Run("sets query path flag from piped data", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"account_id": "acct_123"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, "acct_123", flag.Get()) + }) + + t.Run("sets header path flag from piped data", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "idempotency-key", + HeaderPath: "Idempotency-Key", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"Idempotency-Key": "key-xyz"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, "key-xyz", flag.Get()) + }) + + t.Run("does not set body path flag from piped data", func(t *testing.T) { + t.Parallel() + + // Body params are handled by the maps.Copy merge in flagOptions, not by ApplyStdinDataToFlags. + flag := &Flag[string]{ + Name: "message", + BodyPath: "message", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"message": "hello world"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, flag.IsSet()) + }) + + t.Run("does not override flag already set via CLI", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + } + assert.NoError(t, flag.PreParse()) + assert.NoError(t, flag.Set("account-id", "explicit_value")) + + data := map[string]any{"account_id": "piped_value"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + // The explicitly-set value should win. + assert.Equal(t, "explicit_value", flag.Get()) + }) + + t.Run("sets integer query flag from piped data", func(t *testing.T) { + t.Parallel() + + flag := &Flag[int64]{ + Name: "page-size", + QueryPath: "page_size", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"page_size": int64(50)} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, int64(50), flag.Get()) + }) + + t.Run("sets boolean query flag from piped data", func(t *testing.T) { + t.Parallel() + + flag := &Flag[bool]{ + Name: "include-deleted", + QueryPath: "include_deleted", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"include_deleted": true} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, true, flag.Get()) + }) + + t.Run("resolves query path flag via data alias", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + DataAliases: []string{"accountId", "account"}, + } + assert.NoError(t, flag.PreParse()) + + // Use one of the aliases as the key in piped data. + data := map[string]any{"accountId": "acct_alias"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, "acct_alias", flag.Get()) + }) + + t.Run("does not set body path flag via data alias", func(t *testing.T) { + t.Parallel() + + // Body params are handled by the maps.Copy merge in flagOptions, not by ApplyStdinDataToFlags. + flag := &Flag[string]{ + Name: "user-name", + BodyPath: "user_name", + DataAliases: []string{"userName", "username"}, + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"userName": "alice"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, flag.IsSet()) + }) + + t.Run("ignores flags with no matching key in piped data", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"other_key": "value"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, flag.IsSet()) + }) + + t.Run("ignores flags with no path set", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "some-flag", + // No QueryPath, HeaderPath, or BodyPath + } + assert.NoError(t, flag.PreParse()) + + data := map[string]any{"some-flag": "value"} + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, flag.IsSet()) + }) + + t.Run("handles multiple flags from piped data", func(t *testing.T) { + t.Parallel() + + accountFlag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + } + limitFlag := &Flag[int64]{ + Name: "limit", + QueryPath: "limit", + } + assert.NoError(t, accountFlag.PreParse()) + assert.NoError(t, limitFlag.PreParse()) + + data := map[string]any{ + "account_id": "acct_abc", + "limit": int64(25), + } + cmd := &cli.Command{Flags: []cli.Flag{accountFlag, limitFlag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, accountFlag.IsSet()) + assert.Equal(t, "acct_abc", accountFlag.Get()) + assert.True(t, limitFlag.IsSet()) + assert.Equal(t, int64(25), limitFlag.Get()) + }) + + t.Run("sets inner flag from nested piped data under outer body path", func(t *testing.T) { + t.Parallel() + + outer := &Flag[map[string]any]{ + Name: "address", + BodyPath: "address", + } + assert.NoError(t, outer.PreParse()) + + cityInner := &InnerFlag[string]{ + Name: "address.city", + InnerField: "city", + OuterFlag: outer, + } + + data := map[string]any{ + "address": map[string]any{"city": "San Francisco"}, + } + cmd := &cli.Command{Flags: []cli.Flag{outer, cityInner}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + // InnerFlag.IsSet() is always false by design; verify the value was written + // into the outer flag's underlying map instead. + outerVal, ok := outer.Get().(map[string]any) + assert.True(t, ok, "expected outer flag value to be map[string]any, got %T", outer.Get()) + assert.Equal(t, "San Francisco", outerVal["city"]) + }) + + t.Run("sets inner flag via data alias in nested piped data", func(t *testing.T) { + t.Parallel() + + outer := &Flag[map[string]any]{ + Name: "address", + BodyPath: "address", + } + assert.NoError(t, outer.PreParse()) + + cityInner := &InnerFlag[string]{ + Name: "address.city", + InnerField: "city", + DataAliases: []string{"cityName"}, + OuterFlag: outer, + } + + // Use the alias in piped data. + data := map[string]any{ + "address": map[string]any{"cityName": "Portland"}, + } + cmd := &cli.Command{Flags: []cli.Flag{outer, cityInner}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + // InnerFlag.IsSet() is always false by design; verify the value was written + // into the outer flag's underlying map instead. + outerVal, ok := outer.Get().(map[string]any) + assert.True(t, ok, "expected outer flag value to be map[string]any, got %T", outer.Get()) + assert.Equal(t, "Portland", outerVal["city"]) + }) + + t.Run("does not set inner flag when outer flag has no body path", func(t *testing.T) { + t.Parallel() + + outer := &Flag[map[string]any]{ + Name: "options", + // No BodyPath set + } + assert.NoError(t, outer.PreParse()) + + inner := &InnerFlag[string]{ + Name: "options.key", + InnerField: "key", + OuterFlag: outer, + } + + data := map[string]any{ + "options": map[string]any{"key": "value"}, + } + cmd := &cli.Command{Flags: []cli.Flag{outer, inner}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, inner.IsSet()) + }) + + t.Run("does not set inner flag when piped data has no nested map for outer path", func(t *testing.T) { + t.Parallel() + + outer := &Flag[map[string]any]{ + Name: "address", + BodyPath: "address", + } + assert.NoError(t, outer.PreParse()) + + inner := &InnerFlag[string]{ + Name: "address.city", + InnerField: "city", + OuterFlag: outer, + } + + // The outer body path key is missing from the piped data. + data := map[string]any{"other": "value"} + cmd := &cli.Command{Flags: []cli.Flag{outer, inner}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.False(t, inner.IsSet()) + }) + + t.Run("canonical path key takes precedence over alias when both are present", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + DataAliases: []string{"accountId"}, + } + assert.NoError(t, flag.PreParse()) + + // Both canonical and alias present — canonical should win because it's checked first. + data := map[string]any{ + "account_id": "canonical_value", + "accountId": "alias_value", + } + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, data)) + + assert.True(t, flag.IsSet()) + assert.Equal(t, "canonical_value", flag.Get()) + }) + + t.Run("empty data map does not set any flags", func(t *testing.T) { + t.Parallel() + + flag := &Flag[string]{ + Name: "account-id", + QueryPath: "account_id", + } + assert.NoError(t, flag.PreParse()) + + cmd := &cli.Command{Flags: []cli.Flag{flag}} + assert.NoError(t, ApplyStdinDataToFlags(cmd, map[string]any{})) + + assert.False(t, flag.IsSet()) + }) +} diff --git a/pkg/cmd/agent.go b/pkg/cmd/agent.go index 2a87754..eb68db2 100644 --- a/pkg/cmd/agent.go +++ b/pkg/cmd/agent.go @@ -6,24 +6,19 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var agentsCreate = requestflag.WithInnerFlags(cli.Command{ Name: "create", - Usage: "Perform create operation", + Usage: "Create agent", Suggest: true, Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "user-id", - Required: true, - BodyPath: "userId", - }, &requestflag.Flag[map[string]any]{ Name: "configuration", BodyPath: "configuration", @@ -37,6 +32,10 @@ var agentsCreate = requestflag.WithInnerFlags(cli.Command{ Usage: "Arbitrary string metadata stored on an agent. Runtime enforces maximum key and value sizes.", BodyPath: "metadata", }, + &requestflag.Flag[string]{ + Name: "user-id", + BodyPath: "userId", + }, }, Action: handleAgentsCreate, HideHelpCommand: true, @@ -56,6 +55,7 @@ var agentsCreate = requestflag.WithInnerFlags(cli.Command{ }, &requestflag.InnerFlag[[]string]{ Name: "configuration.tools", + Usage: "Agent tool allowlist. These tools are subject to fleet defaults and locks, and thread or turn requests may only narrow the resulting effective tools.", InnerField: "tools", }, }, @@ -63,12 +63,13 @@ var agentsCreate = requestflag.WithInnerFlags(cli.Command{ var agentsRetrieve = cli.Command{ Name: "retrieve", - Usage: "Perform retrieve operation", + Usage: "Retrieve agent", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, }, Action: handleAgentsRetrieve, @@ -77,12 +78,13 @@ var agentsRetrieve = cli.Command{ var agentsUpdate = requestflag.WithInnerFlags(cli.Command{ Name: "update", - Usage: "Perform update operation", + Usage: "Update agent", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[map[string]any]{ Name: "configuration", @@ -108,6 +110,7 @@ var agentsUpdate = requestflag.WithInnerFlags(cli.Command{ }, &requestflag.InnerFlag[[]string]{ Name: "configuration.tools", + Usage: "Agent tool allowlist. These tools are subject to fleet defaults and locks, and thread or turn requests may only narrow the resulting effective tools.", InnerField: "tools", }, }, @@ -115,7 +118,7 @@ var agentsUpdate = requestflag.WithInnerFlags(cli.Command{ var agentsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List agents", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -149,26 +152,43 @@ var agentsList = cli.Command{ var agentsDelete = cli.Command{ Name: "delete", - Usage: "Perform delete operation", + Usage: "Delete agent", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, }, Action: handleAgentsDelete, HideHelpCommand: true, } +var agentsListTools = cli.Command{ + Name: "list-tools", + Usage: "List tools", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "agent-id", + Required: true, + PathParam: "agentId", + }, + }, + Action: handleAgentsListTools, + HideHelpCommand: true, +} + var agentsRetrieveConfig = cli.Command{ Name: "retrieve-config", - Usage: "Perform retrieve-config operation", + Usage: "Retrieve config", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, }, Action: handleAgentsRetrieveConfig, @@ -177,12 +197,13 @@ var agentsRetrieveConfig = cli.Command{ var agentsUpdateMetadata = cli.Command{ Name: "update-metadata", - Usage: "Perform update-metadata operation", + Usage: "Update metadata", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[map[string]any]{ Name: "metadata", @@ -203,8 +224,6 @@ func handleAgentsCreate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.AgentNewParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -216,6 +235,8 @@ func handleAgentsCreate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.AgentNewParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Agents.New(ctx, params, options...) @@ -289,8 +310,6 @@ func handleAgentsUpdate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.AgentUpdateParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -302,6 +321,8 @@ func handleAgentsUpdate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.AgentUpdateParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Agents.Update( @@ -335,8 +356,6 @@ func handleAgentsList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.AgentListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -348,6 +367,8 @@ func handleAgentsList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.AgentListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -424,6 +445,48 @@ func handleAgentsDelete(ctx context.Context, cmd *cli.Command) error { }) } +func handleAgentsListTools(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { + cmd.Set("agent-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) + if err != nil { + return err + } + + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Agents.ListTools(ctx, cmd.Value("agent-id").(string), options...) + if err != nil { + return err + } + + obj := gjson.ParseBytes(res) + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "agents list-tools", + Transform: transform, + }) +} + func handleAgentsRetrieveConfig(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() @@ -477,8 +540,6 @@ func handleAgentsUpdateMetadata(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.AgentUpdateMetadataParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -490,6 +551,8 @@ func handleAgentsUpdateMetadata(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.AgentUpdateMetadataParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Agents.UpdateMetadata( diff --git a/pkg/cmd/agent_test.go b/pkg/cmd/agent_test.go index 76dbc04..c148c59 100644 --- a/pkg/cmd/agent_test.go +++ b/pkg/cmd/agent_test.go @@ -5,8 +5,8 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" + "github.com/matrices/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/requestflag" ) func TestAgentsCreate(t *testing.T) { @@ -15,10 +15,10 @@ func TestAgentsCreate(t *testing.T) { t, "--api-key", "string", "agents", "create", - "--user-id", "userId", "--configuration", "{approvals: {timeoutMs: 0, tools: {foo: always}}, defaultModel: defaultModel, instructions: instructions, tools: [sandbox.*]}", "--fleet-id", "fleetId", "--metadata", "{project: alpha}", + "--user-id", "userId", ) }) @@ -31,20 +31,19 @@ func TestAgentsCreate(t *testing.T) { t, "--api-key", "string", "agents", "create", - "--user-id", "userId", "--configuration.approvals", "{timeoutMs: 0, tools: {foo: always}}", "--configuration.default-model", "defaultModel", "--configuration.instructions", "instructions", "--configuration.tools", "[sandbox.*]", "--fleet-id", "fleetId", "--metadata", "{project: alpha}", + "--user-id", "userId", ) }) t.Run("piping data", func(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + - "userId: userId\n" + "configuration:\n" + " approvals:\n" + " timeoutMs: 0\n" + @@ -56,7 +55,8 @@ func TestAgentsCreate(t *testing.T) { " - sandbox.*\n" + "fleetId: fleetId\n" + "metadata:\n" + - " project: alpha\n") + " project: alpha\n" + + "userId: userId\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", @@ -151,6 +151,17 @@ func TestAgentsDelete(t *testing.T) { }) } +func TestAgentsListTools(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "agents", "list-tools", + "--agent-id", "agent_abc123", + ) + }) +} + func TestAgentsRetrieveConfig(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( diff --git a/pkg/cmd/approvalgrant.go b/pkg/cmd/approvalgrant.go index d36d16c..1eb47c6 100644 --- a/pkg/cmd/approvalgrant.go +++ b/pkg/cmd/approvalgrant.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var approvalGrantsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List approval grants", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -44,16 +45,18 @@ var approvalGrantsList = cli.Command{ var approvalGrantsDelete = cli.Command{ Name: "delete", - Usage: "Perform delete operation", + Usage: "Delete approval grant", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "grant-id", - Required: true, + Name: "grant-id", + Required: true, + PathParam: "grantId", }, }, Action: handleApprovalGrantsDelete, @@ -62,20 +65,23 @@ var approvalGrantsDelete = cli.Command{ var approvalGrantsDeleteForThread = cli.Command{ Name: "delete-for-thread", - Usage: "Perform delete-for-thread operation", + Usage: "Delete approval grant", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ - Name: "grant-id", - Required: true, + Name: "grant-id", + Required: true, + PathParam: "grantId", }, }, Action: handleApprovalGrantsDeleteForThread, @@ -84,16 +90,18 @@ var approvalGrantsDeleteForThread = cli.Command{ var approvalGrantsListForThread = cli.Command{ Name: "list-for-thread", - Usage: "Perform list-for-thread operation", + Usage: "List approval grants", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, }, Action: handleApprovalGrantsListForThread, @@ -111,8 +119,6 @@ func handleApprovalGrantsList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ApprovalGrantListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -124,6 +130,8 @@ func handleApprovalGrantsList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ApprovalGrantListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") diff --git a/pkg/cmd/approvalgrant_test.go b/pkg/cmd/approvalgrant_test.go index 6ac425a..e721f9c 100644 --- a/pkg/cmd/approvalgrant_test.go +++ b/pkg/cmd/approvalgrant_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestApprovalGrantsList(t *testing.T) { diff --git a/pkg/cmd/approval.go b/pkg/cmd/approvalrequest.go similarity index 78% rename from pkg/cmd/approval.go rename to pkg/cmd/approvalrequest.go index 1380afd..fb7d685 100644 --- a/pkg/cmd/approval.go +++ b/pkg/cmd/approvalrequest.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) -var approvalsList = cli.Command{ +var approvalRequestsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List approvals", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -43,26 +44,29 @@ var approvalsList = cli.Command{ Usage: "The maximum number of items to return (use -1 for unlimited).", }, }, - Action: handleApprovalsList, + Action: handleApprovalRequestsList, HideHelpCommand: true, } -var approvalsResolve = cli.Command{ +var approvalRequestsResolve = cli.Command{ Name: "resolve", - Usage: "Perform resolve operation", + Usage: "Create approval", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ - Name: "approval-id", - Required: true, + Name: "approval-id", + Required: true, + PathParam: "approvalId", }, &requestflag.Flag[string]{ Name: "decision", @@ -76,11 +80,11 @@ var approvalsResolve = cli.Command{ BodyPath: "grant", }, }, - Action: handleApprovalsResolve, + Action: handleApprovalRequestsResolve, HideHelpCommand: true, } -func handleApprovalsList(ctx context.Context, cmd *cli.Command) error { +func handleApprovalRequestsList(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { @@ -91,8 +95,6 @@ func handleApprovalsList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ApprovalListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -104,13 +106,15 @@ func handleApprovalsList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ApprovalRequestListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") if format == "raw" { var res []byte options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Approvals.List( + _, err = client.ApprovalRequests.List( ctx, cmd.Value("agent-id").(string), params, @@ -124,11 +128,11 @@ func handleApprovalsList(ctx context.Context, cmd *cli.Command) error { ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "approvals list", + Title: "approval-requests list", Transform: transform, }) } else { - iter := client.Approvals.ListAutoPaging( + iter := client.ApprovalRequests.ListAutoPaging( ctx, cmd.Value("agent-id").(string), params, @@ -142,13 +146,13 @@ func handleApprovalsList(ctx context.Context, cmd *cli.Command) error { ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "approvals list", + Title: "approval-requests list", Transform: transform, }) } } -func handleApprovalsResolve(ctx context.Context, cmd *cli.Command) error { +func handleApprovalRequestsResolve(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { @@ -167,8 +171,6 @@ func handleApprovalsResolve(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ApprovalResolveParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -180,9 +182,11 @@ func handleApprovalsResolve(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ApprovalRequestResolveParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Approvals.Resolve( + _, err = client.ApprovalRequests.Resolve( ctx, cmd.Value("agent-id").(string), cmd.Value("thread-id").(string), @@ -202,7 +206,7 @@ func handleApprovalsResolve(ctx context.Context, cmd *cli.Command) error { ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "approvals resolve", + Title: "approval-requests resolve", Transform: transform, }) } diff --git a/pkg/cmd/approval_test.go b/pkg/cmd/approvalrequest_test.go similarity index 80% rename from pkg/cmd/approval_test.go rename to pkg/cmd/approvalrequest_test.go index 2ce029e..151aaae 100644 --- a/pkg/cmd/approval_test.go +++ b/pkg/cmd/approvalrequest_test.go @@ -5,15 +5,15 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) -func TestApprovalsList(t *testing.T) { +func TestApprovalRequestsList(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "approvals", "list", + "approval-requests", "list", "--max-items", "10", "--agent-id", "agent_abc123", "--cursor", "cursor_abc123", @@ -23,12 +23,12 @@ func TestApprovalsList(t *testing.T) { }) } -func TestApprovalsResolve(t *testing.T) { +func TestApprovalRequestsResolve(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "approvals", "resolve", + "approval-requests", "resolve", "--agent-id", "agent_abc123", "--thread-id", "thread_abc123", "--approval-id", "approval_abc123", @@ -45,7 +45,7 @@ func TestApprovalsResolve(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "approvals", "resolve", + "approval-requests", "resolve", "--agent-id", "agent_abc123", "--thread-id", "thread_abc123", "--approval-id", "approval_abc123", diff --git a/pkg/cmd/auth.go b/pkg/cmd/auth.go index 5176459..59c7017 100644 --- a/pkg/cmd/auth.go +++ b/pkg/cmd/auth.go @@ -6,17 +6,17 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var authContext = cli.Command{ Name: "context", - Usage: "Perform context operation", + Usage: "Retrieve context", Suggest: true, Flags: []cli.Flag{}, Action: handleAuthContext, @@ -25,7 +25,7 @@ var authContext = cli.Command{ var authListFleets = cli.Command{ Name: "list-fleets", - Usage: "Perform list-fleets operation", + Usage: "List fleets", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -94,8 +94,6 @@ func handleAuthListFleets(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.AuthListFleetsParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -107,6 +105,8 @@ func handleAuthListFleets(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.AuthListFleetsParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") diff --git a/pkg/cmd/auth_test.go b/pkg/cmd/auth_test.go index 959672b..3bcbedb 100644 --- a/pkg/cmd/auth_test.go +++ b/pkg/cmd/auth_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestAuthContext(t *testing.T) { diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 66b9c1b..2092fb7 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -12,8 +12,8 @@ import ( "slices" "strings" - "github.com/stainless-sdks/cerca-cli/internal/autocomplete" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" + "github.com/matrices/cerca-cli/internal/autocomplete" + "github.com/matrices/cerca-cli/internal/requestflag" docs "github.com/urfave/cli-docs/v3" "github.com/urfave/cli/v3" ) @@ -102,26 +102,28 @@ func init() { }, }, { - Name: "credentials", + Name: "connections", Category: "API RESOURCE", Suggest: true, Commands: []*cli.Command{ - &credentialsList, - &credentialsDelete, - &credentialsCreateAPIKey, + &connectionsCreate, + &connectionsList, + &connectionsDelete, + &connectionsAttach, + &connectionsDetach, + &connectionsListForAgent, }, }, { - Name: "tool-sources", + Name: "tools", Category: "API RESOURCE", Suggest: true, Commands: []*cli.Command{ - &toolSourcesCreate, - &toolSourcesRetrieve, - &toolSourcesUpdate, - &toolSourcesList, - &toolSourcesDelete, - &toolSourcesListForAgent, + &toolsCreate, + &toolsRetrieve, + &toolsUpdate, + &toolsList, + &toolsDelete, }, }, { @@ -146,9 +148,10 @@ func init() { &eventsListForAgent, &eventsListForFleet, &eventsListForThread, - &eventsSubscribeAgent, - &eventsSubscribeFleet, - &eventsSubscribeThread, + &eventsStreamForAgent, + &eventsStreamForFleet, + &eventsStreamForThread, + &eventsStreamForThreadEvents, }, }, { @@ -161,6 +164,7 @@ func init() { &agentsUpdate, &agentsList, &agentsDelete, + &agentsListTools, &agentsRetrieveConfig, &agentsUpdateMetadata, }, @@ -192,16 +196,6 @@ func init() { &contextWrite, }, }, - { - Name: "connections", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &connectionsList, - &connectionsAttach, - &connectionsDetach, - }, - }, { Name: "schedules", Category: "API RESOURCE", @@ -215,12 +209,12 @@ func init() { }, }, { - Name: "approvals", + Name: "approval-requests", Category: "API RESOURCE", Suggest: true, Commands: []*cli.Command{ - &approvalsList, - &approvalsResolve, + &approvalRequestsList, + &approvalRequestsResolve, }, }, { @@ -234,15 +228,6 @@ func init() { &approvalGrantsListForThread, }, }, - { - Name: "logs", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &logsListForAgent, - &logsListForThread, - }, - }, { Name: "sandbox", Category: "API RESOURCE", @@ -253,14 +238,6 @@ func init() { &sandboxWrite, }, }, - { - Name: "tools", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &toolsListForAgent, - }, - }, { Name: "models", Category: "API RESOURCE", diff --git a/pkg/cmd/cmdutil.go b/pkg/cmd/cmdutil.go index 88c9724..de25c95 100644 --- a/pkg/cmd/cmdutil.go +++ b/pkg/cmd/cmdutil.go @@ -16,8 +16,8 @@ import ( "strings" "syscall" + "github.com/matrices/cerca-cli/internal/jsonview" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/jsonview" "github.com/charmbracelet/x/term" "github.com/itchyny/json2yaml" diff --git a/pkg/cmd/cmdutil_test.go b/pkg/cmd/cmdutil_test.go index 13b8256..e38f193 100644 --- a/pkg/cmd/cmdutil_test.go +++ b/pkg/cmd/cmdutil_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - "github.com/stainless-sdks/cerca-cli/internal/jsonview" + "github.com/matrices/cerca-cli/internal/jsonview" ) func TestStreamOutput(t *testing.T) { diff --git a/pkg/cmd/connection.go b/pkg/cmd/connection.go index 9021027..220e77d 100644 --- a/pkg/cmd/connection.go +++ b/pkg/cmd/connection.go @@ -6,36 +6,117 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) +var connectionsCreate = cli.Command{ + Name: "create", + Usage: "Create API key", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "api-key", + Usage: "API key secret. It is stored securely and is not returned.", + Required: true, + BodyPath: "apiKey", + }, + &requestflag.Flag[map[string]any]{ + Name: "owner", + Usage: "Public owner for a reusable connection. Organization owners use the authenticated organization; fleet owners add a fleetId.", + Required: true, + BodyPath: "owner", + }, + &requestflag.Flag[string]{ + Name: "provider", + Usage: "Credential provider to store an API key for.", + Required: true, + BodyPath: "provider", + }, + &requestflag.Flag[string]{ + Name: "account-label", + Usage: "Optional human-readable account label.", + BodyPath: "accountLabel", + }, + }, + Action: handleConnectionsCreate, + HideHelpCommand: true, +} + var connectionsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List connections", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "owner-type", + Usage: "Public connection owner type.", + Required: true, + QueryPath: "ownerType", + }, + &requestflag.Flag[string]{ + Name: "cursor", + Usage: "Opaque pagination cursor returned by a previous request.", + QueryPath: "cursor", + }, + &requestflag.Flag[string]{ + Name: "fleet-id", + Usage: "Required when ownerType is fleet.", + QueryPath: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "limit", + Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", + QueryPath: "limit", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", }, }, Action: handleConnectionsList, HideHelpCommand: true, } +var connectionsDelete = cli.Command{ + Name: "delete", + Usage: "Delete connection", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "connection-id", + Required: true, + PathParam: "connectionId", + }, + &requestflag.Flag[string]{ + Name: "owner-type", + Usage: "Public connection owner type.", + Required: true, + QueryPath: "ownerType", + }, + &requestflag.Flag[string]{ + Name: "fleet-id", + Usage: "Required when ownerType is fleet.", + QueryPath: "fleetId", + }, + }, + Action: handleConnectionsDelete, + HideHelpCommand: true, +} + var connectionsAttach = cli.Command{ Name: "attach", - Usage: "Perform attach operation", + Usage: "Attach connection", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "connection-id", @@ -53,27 +134,140 @@ var connectionsAttach = cli.Command{ var connectionsDetach = cli.Command{ Name: "detach", - Usage: "Perform detach operation", + Usage: "Detach connection", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "connection-id", - Required: true, + Name: "connection-id", + Required: true, + PathParam: "connectionId", }, }, Action: handleConnectionsDetach, HideHelpCommand: true, } +var connectionsListForAgent = cli.Command{ + Name: "list-for-agent", + Usage: "List connections", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "agent-id", + Required: true, + PathParam: "agentId", + }, + }, + Action: handleConnectionsListForAgent, + HideHelpCommand: true, +} + +func handleConnectionsCreate(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + ApplicationJSON, + false, + ) + if err != nil { + return err + } + + params := cercago.ConnectionNewParams{} + + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Connections.New(ctx, params, options...) + if err != nil { + return err + } + + obj := gjson.ParseBytes(res) + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "connections create", + Transform: transform, + }) +} + func handleConnectionsList(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { - cmd.Set("agent-id", unusedArgs[0]) + + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) + if err != nil { + return err + } + + params := cercago.ConnectionListParams{} + + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + if format == "raw" { + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Connections.List(ctx, params, options...) + if err != nil { + return err + } + obj := gjson.ParseBytes(res) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "connections list", + Transform: transform, + }) + } else { + iter := client.Connections.ListAutoPaging(ctx, params, options...) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "connections list", + Transform: transform, + }) + } +} + +func handleConnectionsDelete(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("connection-id") && len(unusedArgs) > 0 { + cmd.Set("connection-id", unusedArgs[0]) unusedArgs = unusedArgs[1:] } if len(unusedArgs) > 0 { @@ -91,9 +285,16 @@ func handleConnectionsList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ConnectionDeleteParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Connections.List(ctx, cmd.Value("agent-id").(string), options...) + _, err = client.Connections.Delete( + ctx, + cmd.Value("connection-id").(string), + params, + options..., + ) if err != nil { return err } @@ -106,7 +307,7 @@ func handleConnectionsList(ctx context.Context, cmd *cli.Command) error { ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "connections list", + Title: "connections delete", Transform: transform, }) } @@ -122,8 +323,6 @@ func handleConnectionsAttach(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ConnectionAttachParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -135,6 +334,8 @@ func handleConnectionsAttach(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ConnectionAttachParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Connections.Attach( @@ -210,3 +411,45 @@ func handleConnectionsDetach(ctx context.Context, cmd *cli.Command) error { Transform: transform, }) } + +func handleConnectionsListForAgent(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { + cmd.Set("agent-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) + if err != nil { + return err + } + + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Connections.ListForAgent(ctx, cmd.Value("agent-id").(string), options...) + if err != nil { + return err + } + + obj := gjson.ParseBytes(res) + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "connections list-for-agent", + Transform: transform, + }) +} diff --git a/pkg/cmd/connection_test.go b/pkg/cmd/connection_test.go index b7c06b1..8eb81ea 100644 --- a/pkg/cmd/connection_test.go +++ b/pkg/cmd/connection_test.go @@ -5,16 +5,62 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) +func TestConnectionsCreate(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "connections", "create", + "--api-key", "sk_live_...", + "--owner", "{type: organization}", + "--provider", "custom", + "--account-label", "primary", + ) + }) + + t.Run("piping data", func(t *testing.T) { + // Test piping YAML data over stdin + pipeData := []byte("" + + "apiKey: sk_live_...\n" + + "owner:\n" + + " type: organization\n" + + "provider: custom\n" + + "accountLabel: primary\n") + mocktest.TestRunMockTestWithPipeAndFlags( + t, pipeData, + "--api-key", "string", + "connections", "create", + ) + }) +} + func TestConnectionsList(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", "connections", "list", - "--agent-id", "agent_abc123", + "--max-items", "10", + "--owner-type", "fleet", + "--cursor", "cursor_abc123", + "--fleet-id", "fleet_abc123", + "--limit", "20", + ) + }) +} + +func TestConnectionsDelete(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "connections", "delete", + "--connection-id", "9f063c59-2775-4614-8a68-22e5f90f92f3", + "--owner-type", "fleet", + "--fleet-id", "fleet_abc123", ) }) } @@ -26,7 +72,7 @@ func TestConnectionsAttach(t *testing.T) { "--api-key", "string", "connections", "attach", "--agent-id", "agent_abc123", - "--connection-id", "connectionId", + "--connection-id", "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "--metadata", "{foo: string}", ) }) @@ -34,7 +80,7 @@ func TestConnectionsAttach(t *testing.T) { t.Run("piping data", func(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + - "connectionId: connectionId\n" + + "connectionId: 182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e\n" + "metadata:\n" + " foo: string\n") mocktest.TestRunMockTestWithPipeAndFlags( @@ -53,7 +99,18 @@ func TestConnectionsDetach(t *testing.T) { "--api-key", "string", "connections", "detach", "--agent-id", "agent_abc123", - "--connection-id", "env:org_abc123:fleet_abc123::conn_abc123", + "--connection-id", "9f063c59-2775-4614-8a68-22e5f90f92f3", + ) + }) +} + +func TestConnectionsListForAgent(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "connections", "list-for-agent", + "--agent-id", "agent_abc123", ) }) } diff --git a/pkg/cmd/context.go b/pkg/cmd/context.go index 9015c92..0689390 100644 --- a/pkg/cmd/context.go +++ b/pkg/cmd/context.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var contextRetrieve = cli.Command{ Name: "retrieve", - Usage: "Perform retrieve operation", + Usage: "Retrieve context entry", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "key", @@ -36,12 +37,13 @@ var contextRetrieve = cli.Command{ var contextList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List context entries", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -69,12 +71,13 @@ var contextList = cli.Command{ var contextDelete = cli.Command{ Name: "delete", - Usage: "Perform delete operation", + Usage: "Delete context entry", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "key", @@ -89,12 +92,13 @@ var contextDelete = cli.Command{ var contextSearch = cli.Command{ Name: "search", - Usage: "Perform search operation", + Usage: "Search context", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "q", @@ -128,12 +132,13 @@ var contextSearch = cli.Command{ var contextWrite = cli.Command{ Name: "write", - Usage: "Perform write operation", + Usage: "Update context entry", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "content", @@ -169,8 +174,6 @@ func handleContextRetrieve(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ContextGetParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -182,6 +185,8 @@ func handleContextRetrieve(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ContextGetParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Context.Get( @@ -218,8 +223,6 @@ func handleContextList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ContextListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -231,6 +234,8 @@ func handleContextList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ContextListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -286,8 +291,6 @@ func handleContextDelete(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ContextDeleteParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -299,6 +302,8 @@ func handleContextDelete(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ContextDeleteParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Context.Delete( @@ -335,8 +340,6 @@ func handleContextSearch(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ContextSearchParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -348,6 +351,8 @@ func handleContextSearch(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ContextSearchParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -403,8 +408,6 @@ func handleContextWrite(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ContextWriteParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -416,6 +419,8 @@ func handleContextWrite(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ContextWriteParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Context.Write( diff --git a/pkg/cmd/context_test.go b/pkg/cmd/context_test.go index 964297a..0ef2bf9 100644 --- a/pkg/cmd/context_test.go +++ b/pkg/cmd/context_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestContextRetrieve(t *testing.T) { diff --git a/pkg/cmd/credential.go b/pkg/cmd/credential.go deleted file mode 100644 index f764124..0000000 --- a/pkg/cmd/credential.go +++ /dev/null @@ -1,260 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/matrices/cerca-go" - "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var credentialsList = cli.Command{ - Name: "list", - Usage: "Perform list operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "scope", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "cursor", - Usage: "Opaque pagination cursor returned by a previous request.", - QueryPath: "cursor", - }, - &requestflag.Flag[string]{ - Name: "limit", - Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleCredentialsList, - HideHelpCommand: true, -} - -var credentialsDelete = cli.Command{ - Name: "delete", - Usage: "Perform delete operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "scope", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "connection-id", - Required: true, - }, - }, - Action: handleCredentialsDelete, - HideHelpCommand: true, -} - -var credentialsCreateAPIKey = cli.Command{ - Name: "create-api-key", - Usage: "Perform create-api-key operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "scope", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "api-key", - Usage: "API key secret. It is stored securely and is not returned.", - Required: true, - BodyPath: "apiKey", - }, - &requestflag.Flag[string]{ - Name: "provider", - Usage: "Credential provider to store an API key for.", - Required: true, - BodyPath: "provider", - }, - &requestflag.Flag[string]{ - Name: "account-label", - Usage: "Optional human-readable account label.", - BodyPath: "accountLabel", - }, - }, - Action: handleCredentialsCreateAPIKey, - HideHelpCommand: true, -} - -func handleCredentialsList(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("scope") && len(unusedArgs) > 0 { - cmd.Set("scope", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.CredentialListParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - if format == "raw" { - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Credentials.List( - ctx, - cmd.Value("scope").(string), - params, - options..., - ) - if err != nil { - return err - } - obj := gjson.ParseBytes(res) - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "credentials list", - Transform: transform, - }) - } else { - iter := client.Credentials.ListAutoPaging( - ctx, - cmd.Value("scope").(string), - params, - options..., - ) - maxItems := int64(-1) - if cmd.IsSet("max-items") { - maxItems = cmd.Value("max-items").(int64) - } - return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "credentials list", - Transform: transform, - }) - } -} - -func handleCredentialsDelete(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("scope") && len(unusedArgs) > 0 { - cmd.Set("scope", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if !cmd.IsSet("connection-id") && len(unusedArgs) > 0 { - cmd.Set("connection-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Credentials.Delete( - ctx, - cmd.Value("scope").(string), - cmd.Value("connection-id").(string), - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "credentials delete", - Transform: transform, - }) -} - -func handleCredentialsCreateAPIKey(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("scope") && len(unusedArgs) > 0 { - cmd.Set("scope", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.CredentialNewAPIKeyParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Credentials.NewAPIKey( - ctx, - cmd.Value("scope").(string), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "credentials create-api-key", - Transform: transform, - }) -} diff --git a/pkg/cmd/credential_test.go b/pkg/cmd/credential_test.go deleted file mode 100644 index 0a6051d..0000000 --- a/pkg/cmd/credential_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/stainless-sdks/cerca-cli/internal/mocktest" -) - -func TestCredentialsList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "credentials", "list", - "--max-items", "10", - "--scope", "env:org_abc123:fleet_abc123", - "--cursor", "cursor_abc123", - "--limit", "20", - ) - }) -} - -func TestCredentialsDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "credentials", "delete", - "--scope", "env:org_abc123:fleet_abc123", - "--connection-id", "env:org_abc123:fleet_abc123::conn_abc123", - ) - }) -} - -func TestCredentialsCreateAPIKey(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "credentials", "create-api-key", - "--scope", "env:org_abc123:fleet_abc123", - "--api-key", "sk_live_...", - "--provider", "custom", - "--account-label", "primary", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "apiKey: sk_live_...\n" + - "provider: custom\n" + - "accountLabel: primary\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "credentials", "create-api-key", - "--scope", "env:org_abc123:fleet_abc123", - ) - }) -} diff --git a/pkg/cmd/event.go b/pkg/cmd/event.go index 5b50df3..596aa7a 100644 --- a/pkg/cmd/event.go +++ b/pkg/cmd/event.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var eventsListForAgent = cli.Command{ Name: "list-for-agent", - Usage: "Perform list-for-agent operation", + Usage: "List events", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -54,12 +55,13 @@ var eventsListForAgent = cli.Command{ var eventsListForFleet = cli.Command{ Name: "list-for-fleet", - Usage: "Perform list-for-fleet operation", + Usage: "List events", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -92,16 +94,18 @@ var eventsListForFleet = cli.Command{ var eventsListForThread = cli.Command{ Name: "list-for-thread", - Usage: "Perform list-for-thread operation", + Usage: "List events", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -132,49 +136,149 @@ var eventsListForThread = cli.Command{ HideHelpCommand: true, } -var eventsSubscribeAgent = cli.Command{ - Name: "subscribe-agent", - Usage: "WebSocket upgrade endpoint. Set\n`Sec-WebSocket-Protocol: agent-v1, agent-auth-` so the runtime can\nauthenticate the stream while preserving the public subprotocol. HTTP clients\nthat cannot upgrade should use `/agents/{agentId}/events` as the polling analog.", +var eventsStreamForAgent = cli.Command{ + Name: "stream-for-agent", + Usage: "Server-Sent Events stream. Each SSE data frame is JSON matching this response\nschema.", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", + }, + &requestflag.Flag[string]{ + Name: "cursor", + Usage: "Opaque pagination cursor returned by a previous request.", + QueryPath: "cursor", + }, + &requestflag.Flag[string]{ + Name: "events", + Usage: "Comma-separated event type filter.", + QueryPath: "events", + }, + &requestflag.Flag[string]{ + Name: "history", + Usage: "When true, starts from the beginning of the retained buffer.", + QueryPath: "history", + }, + &requestflag.Flag[string]{ + Name: "last-event-id", + Usage: "Resume an event-log stream after the last received SSE event id.", + HeaderPath: "Last-Event-ID", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", }, }, - Action: handleEventsSubscribeAgent, + Action: handleEventsStreamForAgent, HideHelpCommand: true, } -var eventsSubscribeFleet = cli.Command{ - Name: "subscribe-fleet", - Usage: "WebSocket upgrade endpoint. Set\n`Sec-WebSocket-Protocol: agent-v1, agent-auth-` so the runtime can\nauthenticate the stream while preserving the public subprotocol. HTTP clients\nthat cannot upgrade should use `/fleets/{fleetId}/events` as the polling analog.", +var eventsStreamForFleet = cli.Command{ + Name: "stream-for-fleet", + Usage: "Server-Sent Events stream. Each SSE data frame is JSON matching this response\nschema.", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "cursor", + Usage: "Opaque pagination cursor returned by a previous request.", + QueryPath: "cursor", + }, + &requestflag.Flag[string]{ + Name: "events", + Usage: "Comma-separated event type filter.", + QueryPath: "events", + }, + &requestflag.Flag[string]{ + Name: "history", + Usage: "When true, starts from the beginning of the retained buffer.", + QueryPath: "history", + }, + &requestflag.Flag[string]{ + Name: "last-event-id", + Usage: "Resume an event-log stream after the last received SSE event id.", + HeaderPath: "Last-Event-ID", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", }, }, - Action: handleEventsSubscribeFleet, + Action: handleEventsStreamForFleet, HideHelpCommand: true, } -var eventsSubscribeThread = cli.Command{ - Name: "subscribe-thread", - Usage: "WebSocket upgrade endpoint. Set\n`Sec-WebSocket-Protocol: agent-v1, agent-auth-` so the runtime can\nauthenticate the stream while preserving the public subprotocol. HTTP clients\nthat cannot upgrade should use `/agents/{agentId}/threads/{threadId}/events` as\nthe polling analog.", +var eventsStreamForThread = cli.Command{ + Name: "stream-for-thread", + Usage: "Server-Sent Events stream. Each SSE data frame is JSON matching this response\nschema.", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", }, }, - Action: handleEventsSubscribeThread, + Action: handleEventsStreamForThread, + HideHelpCommand: true, +} + +var eventsStreamForThreadEvents = cli.Command{ + Name: "stream-for-thread-events", + Usage: "Server-Sent Events stream. Each SSE data frame is JSON matching this response\nschema.", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "agent-id", + Required: true, + PathParam: "agentId", + }, + &requestflag.Flag[string]{ + Name: "thread-id", + Required: true, + PathParam: "threadId", + }, + &requestflag.Flag[string]{ + Name: "cursor", + Usage: "Opaque pagination cursor returned by a previous request.", + QueryPath: "cursor", + }, + &requestflag.Flag[string]{ + Name: "events", + Usage: "Comma-separated event type filter.", + QueryPath: "events", + }, + &requestflag.Flag[string]{ + Name: "history", + Usage: "When true, starts from the beginning of the retained buffer.", + QueryPath: "history", + }, + &requestflag.Flag[string]{ + Name: "last-event-id", + Usage: "Resume an event-log stream after the last received SSE event id.", + HeaderPath: "Last-Event-ID", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", + }, + }, + Action: handleEventsStreamForThreadEvents, HideHelpCommand: true, } @@ -189,8 +293,6 @@ func handleEventsListForAgent(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.EventListForAgentParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -202,6 +304,8 @@ func handleEventsListForAgent(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.EventListForAgentParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -257,8 +361,6 @@ func handleEventsListForFleet(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.EventListForFleetParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -270,6 +372,8 @@ func handleEventsListForFleet(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.EventListForFleetParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -329,8 +433,6 @@ func handleEventsListForThread(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.EventListForThreadParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -342,6 +444,8 @@ func handleEventsListForThread(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.EventListForThreadParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -388,7 +492,7 @@ func handleEventsListForThread(ctx context.Context, cmd *cli.Command) error { } } -func handleEventsSubscribeAgent(ctx context.Context, cmd *cli.Command) error { +func handleEventsStreamForAgent(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { @@ -410,27 +514,31 @@ func handleEventsSubscribeAgent(ctx context.Context, cmd *cli.Command) error { return err } - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Events.SubscribeAgent(ctx, cmd.Value("agent-id").(string), options...) - if err != nil { - return err - } + params := cercago.EventStreamForAgentParams{} - obj := gjson.ParseBytes(res) format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ + stream := client.Events.StreamForAgentStreaming( + ctx, + cmd.Value("agent-id").(string), + params, + options..., + ) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(stream, maxItems, ShowJSONOpts{ ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "events subscribe-agent", + Title: "events stream-for-agent", Transform: transform, }) } -func handleEventsSubscribeFleet(ctx context.Context, cmd *cli.Command) error { +func handleEventsStreamForFleet(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { @@ -452,27 +560,31 @@ func handleEventsSubscribeFleet(ctx context.Context, cmd *cli.Command) error { return err } - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Events.SubscribeFleet(ctx, cmd.Value("fleet-id").(string), options...) - if err != nil { - return err - } + params := cercago.EventStreamForFleetParams{} - obj := gjson.ParseBytes(res) format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ + stream := client.Events.StreamForFleetStreaming( + ctx, + cmd.Value("fleet-id").(string), + params, + options..., + ) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(stream, maxItems, ShowJSONOpts{ ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "events subscribe-fleet", + Title: "events stream-for-fleet", Transform: transform, }) } -func handleEventsSubscribeThread(ctx context.Context, cmd *cli.Command) error { +func handleEventsStreamForThread(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { @@ -498,27 +610,75 @@ func handleEventsSubscribeThread(ctx context.Context, cmd *cli.Command) error { return err } - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Events.SubscribeThread( + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + stream := client.Events.StreamForThreadStreaming( ctx, cmd.Value("agent-id").(string), cmd.Value("thread-id").(string), options..., ) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(stream, maxItems, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "events stream-for-thread", + Transform: transform, + }) +} + +func handleEventsStreamForThreadEvents(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { + cmd.Set("agent-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if !cmd.IsSet("thread-id") && len(unusedArgs) > 0 { + cmd.Set("thread-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) if err != nil { return err } - obj := gjson.ParseBytes(res) + params := cercago.EventStreamForThreadEventsParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ + stream := client.Events.StreamForThreadEventsStreaming( + ctx, + cmd.Value("agent-id").(string), + cmd.Value("thread-id").(string), + params, + options..., + ) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(stream, maxItems, ShowJSONOpts{ ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "events subscribe-thread", + Title: "events stream-for-thread-events", Transform: transform, }) } diff --git a/pkg/cmd/event_test.go b/pkg/cmd/event_test.go index d4f7521..d483299 100644 --- a/pkg/cmd/event_test.go +++ b/pkg/cmd/event_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestEventsListForAgent(t *testing.T) { @@ -57,36 +57,64 @@ func TestEventsListForThread(t *testing.T) { }) } -func TestEventsSubscribeAgent(t *testing.T) { +func TestEventsStreamForAgent(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "events", "subscribe-agent", + "events", "stream-for-agent", + "--max-items", "10", "--agent-id", "agent_abc123", + "--cursor", "cursor_abc123", + "--events", "thread.created,thread.completed", + "--history", "true", + "--last-event-id", "4711", ) }) } -func TestEventsSubscribeFleet(t *testing.T) { +func TestEventsStreamForFleet(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "events", "subscribe-fleet", + "events", "stream-for-fleet", + "--max-items", "10", "--fleet-id", "fleet_abc123", + "--cursor", "cursor_abc123", + "--events", "thread.created,thread.completed", + "--history", "true", + "--last-event-id", "4711", ) }) } -func TestEventsSubscribeThread(t *testing.T) { +func TestEventsStreamForThread(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "events", "subscribe-thread", + "events", "stream-for-thread", + "--max-items", "10", "--agent-id", "agent_abc123", "--thread-id", "thread_abc123", ) }) } + +func TestEventsStreamForThreadEvents(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "events", "stream-for-thread-events", + "--max-items", "10", + "--agent-id", "agent_abc123", + "--thread-id", "thread_abc123", + "--cursor", "cursor_abc123", + "--events", "thread.created,thread.completed", + "--history", "true", + "--last-event-id", "4711", + ) + }) +} diff --git a/pkg/cmd/flagoptions.go b/pkg/cmd/flagoptions.go index aba8d70..5ba3e7a 100644 --- a/pkg/cmd/flagoptions.go +++ b/pkg/cmd/flagoptions.go @@ -16,11 +16,11 @@ import ( "strings" "unicode/utf8" + "github.com/matrices/cerca-cli/internal/apiform" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/debugmiddleware" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiform" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/debugmiddleware" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/goccy/go-yaml" "github.com/urfave/cli/v3" @@ -339,7 +339,7 @@ func flagOptions( } stdinConsumedByPipe := false - if (bodyType == MultipartFormEncoded || bodyType == ApplicationJSON) && !ignoreStdin && isInputPiped() { + if bodyType != ApplicationOctetStream && !ignoreStdin && isInputPiped() { pipeData, err := io.ReadAll(os.Stdin) if err != nil { return nil, err @@ -353,16 +353,45 @@ func flagOptions( } if bodyMap, ok := bodyData.(map[string]any); ok { applyDataAliases(cmd, bodyMap) - if flagMap, ok := requestContents.Body.(map[string]any); ok { - maps.Copy(bodyMap, flagMap) - requestContents.Body = bodyMap + // Apply any matching keys from the piped data to path, query, and header flags + // that have not already been set via the command line. + if err := requestflag.ApplyStdinDataToFlags(cmd, bodyMap); err != nil { + return nil, err + } + // Re-extract request contents now that flags may have been updated. + requestContents = requestflag.ExtractRequestContents(cmd) + // Remove keys that were consumed as query, header, or path params so they + // don't also leak into the request body via the maps.Copy merge below. + // We delete both the canonical key and any aliases since the user may have + // piped data using an alias name rather than the canonical API name. + for _, flag := range cmd.Flags { + inReq, ok := flag.(requestflag.InRequest) + if !ok || !flag.IsSet() { + continue + } + if inReq.GetQueryPath() != "" || inReq.GetHeaderPath() != "" || inReq.GetPathParam() != "" { + delete(bodyMap, inReq.GetQueryPath()) + delete(bodyMap, inReq.GetHeaderPath()) + delete(bodyMap, inReq.GetPathParam()) + for _, alias := range inReq.GetDataAliases() { + delete(bodyMap, alias) + } + } + } + if bodyType != EmptyBody { + if flagMap, ok := requestContents.Body.(map[string]any); ok { + maps.Copy(bodyMap, flagMap) + requestContents.Body = bodyMap + } else { + bodyData = requestContents.Body + } + } + } else if bodyType != EmptyBody { + if flagMap, ok := requestContents.Body.(map[string]any); ok && len(flagMap) > 0 { + return nil, fmt.Errorf("Cannot merge flags with a body that is not a map: %v", bodyData) } else { - bodyData = requestContents.Body + requestContents.Body = bodyData } - } else if flagMap, ok := requestContents.Body.(map[string]any); ok && len(flagMap) > 0 { - return nil, fmt.Errorf("Cannot merge flags with a body that is not a map: %v", bodyData) - } else { - requestContents.Body = bodyData } } } @@ -370,7 +399,6 @@ func flagOptions( if missingFlags := requestflag.GetMissingRequiredFlags(cmd, requestContents.Body); len(missingFlags) > 0 { if len(missingFlags) == 1 { return nil, fmt.Errorf("Required flag %q not set\nRun '%s --help' for usage information", missingFlags[0].Names()[0], cmd.FullName()) - } else { names := []string{} for _, flag := range missingFlags { diff --git a/pkg/cmd/log.go b/pkg/cmd/log.go deleted file mode 100644 index 43cd751..0000000 --- a/pkg/cmd/log.go +++ /dev/null @@ -1,217 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/matrices/cerca-go" - "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var logsListForAgent = cli.Command{ - Name: "list-for-agent", - Usage: "Perform list-for-agent operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "cursor", - Usage: "Opaque pagination cursor returned by a previous request.", - QueryPath: "cursor", - }, - &requestflag.Flag[string]{ - Name: "limit", - Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleLogsListForAgent, - HideHelpCommand: true, -} - -var logsListForThread = cli.Command{ - Name: "list-for-thread", - Usage: "Perform list-for-thread operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "cursor", - Usage: "Opaque pagination cursor returned by a previous request.", - QueryPath: "cursor", - }, - &requestflag.Flag[string]{ - Name: "limit", - Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleLogsListForThread, - HideHelpCommand: true, -} - -func handleLogsListForAgent(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { - cmd.Set("agent-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.LogListForAgentParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - if format == "raw" { - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Logs.ListForAgent( - ctx, - cmd.Value("agent-id").(string), - params, - options..., - ) - if err != nil { - return err - } - obj := gjson.ParseBytes(res) - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "logs list-for-agent", - Transform: transform, - }) - } else { - iter := client.Logs.ListForAgentAutoPaging( - ctx, - cmd.Value("agent-id").(string), - params, - options..., - ) - maxItems := int64(-1) - if cmd.IsSet("max-items") { - maxItems = cmd.Value("max-items").(int64) - } - return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "logs list-for-agent", - Transform: transform, - }) - } -} - -func handleLogsListForThread(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { - cmd.Set("agent-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if !cmd.IsSet("thread-id") && len(unusedArgs) > 0 { - cmd.Set("thread-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.LogListForThreadParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - if format == "raw" { - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Logs.ListForThread( - ctx, - cmd.Value("agent-id").(string), - cmd.Value("thread-id").(string), - params, - options..., - ) - if err != nil { - return err - } - obj := gjson.ParseBytes(res) - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "logs list-for-thread", - Transform: transform, - }) - } else { - iter := client.Logs.ListForThreadAutoPaging( - ctx, - cmd.Value("agent-id").(string), - cmd.Value("thread-id").(string), - params, - options..., - ) - maxItems := int64(-1) - if cmd.IsSet("max-items") { - maxItems = cmd.Value("max-items").(int64) - } - return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "logs list-for-thread", - Transform: transform, - }) - } -} diff --git a/pkg/cmd/log_test.go b/pkg/cmd/log_test.go deleted file mode 100644 index 733d4c3..0000000 --- a/pkg/cmd/log_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/stainless-sdks/cerca-cli/internal/mocktest" -) - -func TestLogsListForAgent(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "logs", "list-for-agent", - "--max-items", "10", - "--agent-id", "agent_abc123", - "--cursor", "cursor_abc123", - "--limit", "20", - ) - }) -} - -func TestLogsListForThread(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "logs", "list-for-thread", - "--max-items", "10", - "--agent-id", "agent_abc123", - "--thread-id", "thread_abc123", - "--cursor", "cursor_abc123", - "--limit", "20", - ) - }) -} diff --git a/pkg/cmd/model.go b/pkg/cmd/model.go index 2256bbd..58f1e44 100644 --- a/pkg/cmd/model.go +++ b/pkg/cmd/model.go @@ -6,16 +6,16 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var modelsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List models", Suggest: true, Flags: []cli.Flag{}, Action: handleModelsList, diff --git a/pkg/cmd/model_test.go b/pkg/cmd/model_test.go index 06dd28f..0c4ad10 100644 --- a/pkg/cmd/model_test.go +++ b/pkg/cmd/model_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestModelsList(t *testing.T) { diff --git a/pkg/cmd/oauth.go b/pkg/cmd/oauth.go index 6c38c85..92b1e3c 100644 --- a/pkg/cmd/oauth.go +++ b/pkg/cmd/oauth.go @@ -6,22 +6,29 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var oauthConnect = cli.Command{ Name: "connect", - Usage: "Perform connect operation", + Usage: "Start OAuth authorization", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "provider", + Name: "provider", + Required: true, + PathParam: "provider", + }, + &requestflag.Flag[map[string]any]{ + Name: "owner", + Usage: "Public owner for a reusable connection. Organization owners use the authenticated organization; fleet owners add a fleetId.", Required: true, + BodyPath: "owner", }, &requestflag.Flag[string]{ Name: "return-origin", @@ -29,12 +36,6 @@ var oauthConnect = cli.Command{ Required: true, BodyPath: "returnOrigin", }, - &requestflag.Flag[string]{ - Name: "scope", - Usage: "Credential connection scope to attach the OAuth account to.", - Required: true, - BodyPath: "scope", - }, &requestflag.Flag[[]string]{ Name: "scope", Usage: "Provider-specific OAuth scopes. Empty entries are ignored after trimming.", @@ -56,8 +57,6 @@ func handleOAuthConnect(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.OAuthConnectParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -69,6 +68,8 @@ func handleOAuthConnect(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.OAuthConnectParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.OAuth.Connect( diff --git a/pkg/cmd/oauth_test.go b/pkg/cmd/oauth_test.go index 8091f94..e9d6e17 100644 --- a/pkg/cmd/oauth_test.go +++ b/pkg/cmd/oauth_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestOAuthConnect(t *testing.T) { @@ -15,8 +15,8 @@ func TestOAuthConnect(t *testing.T) { "--api-key", "string", "oauth", "connect", "--provider", "google", + "--owner", "{type: organization}", "--return-origin", "https://app.example.com", - "--scope", "env:org_abc123:fleet_abc123", "--scope", "email", "--scope", "profile", ) @@ -25,8 +25,9 @@ func TestOAuthConnect(t *testing.T) { t.Run("piping data", func(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + + "owner:\n" + + " type: organization\n" + "returnOrigin: https://app.example.com\n" + - "scope: env:org_abc123:fleet_abc123\n" + "scopes:\n" + " - email\n" + " - profile\n") diff --git a/pkg/cmd/sandbox.go b/pkg/cmd/sandbox.go index ab7cac1..5ff7126 100644 --- a/pkg/cmd/sandbox.go +++ b/pkg/cmd/sandbox.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var sandboxExec = cli.Command{ Name: "exec", - Usage: "Perform exec operation", + Usage: "Execute sandbox command", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "command", @@ -37,7 +38,7 @@ var sandboxExec = cli.Command{ Usage: "Timeout in seconds. Runtime converts this to milliseconds.", BodyPath: "timeout", }, - &requestflag.Flag[any]{ + &requestflag.Flag[*string]{ Name: "workdir", Usage: "Optional sandbox working directory.", BodyPath: "workdir", @@ -49,12 +50,13 @@ var sandboxExec = cli.Command{ var sandboxRead = cli.Command{ Name: "read", - Usage: "Perform read operation", + Usage: "Read sandbox file", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "path", @@ -76,12 +78,13 @@ var sandboxRead = cli.Command{ var sandboxWrite = cli.Command{ Name: "write", - Usage: "Perform write operation", + Usage: "Write sandbox file", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "content", @@ -109,8 +112,6 @@ func handleSandboxExec(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.SandboxExecParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -122,6 +123,8 @@ func handleSandboxExec(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.SandboxExecParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Sandbox.Exec( @@ -158,8 +161,6 @@ func handleSandboxRead(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.SandboxReadParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -171,6 +172,8 @@ func handleSandboxRead(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.SandboxReadParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Sandbox.Read( @@ -207,8 +210,6 @@ func handleSandboxWrite(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.SandboxWriteParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -220,6 +221,8 @@ func handleSandboxWrite(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.SandboxWriteParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Sandbox.Write( diff --git a/pkg/cmd/sandbox_test.go b/pkg/cmd/sandbox_test.go index f8afa61..a5b313f 100644 --- a/pkg/cmd/sandbox_test.go +++ b/pkg/cmd/sandbox_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestSandboxExec(t *testing.T) { diff --git a/pkg/cmd/schedule.go b/pkg/cmd/schedule.go index 0eb24aa..4183e1c 100644 --- a/pkg/cmd/schedule.go +++ b/pkg/cmd/schedule.go @@ -6,27 +6,28 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var schedulesCreate = cli.Command{ Name: "create", - Usage: "Perform create operation", + Usage: "Create schedule", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "cron", + Name: "message", Required: true, - BodyPath: "cron", + BodyPath: "message", }, &requestflag.Flag[string]{ Name: "name", @@ -34,9 +35,8 @@ var schedulesCreate = cli.Command{ BodyPath: "name", }, &requestflag.Flag[string]{ - Name: "prompt", - Required: true, - BodyPath: "prompt", + Name: "cron", + BodyPath: "cron", }, &requestflag.Flag[string]{ Name: "instructions", @@ -46,12 +46,17 @@ var schedulesCreate = cli.Command{ Name: "model", BodyPath: "model", }, + &requestflag.Flag[any]{ + Name: "run-at", + BodyPath: "runAt", + }, &requestflag.Flag[string]{ Name: "timezone", BodyPath: "timezone", }, &requestflag.Flag[[]string]{ Name: "tool", + Usage: "Per-schedule tool subset. When the schedule starts a thread, these tools can only narrow the agent's effective tools.", BodyPath: "tools", }, }, @@ -61,16 +66,18 @@ var schedulesCreate = cli.Command{ var schedulesUpdate = cli.Command{ Name: "update", - Usage: "Perform update operation", + Usage: "Update schedule", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "schedule-id", - Required: true, + Name: "schedule-id", + Required: true, + PathParam: "scheduleId", }, &requestflag.Flag[string]{ Name: "cron", @@ -84,6 +91,10 @@ var schedulesUpdate = cli.Command{ Name: "instructions", BodyPath: "instructions", }, + &requestflag.Flag[string]{ + Name: "message", + BodyPath: "message", + }, &requestflag.Flag[string]{ Name: "model", BodyPath: "model", @@ -92,9 +103,9 @@ var schedulesUpdate = cli.Command{ Name: "name", BodyPath: "name", }, - &requestflag.Flag[string]{ - Name: "prompt", - BodyPath: "prompt", + &requestflag.Flag[any]{ + Name: "run-at", + BodyPath: "runAt", }, &requestflag.Flag[string]{ Name: "timezone", @@ -102,6 +113,7 @@ var schedulesUpdate = cli.Command{ }, &requestflag.Flag[[]string]{ Name: "tool", + Usage: "Per-schedule tool subset. When updated, these tools can only narrow the agent's effective tools for future scheduled threads.", BodyPath: "tools", }, }, @@ -111,12 +123,13 @@ var schedulesUpdate = cli.Command{ var schedulesList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List schedules", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, }, Action: handleSchedulesList, @@ -125,16 +138,18 @@ var schedulesList = cli.Command{ var schedulesDelete = cli.Command{ Name: "delete", - Usage: "Perform delete operation", + Usage: "Delete schedule", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "schedule-id", - Required: true, + Name: "schedule-id", + Required: true, + PathParam: "scheduleId", }, }, Action: handleSchedulesDelete, @@ -143,16 +158,18 @@ var schedulesDelete = cli.Command{ var schedulesTrigger = cli.Command{ Name: "trigger", - Usage: "Perform trigger operation", + Usage: "Trigger schedule", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "schedule-id", - Required: true, + Name: "schedule-id", + Required: true, + PathParam: "scheduleId", }, }, Action: handleSchedulesTrigger, @@ -170,8 +187,6 @@ func handleSchedulesCreate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ScheduleNewParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -183,6 +198,8 @@ func handleSchedulesCreate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ScheduleNewParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Schedules.New( @@ -223,8 +240,6 @@ func handleSchedulesUpdate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ScheduleUpdateParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -236,6 +251,8 @@ func handleSchedulesUpdate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ScheduleUpdateParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Schedules.Update( diff --git a/pkg/cmd/schedule_test.go b/pkg/cmd/schedule_test.go index 552f569..569c38b 100644 --- a/pkg/cmd/schedule_test.go +++ b/pkg/cmd/schedule_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestSchedulesCreate(t *testing.T) { @@ -15,11 +15,12 @@ func TestSchedulesCreate(t *testing.T) { "--api-key", "string", "schedules", "create", "--agent-id", "agent_abc123", - "--cron", "cron", + "--message", "message", "--name", "name", - "--prompt", "prompt", + "--cron", "cron", "--instructions", "instructions", "--model", "model", + "--run-at", "'2019-12-27T18:11:19.117Z'", "--timezone", "timezone", "--tool", "sandbox.*", ) @@ -28,11 +29,12 @@ func TestSchedulesCreate(t *testing.T) { t.Run("piping data", func(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + - "cron: cron\n" + + "message: message\n" + "name: name\n" + - "prompt: prompt\n" + + "cron: cron\n" + "instructions: instructions\n" + "model: model\n" + + "runAt: '2019-12-27T18:11:19.117Z'\n" + "timezone: timezone\n" + "tools:\n" + " - sandbox.*\n") @@ -56,9 +58,10 @@ func TestSchedulesUpdate(t *testing.T) { "--cron", "cron", "--enabled=true", "--instructions", "instructions", + "--message", "message", "--model", "model", "--name", "name", - "--prompt", "prompt", + "--run-at", "'2019-12-27T18:11:19.117Z'", "--timezone", "timezone", "--tool", "sandbox.*", ) @@ -70,9 +73,10 @@ func TestSchedulesUpdate(t *testing.T) { "cron: cron\n" + "enabled: true\n" + "instructions: instructions\n" + + "message: message\n" + "model: model\n" + "name: name\n" + - "prompt: prompt\n" + + "runAt: '2019-12-27T18:11:19.117Z'\n" + "timezone: timezone\n" + "tools:\n" + " - sandbox.*\n") diff --git a/pkg/cmd/thread.go b/pkg/cmd/thread.go index 4f17576..be25f75 100644 --- a/pkg/cmd/thread.go +++ b/pkg/cmd/thread.go @@ -6,27 +6,32 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var threadsCreate = cli.Command{ Name: "create", - Usage: "Perform create operation", + Usage: "Create thread", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "instructions", BodyPath: "instructions", }, + &requestflag.Flag[string]{ + Name: "message", + BodyPath: "message", + }, &requestflag.Flag[string]{ Name: "model", BodyPath: "model", @@ -38,12 +43,9 @@ var threadsCreate = cli.Command{ }, &requestflag.Flag[[]string]{ Name: "tool", + Usage: "Per-thread tool subset. Omit to inherit the agent's full effective tools; pass [] to run with no configurable tools. Provided entries can only narrow the agent's effective tools.", BodyPath: "tools", }, - &requestflag.Flag[string]{ - Name: "user-message", - BodyPath: "userMessage", - }, }, Action: handleThreadsCreate, HideHelpCommand: true, @@ -51,16 +53,18 @@ var threadsCreate = cli.Command{ var threadsRetrieve = cli.Command{ Name: "retrieve", - Usage: "Perform retrieve operation", + Usage: "Retrieve thread", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ Name: "debug", @@ -79,12 +83,13 @@ var threadsRetrieve = cli.Command{ var threadsList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List threads", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -121,12 +126,14 @@ var threadsCancel = cli.Command{ Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, }, Action: handleThreadsCancel, @@ -139,12 +146,14 @@ var threadsClose = cli.Command{ Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, }, Action: handleThreadsClose, @@ -157,12 +166,14 @@ var threadsCompact = cli.Command{ Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, }, Action: handleThreadsCompact, @@ -171,21 +182,23 @@ var threadsCompact = cli.Command{ var threadsStartTurn = cli.Command{ Name: "start-turn", - Usage: "Perform start-turn operation", + Usage: "Create turn", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ - Name: "user-message", + Name: "message", Required: true, - BodyPath: "userMessage", + BodyPath: "message", }, &requestflag.Flag[string]{ Name: "model", @@ -193,6 +206,7 @@ var threadsStartTurn = cli.Command{ }, &requestflag.Flag[[]string]{ Name: "tool", + Usage: "Per-turn tool subset. Omit to inherit the thread's current tools; pass [] to run this turn with no configurable tools. Provided entries can only narrow the agent/thread effective tools.", BodyPath: "tools", }, }, @@ -206,12 +220,14 @@ var threadsSteer = cli.Command{ Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, + Name: "agent-id", + Required: true, + PathParam: "agentId", }, &requestflag.Flag[string]{ - Name: "thread-id", - Required: true, + Name: "thread-id", + Required: true, + PathParam: "threadId", }, &requestflag.Flag[string]{ Name: "message", @@ -234,8 +250,6 @@ func handleThreadsCreate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ThreadNewParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -247,6 +261,8 @@ func handleThreadsCreate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ThreadNewParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Threads.New( @@ -287,8 +303,6 @@ func handleThreadsRetrieve(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ThreadGetParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -300,6 +314,8 @@ func handleThreadsRetrieve(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ThreadGetParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Threads.Get( @@ -337,8 +353,6 @@ func handleThreadsList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ThreadListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -350,6 +364,8 @@ func handleThreadsList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ThreadListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") @@ -562,8 +578,6 @@ func handleThreadsStartTurn(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ThreadStartTurnParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -575,6 +589,8 @@ func handleThreadsStartTurn(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ThreadStartTurnParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Threads.StartTurn( @@ -616,8 +632,6 @@ func handleThreadsSteer(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.ThreadSteerParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -629,6 +643,8 @@ func handleThreadsSteer(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.ThreadSteerParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Threads.Steer( diff --git a/pkg/cmd/thread_test.go b/pkg/cmd/thread_test.go index 8cc1ea1..9bb04f7 100644 --- a/pkg/cmd/thread_test.go +++ b/pkg/cmd/thread_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestThreadsCreate(t *testing.T) { @@ -16,10 +16,10 @@ func TestThreadsCreate(t *testing.T) { "threads", "create", "--agent-id", "agent_abc123", "--instructions", "instructions", + "--message", "message", "--model", "model", "--system-prompt", "systemPrompt", "--tool", "sandbox.*", - "--user-message", "userMessage", ) }) @@ -27,11 +27,11 @@ func TestThreadsCreate(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + "instructions: instructions\n" + + "message: message\n" + "model: model\n" + "systemPrompt: systemPrompt\n" + "tools:\n" + - " - sandbox.*\n" + - "userMessage: userMessage\n") + " - sandbox.*\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", @@ -115,7 +115,7 @@ func TestThreadsStartTurn(t *testing.T) { "threads", "start-turn", "--agent-id", "agent_abc123", "--thread-id", "thread_abc123", - "--user-message", "userMessage", + "--message", "message", "--model", "model", "--tool", "sandbox.*", ) @@ -124,7 +124,7 @@ func TestThreadsStartTurn(t *testing.T) { t.Run("piping data", func(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + - "userMessage: userMessage\n" + + "message: message\n" + "model: model\n" + "tools:\n" + " - sandbox.*\n") diff --git a/pkg/cmd/tool.go b/pkg/cmd/tool.go index 0cf5de7..7145d93 100644 --- a/pkg/cmd/tool.go +++ b/pkg/cmd/tool.go @@ -6,33 +6,370 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) -var toolsListForAgent = cli.Command{ - Name: "list-for-agent", - Usage: "Perform list-for-agent operation", +var toolsCreate = requestflag.WithInnerFlags(cli.Command{ + Name: "create", + Usage: "Create tool", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "agent-id", + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[map[string]any]{ + Name: "auth", + Usage: "Tool source authentication configuration. The `kind` field selects one of `none`, `api_key`, `oauth_exchange`, or `oauth_connection`.", + Required: true, + BodyPath: "auth", + }, + &requestflag.Flag[string]{ + Name: "namespace", + Required: true, + BodyPath: "namespace", + }, + &requestflag.Flag[[]map[string]any]{ + Name: "tool", + Required: true, + BodyPath: "tools", + }, + &requestflag.Flag[string]{ + Name: "type", + Usage: `Allowed values: "http".`, + Default: "http", + Const: true, + BodyPath: "type", + }, + &requestflag.Flag[string]{ + Name: "approval", + Usage: `Allowed values: "always", "never".`, + BodyPath: "approval", + }, + &requestflag.Flag[bool]{ + Name: "enabled", + BodyPath: "enabled", + }, + &requestflag.Flag[map[string]any]{ + Name: "execution", + Usage: "HTTP tool execution retry and timeout policy.", + BodyPath: "execution", + }, + &requestflag.Flag[string]{ + Name: "url", Required: true, + BodyPath: "url", + }, + }, + Action: handleToolsCreate, + HideHelpCommand: true, +}, map[string][]requestflag.HasOuterFlag{ + "tool": { + &requestflag.InnerFlag[string]{ + Name: "tool.description", + InnerField: "description", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.endpoint", + Usage: "HTTP endpoint invoked when the tool is called.", + InnerField: "endpoint", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.input-schema", + Usage: "JSON Schema object describing tool input parameters.", + InnerField: "inputSchema", + }, + &requestflag.InnerFlag[string]{ + Name: "tool.name", + InnerField: "name", + }, + &requestflag.InnerFlag[string]{ + Name: "tool.approval", + Usage: `Allowed values: "always", "never".`, + InnerField: "approval", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.execution", + Usage: "HTTP tool execution retry and timeout policy.", + InnerField: "execution", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.response", + Usage: "How the HTTP response should be normalized for the agent.", + InnerField: "response", + }, + }, + "execution": { + &requestflag.InnerFlag[string]{ + Name: "execution.idempotency-key-header", + InnerField: "idempotencyKeyHeader", + }, + &requestflag.InnerFlag[float64]{ + Name: "execution.max-attempts", + InnerField: "maxAttempts", + }, + &requestflag.InnerFlag[string]{ + Name: "execution.retry-mode", + Usage: `Allowed values: "disabled", "safe_only", "enabled".`, + InnerField: "retryMode", + }, + &requestflag.InnerFlag[float64]{ + Name: "execution.timeout-ms", + InnerField: "timeoutMs", }, }, - Action: handleToolsListForAgent, +}) + +var toolsRetrieve = cli.Command{ + Name: "retrieve", + Usage: "Retrieve tool source", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "source-id", + Required: true, + PathParam: "sourceId", + }, + }, + Action: handleToolsRetrieve, HideHelpCommand: true, } -func handleToolsListForAgent(ctx context.Context, cmd *cli.Command) error { +var toolsUpdate = requestflag.WithInnerFlags(cli.Command{ + Name: "update", + Usage: "Update tool source", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "source-id", + Required: true, + PathParam: "sourceId", + }, + &requestflag.Flag[map[string]any]{ + Name: "auth", + Usage: "Tool source authentication configuration. The `kind` field selects one of `none`, `api_key`, `oauth_exchange`, or `oauth_connection`.", + Required: true, + BodyPath: "auth", + }, + &requestflag.Flag[string]{ + Name: "namespace", + Required: true, + BodyPath: "namespace", + }, + &requestflag.Flag[[]map[string]any]{ + Name: "tool", + Required: true, + BodyPath: "tools", + }, + &requestflag.Flag[string]{ + Name: "type", + Usage: `Allowed values: "http".`, + Default: "http", + Const: true, + BodyPath: "type", + }, + &requestflag.Flag[string]{ + Name: "approval", + Usage: `Allowed values: "always", "never".`, + BodyPath: "approval", + }, + &requestflag.Flag[bool]{ + Name: "enabled", + BodyPath: "enabled", + }, + &requestflag.Flag[map[string]any]{ + Name: "execution", + Usage: "HTTP tool execution retry and timeout policy.", + BodyPath: "execution", + }, + &requestflag.Flag[string]{ + Name: "url", + Required: true, + BodyPath: "url", + }, + }, + Action: handleToolsUpdate, + HideHelpCommand: true, +}, map[string][]requestflag.HasOuterFlag{ + "tool": { + &requestflag.InnerFlag[string]{ + Name: "tool.description", + InnerField: "description", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.endpoint", + Usage: "HTTP endpoint invoked when the tool is called.", + InnerField: "endpoint", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.input-schema", + Usage: "JSON Schema object describing tool input parameters.", + InnerField: "inputSchema", + }, + &requestflag.InnerFlag[string]{ + Name: "tool.name", + InnerField: "name", + }, + &requestflag.InnerFlag[string]{ + Name: "tool.approval", + Usage: `Allowed values: "always", "never".`, + InnerField: "approval", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.execution", + Usage: "HTTP tool execution retry and timeout policy.", + InnerField: "execution", + }, + &requestflag.InnerFlag[map[string]any]{ + Name: "tool.response", + Usage: "How the HTTP response should be normalized for the agent.", + InnerField: "response", + }, + }, + "execution": { + &requestflag.InnerFlag[string]{ + Name: "execution.idempotency-key-header", + InnerField: "idempotencyKeyHeader", + }, + &requestflag.InnerFlag[float64]{ + Name: "execution.max-attempts", + InnerField: "maxAttempts", + }, + &requestflag.InnerFlag[string]{ + Name: "execution.retry-mode", + Usage: `Allowed values: "disabled", "safe_only", "enabled".`, + InnerField: "retryMode", + }, + &requestflag.InnerFlag[float64]{ + Name: "execution.timeout-ms", + InnerField: "timeoutMs", + }, + }, +}) + +var toolsList = cli.Command{ + Name: "list", + Usage: "List tools", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "cursor", + Usage: "Opaque pagination cursor returned by a previous request.", + QueryPath: "cursor", + }, + &requestflag.Flag[string]{ + Name: "limit", + Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", + QueryPath: "limit", + }, + &requestflag.Flag[int64]{ + Name: "max-items", + Usage: "The maximum number of items to return (use -1 for unlimited).", + }, + }, + Action: handleToolsList, + HideHelpCommand: true, +} + +var toolsDelete = cli.Command{ + Name: "delete", + Usage: "Delete tool source", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "fleet-id", + Required: true, + PathParam: "fleetId", + }, + &requestflag.Flag[string]{ + Name: "source-id", + Required: true, + PathParam: "sourceId", + }, + }, + Action: handleToolsDelete, + HideHelpCommand: true, +} + +func handleToolsCreate(ctx context.Context, cmd *cli.Command) error { client := cercago.NewClient(getDefaultRequestOptions(cmd)...) unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { - cmd.Set("agent-id", unusedArgs[0]) + if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { + cmd.Set("fleet-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + ApplicationJSON, + false, + ) + if err != nil { + return err + } + + params := cercago.ToolNewParams{} + + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Tools.New( + ctx, + cmd.Value("fleet-id").(string), + params, + options..., + ) + if err != nil { + return err + } + + obj := gjson.ParseBytes(res) + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "tools create", + Transform: transform, + }) +} + +func handleToolsRetrieve(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { + cmd.Set("fleet-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { + cmd.Set("source-id", unusedArgs[0]) unusedArgs = unusedArgs[1:] } if len(unusedArgs) > 0 { @@ -52,7 +389,66 @@ func handleToolsListForAgent(ctx context.Context, cmd *cli.Command) error { var res []byte options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Tools.ListForAgent(ctx, cmd.Value("agent-id").(string), options...) + _, err = client.Tools.Get( + ctx, + cmd.Value("fleet-id").(string), + cmd.Value("source-id").(string), + options..., + ) + if err != nil { + return err + } + + obj := gjson.ParseBytes(res) + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "tools retrieve", + Transform: transform, + }) +} + +func handleToolsUpdate(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { + cmd.Set("fleet-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { + cmd.Set("source-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + ApplicationJSON, + false, + ) + if err != nil { + return err + } + + params := cercago.ToolUpdateParams{} + + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Tools.Update( + ctx, + cmd.Value("fleet-id").(string), + cmd.Value("source-id").(string), + params, + options..., + ) if err != nil { return err } @@ -65,7 +461,109 @@ func handleToolsListForAgent(ctx context.Context, cmd *cli.Command) error { ExplicitFormat: explicitFormat, Format: format, RawOutput: cmd.Root().Bool("raw-output"), - Title: "tools list-for-agent", + Title: "tools update", Transform: transform, }) } + +func handleToolsList(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { + cmd.Set("fleet-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) + if err != nil { + return err + } + + params := cercago.ToolListParams{} + + format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") + transform := cmd.Root().String("transform") + if format == "raw" { + var res []byte + options = append(options, option.WithResponseBodyInto(&res)) + _, err = client.Tools.List( + ctx, + cmd.Value("fleet-id").(string), + params, + options..., + ) + if err != nil { + return err + } + obj := gjson.ParseBytes(res) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "tools list", + Transform: transform, + }) + } else { + iter := client.Tools.ListAutoPaging( + ctx, + cmd.Value("fleet-id").(string), + params, + options..., + ) + maxItems := int64(-1) + if cmd.IsSet("max-items") { + maxItems = cmd.Value("max-items").(int64) + } + return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "tools list", + Transform: transform, + }) + } +} + +func handleToolsDelete(ctx context.Context, cmd *cli.Command) error { + client := cercago.NewClient(getDefaultRequestOptions(cmd)...) + unusedArgs := cmd.Args().Slice() + if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { + cmd.Set("fleet-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { + cmd.Set("source-id", unusedArgs[0]) + unusedArgs = unusedArgs[1:] + } + if len(unusedArgs) > 0 { + return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) + } + + options, err := flagOptions( + cmd, + apiquery.NestedQueryFormatBrackets, + apiquery.ArrayQueryFormatComma, + EmptyBody, + false, + ) + if err != nil { + return err + } + + return client.Tools.Delete( + ctx, + cmd.Value("fleet-id").(string), + cmd.Value("source-id").(string), + options..., + ) +} diff --git a/pkg/cmd/tool_test.go b/pkg/cmd/tool_test.go index 2106d31..d811081 100644 --- a/pkg/cmd/tool_test.go +++ b/pkg/cmd/tool_test.go @@ -5,16 +5,237 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/requestflag" ) -func TestToolsListForAgent(t *testing.T) { +func TestToolsCreate(t *testing.T) { t.Run("regular flags", func(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "tools", "list-for-agent", - "--agent-id", "agent_abc123", + "tools", "create", + "--fleet-id", "fleet_abc123", + "--auth", "{kind: none}", + "--namespace", "docs", + "--tool", "{description: Search documents, endpoint: {method: GET, url: https://docs.example.com/search, body: json_params, headers: {foo: string}, path: {foo: params.query}, query: {foo: params.query}}, inputSchema: {type: bar}, name: search, approval: always, execution: {idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}, response: {mode: auto}}", + "--type", "http", + "--approval", "always", + "--enabled=true", + "--execution", "{idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}", + "--url", "https://mcp.example.com", + ) + }) + + t.Run("inner flags", func(t *testing.T) { + // Check that inner flags have been set up correctly + requestflag.CheckInnerFlags(toolsCreate) + + // Alternative argument passing style using inner flags + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "create", + "--fleet-id", "fleet_abc123", + "--auth", "{kind: none}", + "--namespace", "docs", + "--tool.description", "Search documents", + "--tool.endpoint", "{method: GET, url: https://docs.example.com/search, body: json_params, headers: {foo: string}, path: {foo: params.query}, query: {foo: params.query}}", + "--tool.input-schema", "{type: bar}", + "--tool.name", "search", + "--tool.approval", "always", + "--tool.execution", "{idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}", + "--tool.response", "{mode: auto}", + "--type", "http", + "--approval", "always", + "--enabled=true", + "--execution.idempotency-key-header", "idempotencyKeyHeader", + "--execution.max-attempts", "3", + "--execution.retry-mode", "safe_only", + "--execution.timeout-ms", "10000", + "--url", "https://mcp.example.com", + ) + }) + + t.Run("piping data", func(t *testing.T) { + // Test piping YAML data over stdin + pipeData := []byte("" + + "auth:\n" + + " kind: none\n" + + "namespace: docs\n" + + "tools:\n" + + " - description: Search documents\n" + + " endpoint:\n" + + " method: GET\n" + + " url: https://docs.example.com/search\n" + + " body: json_params\n" + + " headers:\n" + + " foo: string\n" + + " path:\n" + + " foo: params.query\n" + + " query:\n" + + " foo: params.query\n" + + " inputSchema:\n" + + " type: bar\n" + + " name: search\n" + + " approval: always\n" + + " execution:\n" + + " idempotencyKeyHeader: idempotencyKeyHeader\n" + + " maxAttempts: 3\n" + + " retryMode: safe_only\n" + + " timeoutMs: 10000\n" + + " response:\n" + + " mode: auto\n" + + "type: http\n" + + "approval: always\n" + + "enabled: true\n" + + "execution:\n" + + " idempotencyKeyHeader: idempotencyKeyHeader\n" + + " maxAttempts: 3\n" + + " retryMode: safe_only\n" + + " timeoutMs: 10000\n" + + "url: https://mcp.example.com\n") + mocktest.TestRunMockTestWithPipeAndFlags( + t, pipeData, + "--api-key", "string", + "tools", "create", + "--fleet-id", "fleet_abc123", + ) + }) +} + +func TestToolsRetrieve(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "retrieve", + "--fleet-id", "fleet_abc123", + "--source-id", "toolsrc_abc123", + ) + }) +} + +func TestToolsUpdate(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "update", + "--fleet-id", "fleet_abc123", + "--source-id", "toolsrc_abc123", + "--auth", "{kind: none}", + "--namespace", "docs", + "--tool", "{description: Search documents, endpoint: {method: GET, url: https://docs.example.com/search, body: json_params, headers: {foo: string}, path: {foo: params.query}, query: {foo: params.query}}, inputSchema: {type: bar}, name: search, approval: always, execution: {idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}, response: {mode: auto}}", + "--type", "http", + "--approval", "always", + "--enabled=true", + "--execution", "{idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}", + "--url", "https://mcp.example.com", + ) + }) + + t.Run("inner flags", func(t *testing.T) { + // Check that inner flags have been set up correctly + requestflag.CheckInnerFlags(toolsUpdate) + + // Alternative argument passing style using inner flags + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "update", + "--fleet-id", "fleet_abc123", + "--source-id", "toolsrc_abc123", + "--auth", "{kind: none}", + "--namespace", "docs", + "--tool.description", "Search documents", + "--tool.endpoint", "{method: GET, url: https://docs.example.com/search, body: json_params, headers: {foo: string}, path: {foo: params.query}, query: {foo: params.query}}", + "--tool.input-schema", "{type: bar}", + "--tool.name", "search", + "--tool.approval", "always", + "--tool.execution", "{idempotencyKeyHeader: idempotencyKeyHeader, maxAttempts: 3, retryMode: safe_only, timeoutMs: 10000}", + "--tool.response", "{mode: auto}", + "--type", "http", + "--approval", "always", + "--enabled=true", + "--execution.idempotency-key-header", "idempotencyKeyHeader", + "--execution.max-attempts", "3", + "--execution.retry-mode", "safe_only", + "--execution.timeout-ms", "10000", + "--url", "https://mcp.example.com", + ) + }) + + t.Run("piping data", func(t *testing.T) { + // Test piping YAML data over stdin + pipeData := []byte("" + + "auth:\n" + + " kind: none\n" + + "namespace: docs\n" + + "tools:\n" + + " - description: Search documents\n" + + " endpoint:\n" + + " method: GET\n" + + " url: https://docs.example.com/search\n" + + " body: json_params\n" + + " headers:\n" + + " foo: string\n" + + " path:\n" + + " foo: params.query\n" + + " query:\n" + + " foo: params.query\n" + + " inputSchema:\n" + + " type: bar\n" + + " name: search\n" + + " approval: always\n" + + " execution:\n" + + " idempotencyKeyHeader: idempotencyKeyHeader\n" + + " maxAttempts: 3\n" + + " retryMode: safe_only\n" + + " timeoutMs: 10000\n" + + " response:\n" + + " mode: auto\n" + + "type: http\n" + + "approval: always\n" + + "enabled: true\n" + + "execution:\n" + + " idempotencyKeyHeader: idempotencyKeyHeader\n" + + " maxAttempts: 3\n" + + " retryMode: safe_only\n" + + " timeoutMs: 10000\n" + + "url: https://mcp.example.com\n") + mocktest.TestRunMockTestWithPipeAndFlags( + t, pipeData, + "--api-key", "string", + "tools", "update", + "--fleet-id", "fleet_abc123", + "--source-id", "toolsrc_abc123", + ) + }) +} + +func TestToolsList(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "list", + "--max-items", "10", + "--fleet-id", "fleet_abc123", + "--cursor", "cursor_abc123", + "--limit", "20", + ) + }) +} + +func TestToolsDelete(t *testing.T) { + t.Run("regular flags", func(t *testing.T) { + mocktest.TestRunMockTestWithFlags( + t, + "--api-key", "string", + "tools", "delete", + "--fleet-id", "fleet_abc123", + "--source-id", "toolsrc_abc123", ) }) } diff --git a/pkg/cmd/toolsource.go b/pkg/cmd/toolsource.go deleted file mode 100644 index a339121..0000000 --- a/pkg/cmd/toolsource.go +++ /dev/null @@ -1,433 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/matrices/cerca-go" - "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var toolSourcesCreate = cli.Command{ - Name: "create", - Usage: "Perform create operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, - }, - &requestflag.Flag[map[string]any]{ - Name: "source", - Required: true, - BodyRoot: true, - }, - }, - Action: handleToolSourcesCreate, - HideHelpCommand: true, -} - -var toolSourcesRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Perform retrieve operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "source-id", - Required: true, - }, - }, - Action: handleToolSourcesRetrieve, - HideHelpCommand: true, -} - -var toolSourcesUpdate = cli.Command{ - Name: "update", - Usage: "Perform update operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "source-id", - Required: true, - }, - &requestflag.Flag[map[string]any]{ - Name: "source", - Required: true, - BodyRoot: true, - }, - }, - Action: handleToolSourcesUpdate, - HideHelpCommand: true, -} - -var toolSourcesList = cli.Command{ - Name: "list", - Usage: "Perform list operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "cursor", - Usage: "Opaque pagination cursor returned by a previous request.", - QueryPath: "cursor", - }, - &requestflag.Flag[string]{ - Name: "limit", - Usage: "Maximum number of items to return. Defaults to 20 and preserves parseInt semantics.", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleToolSourcesList, - HideHelpCommand: true, -} - -var toolSourcesDelete = cli.Command{ - Name: "delete", - Usage: "Perform delete operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, - }, - &requestflag.Flag[string]{ - Name: "source-id", - Required: true, - }, - }, - Action: handleToolSourcesDelete, - HideHelpCommand: true, -} - -var toolSourcesListForAgent = cli.Command{ - Name: "list-for-agent", - Usage: "Perform list-for-agent operation", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "agent-id", - Required: true, - }, - }, - Action: handleToolSourcesListForAgent, - HideHelpCommand: true, -} - -func handleToolSourcesCreate(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { - cmd.Set("fleet-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.ToolSourceNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.ToolSources.New( - ctx, - cmd.Value("fleet-id").(string), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources create", - Transform: transform, - }) -} - -func handleToolSourcesRetrieve(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { - cmd.Set("fleet-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { - cmd.Set("source-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.ToolSources.Get( - ctx, - cmd.Value("fleet-id").(string), - cmd.Value("source-id").(string), - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources retrieve", - Transform: transform, - }) -} - -func handleToolSourcesUpdate(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { - cmd.Set("fleet-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { - cmd.Set("source-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.ToolSourceUpdateParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.ToolSources.Update( - ctx, - cmd.Value("fleet-id").(string), - cmd.Value("source-id").(string), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources update", - Transform: transform, - }) -} - -func handleToolSourcesList(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { - cmd.Set("fleet-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := cercago.ToolSourceListParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - if format == "raw" { - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.ToolSources.List( - ctx, - cmd.Value("fleet-id").(string), - params, - options..., - ) - if err != nil { - return err - } - obj := gjson.ParseBytes(res) - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources list", - Transform: transform, - }) - } else { - iter := client.ToolSources.ListAutoPaging( - ctx, - cmd.Value("fleet-id").(string), - params, - options..., - ) - maxItems := int64(-1) - if cmd.IsSet("max-items") { - maxItems = cmd.Value("max-items").(int64) - } - return ShowJSONIterator(iter, maxItems, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources list", - Transform: transform, - }) - } -} - -func handleToolSourcesDelete(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("fleet-id") && len(unusedArgs) > 0 { - cmd.Set("fleet-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if !cmd.IsSet("source-id") && len(unusedArgs) > 0 { - cmd.Set("source-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - return client.ToolSources.Delete( - ctx, - cmd.Value("fleet-id").(string), - cmd.Value("source-id").(string), - options..., - ) -} - -func handleToolSourcesListForAgent(ctx context.Context, cmd *cli.Command) error { - client := cercago.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("agent-id") && len(unusedArgs) > 0 { - cmd.Set("agent-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.ToolSources.ListForAgent(ctx, cmd.Value("agent-id").(string), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - return ShowJSON(obj, ShowJSONOpts{ - ExplicitFormat: explicitFormat, - Format: format, - RawOutput: cmd.Root().Bool("raw-output"), - Title: "tool-sources list-for-agent", - Transform: transform, - }) -} diff --git a/pkg/cmd/toolsource_test.go b/pkg/cmd/toolsource_test.go deleted file mode 100644 index a5888f7..0000000 --- a/pkg/cmd/toolsource_test.go +++ /dev/null @@ -1,136 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/stainless-sdks/cerca-cli/internal/mocktest" -) - -func TestToolSourcesCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "create", - "--fleet-id", "fleet_abc123", - "--source", "{auth: {kind: bar}, namespace: docs, tools: [{name: bar, description: bar, inputSchema: bar, endpoint: bar}], type: http, approval: always, enabled: true, execution: {timeoutMs: bar, maxAttempts: bar, retryMode: bar}}", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "auth:\n" + - " kind: bar\n" + - "namespace: docs\n" + - "tools:\n" + - " - name: bar\n" + - " description: bar\n" + - " inputSchema: bar\n" + - " endpoint: bar\n" + - "type: http\n" + - "approval: always\n" + - "enabled: true\n" + - "execution:\n" + - " timeoutMs: bar\n" + - " maxAttempts: bar\n" + - " retryMode: bar\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "tool-sources", "create", - "--fleet-id", "fleet_abc123", - ) - }) -} - -func TestToolSourcesRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "retrieve", - "--fleet-id", "fleet_abc123", - "--source-id", "toolsrc_abc123", - ) - }) -} - -func TestToolSourcesUpdate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "update", - "--fleet-id", "fleet_abc123", - "--source-id", "toolsrc_abc123", - "--source", "{auth: {kind: bar}, namespace: docs, tools: [{name: bar, description: bar, inputSchema: bar, endpoint: bar}], type: http, approval: always, enabled: true, execution: {timeoutMs: bar, maxAttempts: bar, retryMode: bar}}", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "auth:\n" + - " kind: bar\n" + - "namespace: docs\n" + - "tools:\n" + - " - name: bar\n" + - " description: bar\n" + - " inputSchema: bar\n" + - " endpoint: bar\n" + - "type: http\n" + - "approval: always\n" + - "enabled: true\n" + - "execution:\n" + - " timeoutMs: bar\n" + - " maxAttempts: bar\n" + - " retryMode: bar\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "tool-sources", "update", - "--fleet-id", "fleet_abc123", - "--source-id", "toolsrc_abc123", - ) - }) -} - -func TestToolSourcesList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "list", - "--max-items", "10", - "--fleet-id", "fleet_abc123", - "--cursor", "cursor_abc123", - "--limit", "20", - ) - }) -} - -func TestToolSourcesDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "delete", - "--fleet-id", "fleet_abc123", - "--source-id", "toolsrc_abc123", - ) - }) -} - -func TestToolSourcesListForAgent(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "tool-sources", "list-for-agent", - "--agent-id", "agent_abc123", - ) - }) -} diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index ad586f4..9bb8168 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -2,4 +2,4 @@ package cmd -const Version = "0.0.1" +const Version = "0.1.0" // x-release-please-version diff --git a/pkg/cmd/webhook.go b/pkg/cmd/webhook.go index b0af311..8ab6372 100644 --- a/pkg/cmd/webhook.go +++ b/pkg/cmd/webhook.go @@ -6,22 +6,23 @@ import ( "context" "fmt" + "github.com/matrices/cerca-cli/internal/apiquery" + "github.com/matrices/cerca-cli/internal/requestflag" "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" - "github.com/stainless-sdks/cerca-cli/internal/apiquery" - "github.com/stainless-sdks/cerca-cli/internal/requestflag" "github.com/tidwall/gjson" "github.com/urfave/cli/v3" ) var webhooksCreate = cli.Command{ Name: "create", - Usage: "Perform create operation", + Usage: "Create webhook", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ Name: "url", @@ -41,16 +42,18 @@ var webhooksCreate = cli.Command{ var webhooksRetrieve = cli.Command{ Name: "retrieve", - Usage: "Perform retrieve operation", + Usage: "Retrieve webhook", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ - Name: "webhook-id", - Required: true, + Name: "webhook-id", + Required: true, + PathParam: "webhookId", }, }, Action: handleWebhooksRetrieve, @@ -59,16 +62,18 @@ var webhooksRetrieve = cli.Command{ var webhooksUpdate = cli.Command{ Name: "update", - Usage: "Perform update operation", + Usage: "Update webhook", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ - Name: "webhook-id", - Required: true, + Name: "webhook-id", + Required: true, + PathParam: "webhookId", }, &requestflag.Flag[bool]{ Name: "enabled", @@ -92,12 +97,13 @@ var webhooksUpdate = cli.Command{ var webhooksList = cli.Command{ Name: "list", - Usage: "Perform list operation", + Usage: "List webhooks", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ Name: "cursor", @@ -120,16 +126,18 @@ var webhooksList = cli.Command{ var webhooksDelete = cli.Command{ Name: "delete", - Usage: "Perform delete operation", + Usage: "Delete webhook", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ - Name: "webhook-id", - Required: true, + Name: "webhook-id", + Required: true, + PathParam: "webhookId", }, }, Action: handleWebhooksDelete, @@ -138,16 +146,18 @@ var webhooksDelete = cli.Command{ var webhooksRotate = cli.Command{ Name: "rotate", - Usage: "Perform rotate operation", + Usage: "Rotate webhook secret", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ - Name: "webhook-id", - Required: true, + Name: "webhook-id", + Required: true, + PathParam: "webhookId", }, }, Action: handleWebhooksRotate, @@ -156,16 +166,18 @@ var webhooksRotate = cli.Command{ var webhooksTest = cli.Command{ Name: "test", - Usage: "Perform test operation", + Usage: "Send test webhook", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ - Name: "fleet-id", - Required: true, + Name: "fleet-id", + Required: true, + PathParam: "fleetId", }, &requestflag.Flag[string]{ - Name: "webhook-id", - Required: true, + Name: "webhook-id", + Required: true, + PathParam: "webhookId", }, }, Action: handleWebhooksTest, @@ -183,8 +195,6 @@ func handleWebhooksCreate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.WebhookNewParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -196,6 +206,8 @@ func handleWebhooksCreate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.WebhookNewParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Webhooks.New( @@ -287,8 +299,6 @@ func handleWebhooksUpdate(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.WebhookUpdateParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -300,6 +310,8 @@ func handleWebhooksUpdate(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.WebhookUpdateParams{} + var res []byte options = append(options, option.WithResponseBodyInto(&res)) _, err = client.Webhooks.Update( @@ -337,8 +349,6 @@ func handleWebhooksList(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } - params := cercago.WebhookListParams{} - options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -350,6 +360,8 @@ func handleWebhooksList(ctx context.Context, cmd *cli.Command) error { return err } + params := cercago.WebhookListParams{} + format := cmd.Root().String("format") explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") diff --git a/pkg/cmd/webhook_test.go b/pkg/cmd/webhook_test.go index a06ef44..9a45b17 100644 --- a/pkg/cmd/webhook_test.go +++ b/pkg/cmd/webhook_test.go @@ -5,7 +5,7 @@ package cmd import ( "testing" - "github.com/stainless-sdks/cerca-cli/internal/mocktest" + "github.com/matrices/cerca-cli/internal/mocktest" ) func TestWebhooksCreate(t *testing.T) {