release: 0.2.0#2
Conversation
e9ea7d3 to
27e3edd
Compare
27e3edd to
25aedf0
Compare
Confidence Score: 5/5 - Safe to MergeSafe to merge — this release PR for version 0.2.0 appears clean with no issues identified across the reviewed files. The automated review found no logic bugs, security concerns, or correctness problems in any of the 6 reviewed changed files. This looks like a straightforward version bump or release packaging PR with no substantive technical concerns raised. Key Findings:
|
25aedf0 to
03c6c45
Compare
|
🧪 Testing To try out this version of the SDK: Expires at: Sun, 07 Jun 2026 05:13:02 GMT |
🐤 Canary SummaryThis PR enhances CLI error messaging for base URL configuration:
Affected User Flows
|
Confidence Score: 5/5 - Safe to MergeSafe to merge — this PR cleanly delivers the 0.2.0 release with well-scoped additions including Key Findings:
Files requiring special attention
|
🐤 Canary Proposed TestsNo testable user journeys found for this PR. |
Confidence Score: 4/5 - Mostly SafeSafe to merge — this PR introduces well-scoped features including Key Findings:
Files requiring special attention
|
| @@ -27,6 +29,8 @@ func TestInnerFlagSet(t *testing.T) { | |||
|
|
|||
| for _, tt := range tests { | |||
| t.Run(tt.name, func(t *testing.T) { | |||
There was a problem hiding this comment.
Correctness: Adding t.Parallel() inside the range loop without capturing tt (via tt := tt) causes all parallel subtests to share the same loop variable — in Go < 1.22, by the time the subtests run, tt will hold the last iteration's value, making all tests use identical inputs.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `internal/requestflag/innerflag_test.go`, inside the `for _, tt := range tests` loop in `TestInnerFlagSet`, `t.Parallel()` is called without first capturing the loop variable. For Go versions before 1.22, this causes a data race where all parallel subtests may use the last value of `tt`. Add `tt := tt` immediately before `t.Parallel()` on the line after `t.Run(tt.name, func(t *testing.T) {` to create a per-iteration local copy.
| // Test initialization and setting | ||
| t.Run("PreParse initialization", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.PreParse()) | ||
| assert.True(t, strFlag.applied) | ||
| assert.Equal(t, "default-string", strFlag.Get()) | ||
| }) | ||
|
|
||
| t.Run("Set string flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.Set("string-flag", "new-value")) | ||
| assert.Equal(t, "new-value", strFlag.Get()) | ||
| assert.True(t, strFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with valid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, superstitiousIntFlag.Set("int-flag", "100")) | ||
| assert.Equal(t, int64(100), superstitiousIntFlag.Get()) | ||
| assert.True(t, superstitiousIntFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with invalid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "not-an-int")) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with validator failing", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "13")) | ||
| }) | ||
|
|
||
| t.Run("Set bool flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, boolFlag.Set("bool-flag", "true")) | ||
| assert.Equal(t, true, boolFlag.Get()) | ||
| assert.True(t, boolFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set slice flag with multiple values", func(t *testing.T) { |
There was a problem hiding this comment.
Correctness: Adding t.Parallel() to these subtests while they all share the same strFlag, superstitiousIntFlag, and boolFlag instances (defined in the outer TestFlagSet scope) introduces data races — concurrent calls to PreParse, Set, and Get on the same flag objects will race under -race, causing flaky or incorrect test results.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `internal/requestflag/requestflag_test.go`, the diff adds `t.Parallel()` to subtests inside `TestFlagSet` (around lines 347–393). However, those subtests all operate on the same shared `strFlag`, `superstitiousIntFlag`, and `boolFlag` variables declared in the outer test function. Running them in parallel causes concurrent reads and writes to those shared `Flag` structs, which is a data race.
Fix: Either (a) remove `t.Parallel()` from the subtests that share these outer-scope flag variables, or (b) move the flag construction inside each subtest so each parallel subtest has its own independent instance. The subtests at the bottom of `TestFlagSet` that already create local `sliceFlag` variables are safe to keep parallel.
Confidence Score: 3/5 - Review RecommendedNot safe to merge without fixes — while this PR delivers meaningful improvements like Key Findings:
Files requiring special attention
|
03c6c45 to
5683b46
Compare
5683b46 to
373f189
Compare
| // Test initialization and setting | ||
| t.Run("PreParse initialization", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.PreParse()) | ||
| assert.True(t, strFlag.applied) | ||
| assert.Equal(t, "default-string", strFlag.Get()) | ||
| }) | ||
|
|
||
| t.Run("Set string flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.Set("string-flag", "new-value")) | ||
| assert.Equal(t, "new-value", strFlag.Get()) | ||
| assert.True(t, strFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with valid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, superstitiousIntFlag.Set("int-flag", "100")) | ||
| assert.Equal(t, int64(100), superstitiousIntFlag.Get()) | ||
| assert.True(t, superstitiousIntFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with invalid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "not-an-int")) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with validator failing", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "13")) | ||
| }) | ||
|
|
||
| t.Run("Set bool flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, boolFlag.Set("bool-flag", "true")) | ||
| assert.Equal(t, true, boolFlag.Get()) | ||
| assert.True(t, boolFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set slice flag with multiple values", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| sliceFlag := &Flag[[]int64]{ | ||
| Name: "slice-flag", | ||
| Default: []int64{}, |
There was a problem hiding this comment.
Correctness: The subtests now run in parallel but all mutate shared strFlag, superstitiousIntFlag, and boolFlag declared in the outer TestFlagSet scope — this introduces data races on Flag internal fields (value, hasBeenSet, applied, count) with no synchronization, causing non-deterministic test failures and potential panics.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `internal/requestflag/requestflag_test.go`, the `t.Parallel()` calls added to subtests inside `TestFlagSet` (starting around line 348) cause data races because `strFlag`, `superstitiousIntFlag`, and `boolFlag` are shared mutable state across parallel subtests. Fix this by either: (1) removing `t.Parallel()` from all subtests that share these outer-scope flag variables, or (2) moving the flag declarations inside each subtest so each parallel subtest operates on its own independent flag instance.
| Usage: "The file to ingest.", | ||
| Required: true, | ||
| BodyPath: "file", | ||
| }, | ||
| &requestflag.Flag[any]{ |
There was a problem hiding this comment.
Correctness: Removing FileInput: true means the requestflag package will no longer treat this flag as a file path to read and stream — it will pass the raw string value (the filename) as the body instead of the file contents, silently breaking the upload endpoint.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/memory.go around line 375, the `FileInput: true` field was removed from the `requestflag.Flag[string]` struct for the 'file' flag in the `memoriesUpload` command. This field is required for the requestflag package to read the actual file contents from the provided path and pass them to the multipart form upload. Without it, only the raw filename string is sent as the body, breaking file uploads silently. Restore `FileInput: true` to the flag definition.
| @@ -114,6 +116,8 @@ func TestEncode(t *testing.T) { | |||
|
|
|||
| for name, test := range tests { | |||
| t.Run(name, func(t *testing.T) { | |||
There was a problem hiding this comment.
Correctness: Adding t.Parallel() inside the range loop without capturing name and test per iteration causes all subtests to close over the same loop variables — in Go < 1.22 they will all run with the last iteration's values, producing incorrect/flaky test results.
Affected Locations:
- internal/apiquery/query_test.go:118-118
- internal/requestflag/innerflag_test.go:31-31
- internal/requestflag/requestflag_test.go:60-61
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In internal/apiquery/query_test.go, around line 116-117, the for-range loop over `tests` was given a `t.Parallel()` call inside the `t.Run` callback. Without capturing the loop variables locally, all parallel subtests will share the same `name` and `test` variables (the last iteration's values in Go < 1.22), causing incorrect test behaviour. Add `name, test := name, test` immediately after the `t.Run` open brace (before `t.Parallel()`) to shadow and capture each iteration's values.
Confidence Score: 1/5 - Blocking IssuesNot safe to merge — this PR introduces multiple correctness-breaking bugs that will cause non-deterministic test failures and a silent functional regression in production. In Key Findings:
Files requiring special attention
|
373f189 to
b0b376a
Compare
b0b376a to
4d95a0b
Compare
| // Test initialization and setting | ||
| t.Run("PreParse initialization", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.PreParse()) | ||
| assert.True(t, strFlag.applied) | ||
| assert.Equal(t, "default-string", strFlag.Get()) | ||
| }) | ||
|
|
||
| t.Run("Set string flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.Set("string-flag", "new-value")) | ||
| assert.Equal(t, "new-value", strFlag.Get()) | ||
| assert.True(t, strFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with valid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, superstitiousIntFlag.Set("int-flag", "100")) | ||
| assert.Equal(t, int64(100), superstitiousIntFlag.Get()) | ||
| assert.True(t, superstitiousIntFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with invalid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "not-an-int")) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with validator failing", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "13")) | ||
| }) | ||
|
|
||
| t.Run("Set bool flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, boolFlag.Set("bool-flag", "true")) | ||
| assert.Equal(t, true, boolFlag.Get()) | ||
| assert.True(t, boolFlag.IsSet()) | ||
| }) | ||
|
|
There was a problem hiding this comment.
Correctness: All subtests now run in parallel but share the same strFlag, superstitiousIntFlag, and boolFlag instances defined in the outer scope — concurrent reads and writes to their internal mutable fields (value, hasBeenSet, applied, count) will cause data races detected by go test -race and produce non-deterministic results.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `internal/requestflag/requestflag_test.go`, the subtests inside `TestFlagSet` (starting around line 347) were made parallel via `t.Parallel()`, but they all share the same `strFlag`, `superstitiousIntFlag`, and `boolFlag` variables declared in the outer function scope. These `Flag` structs have mutable fields (`value`, `hasBeenSet`, `applied`, `count`) that are written concurrently, causing data races. Fix this by either: (1) removing `t.Parallel()` from subtests that share these outer variables, or (2) moving the flag construction inside each subtest so each parallel subtest has its own local instance.
| Suggest: true, | ||
| Flags: []cli.Flag{ | ||
| &requestflag.Flag[string]{ | ||
| Name: "file", | ||
| Usage: "The file to ingest.", | ||
| Required: true, | ||
| BodyPath: "file", | ||
| FileInput: true, | ||
| Name: "file", | ||
| Usage: "The file to ingest.", | ||
| Required: true, | ||
| BodyPath: "file", | ||
| }, | ||
| &requestflag.Flag[any]{ | ||
| Name: "collection", |
There was a problem hiding this comment.
Correctness: The FileInput: true field has been removed from the file flag, which likely means the CLI will pass the raw string (file path) instead of reading and streaming the file contents for multipart upload — breaking the memories upload command.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/memory.go around line 371-380, the `FileInput: true` field was removed from the `requestflag.Flag[string]` definition for the 'file' flag in the `memoriesUpload` command. This field is required so the CLI reads the file from disk and passes its contents (not just the path string) to the multipart form upload. Restore `FileInput: true` to the flag definition to fix the upload functionality.
Confidence Score: 2/5 - Changes NeededNot safe to merge — this PR introduces three high-severity bugs that must be addressed before merging. In Key Findings:
Files requiring special attention
|
| @@ -114,6 +116,8 @@ func TestEncode(t *testing.T) { | |||
|
|
|||
| for name, test := range tests { | |||
| t.Run(name, func(t *testing.T) { | |||
There was a problem hiding this comment.
Correctness: Adding t.Parallel() inside the subtest causes the closure to capture the test loop variable by reference; in Go < 1.22 all parallel subtests will race on the same test value, producing incorrect or flaky results. A local copy (test := test) is needed before t.Parallel() to pin the value per iteration.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In internal/apiquery/query_test.go, around line 118, the subtest closure added `t.Parallel()` but the `test` loop variable is captured by reference. In Go versions before 1.22, all parallel subtests will share the same loop variable, causing data races and incorrect test behavior. Fix by adding `test := test` immediately before `t.Parallel()` inside the closure to create a per-iteration copy of the loop variable.
| // Test initialization and setting | ||
| t.Run("PreParse initialization", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.PreParse()) | ||
| assert.True(t, strFlag.applied) | ||
| assert.Equal(t, "default-string", strFlag.Get()) | ||
| }) | ||
|
|
||
| t.Run("Set string flag", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, strFlag.Set("string-flag", "new-value")) | ||
| assert.Equal(t, "new-value", strFlag.Get()) | ||
| assert.True(t, strFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with valid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.NoError(t, superstitiousIntFlag.Set("int-flag", "100")) | ||
| assert.Equal(t, int64(100), superstitiousIntFlag.Get()) | ||
| assert.True(t, superstitiousIntFlag.IsSet()) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with invalid value", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "not-an-int")) | ||
| }) | ||
|
|
||
| t.Run("Set int flag with validator failing", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| assert.Error(t, superstitiousIntFlag.Set("int-flag", "13")) | ||
| }) | ||
|
|
||
| t.Run("Set bool flag", func(t *testing.T) { | ||
| t.Parallel() |
There was a problem hiding this comment.
Correctness: The subtests PreParse initialization, Set string flag, Set int flag with valid value, Set int flag with invalid value, Set int flag with validator failing, and Set bool flag all share the same strFlag, superstitiousIntFlag, and boolFlag pointers declared in the outer TestFlagSet scope. Adding t.Parallel() to these subtests causes concurrent reads and writes to those shared flag structs (mutating value, hasBeenSet, applied, count), introducing data races that will produce non-deterministic failures or corrupt state under -race.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `internal/requestflag/requestflag_test.go`, the subtests inside `TestFlagSet` that were made parallel (lines ~348-386) all mutate shared flag instances (`strFlag`, `superstitiousIntFlag`, `boolFlag`) declared in the outer function scope. This creates data races. Fix this by either: (1) removing `t.Parallel()` from all subtests that share these outer-scope flag variables, or (2) constructing a fresh flag instance inside each subtest instead of sharing the outer-scope ones. The 'Set slice flag' subtests are fine since they already create local flags.
| Suggest: true, | ||
| Flags: []cli.Flag{ | ||
| &requestflag.Flag[string]{ | ||
| Name: "file", | ||
| Usage: "The file to ingest.", | ||
| Required: true, | ||
| BodyPath: "file", | ||
| FileInput: true, | ||
| Name: "file", | ||
| Usage: "The file to ingest.", | ||
| Required: true, | ||
| BodyPath: "file", | ||
| }, | ||
| &requestflag.Flag[any]{ | ||
| Name: "collection", |
There was a problem hiding this comment.
Correctness: Removing FileInput: true means the file flag will no longer be treated as a file path to open and read — it will be passed as a raw string, causing multipart uploads to send the filename string instead of the actual file contents.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/memory.go around line 371-380, the `FileInput: true` field was removed from the `requestflag.Flag[string]` definition for the 'file' flag in the `memoriesUpload` command. This field is responsible for instructing the flag processing logic to open the file at the given path and read its contents for multipart upload. Without it, the flag value is treated as a plain string (the filename), not the actual file data, breaking file uploads. Please restore `FileInput: true` to this flag definition.
Confidence Score: 1/5 - Blocking IssuesNot safe to merge — this PR introduces data races in parallelized test suites and a functional regression in multipart file uploads. In Key Findings:
Files requiring special attention
|
6d009e5 to
bfb2be0
Compare
There was a problem hiding this comment.
Releases v0.2.0 of the Hyperspell CLI with new features, type safety improvements, and broad test/code quality refactors.
- Adds
--raw-output/-rflag for stripping JSON quotes from string output (jq-style) - Adds stdin support via
-for binary file parameters withonceStdinReadergate - Adds
fileUploadstruct propagating filename and MIME-typed content-type for multipart encoding - Adds
applyDataAliases/rewriteAliasesforx-stainless-cli-data-aliasparameter aliasing - Extends
Flag[T]andInnerFlagto support pointer types (*string,*float64, etc.) enabling tri-state (unset/null/value) semantics for nullable fields - Refactors all command handlers to use
ShowJSONOptsstruct replacing positionalShowJSONarguments - Adds
ValidateBaseURLwith startup enforcement and--base-urlflag validation - Fixes Zsh autocomplete to support both
fpathautoload and direct source methods - Adds
t.Parallel()across all test suites; addsTestNullLiteralHandlingandTestInnerFlagDispatchOnUntypedFlagtest suites - Adds
options.recency-half-life-daysflag anddatebody flag to memory commands - Hardens shell scripts with
${SKIP_BREW:-}expansion andlinkscript module resolution guard - Bumps
@stdy/climock dependency from0.20.2to0.22.1
| if err := json2yaml.Convert(&yaml, input); err != nil { | ||
| return nil, err | ||
| } | ||
| _, err := expectedOutput.Write([]byte(yaml.String())) | ||
| _, err := opts.Stdout.Write([]byte(yaml.String())) |
There was a problem hiding this comment.
Correctness: 🐛 formatJSON for the yaml case writes directly to opts.Stdout and returns nil bytes instead of returning the formatted content — when ShowJSONIterator calls formatJSON to buffer output before deciding whether to use a pager, YAML content is immediately written to real stdout, bypassing pager logic entirely and producing out-of-order or duplicated output.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/cmdutil.go, the `formatJSON` function's `yaml` case (around line 358) writes directly to `opts.Stdout` and returns `(nil, err)` instead of returning the formatted YAML bytes. This breaks `ShowJSONIterator`, which calls `formatJSON` to accumulate bytes before deciding whether to page output — YAML bypasses that buffering and writes straight to stdout. Fix: change `_, err := opts.Stdout.Write([]byte(yaml.String())); return nil, err` to `return []byte(yaml.String()), nil` so the bytes are returned to the caller like every other format case.
There was a problem hiding this comment.
This PR delivers the v0.2.0 release of the Hyperspell CLI, introducing major feature additions, type safety improvements, and cross-cutting refactors.
- Stdin support:
ApplyStdinDataToFlags()routes piped YAML/JSON to path, query, and header flags;onceStdinReaderprevents duplicate stdin consumption for binary file params - File metadata:
fileUploadstruct enriches file references with basename and MIME-typedcontentTypefor multipart uploads - Nullable scalar typing:
Flag[T]andInnerFlagtype constraints extended to pointer-to-primitive types (*string,*float64,*int64,*bool, etc.) enabling tri-state (unset/null/value) field handling --raw-outputflag: New-rflag strips JSON quotes from string results, mirroringjq -rbehaviorShowJSONOptsrefactor: Replaces positionalos.Stdout+ individual parameters with a structured options struct across all command handlers; stdout/stderr become injectable for testabilityValidateBaseURL: Startup and flag-level validation ofHYPERSPELL_BASE_URLwith clear protocol scheme error messages- Zsh autocompletion fix: Script refactored to support both fpath/autoload (
#compdefdirective) and direct sourcing with conditionalcompdeffallback - PathParam annotations:
PathParamfield added to relevant flags acrossconnection,evaluate,folder,integration,memory, and other command files - Test parallelization:
t.Parallel()added across all test suites inapiform,apiquery,autocomplete,jsonview,requestflag, andcmdutil - Script fixes:
bootstrapunbound variable fix,linkscript Go module validation guard,@stdy/clibumped to0.22.1inmock/testscripts
| @@ -143,6 +154,8 @@ func handleAuthUserToken(ctx context.Context, cmd *cli.Command) error { | |||
| return err | |||
There was a problem hiding this comment.
Correctness: AuthUserTokenParams{} is always empty — the user-id (required), expires-in, and origin flag values are never read and assigned into the struct, so the API call is sent without the required user_id field.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/auth.go, the `handleAuthUserToken` function (around line 154) creates an empty `hyperspell.AuthUserTokenParams{}` struct but never populates it with the CLI flag values (`user-id`, `expires-in`, `origin`). Fix this by reading each flag value from `cmd` and assigning them to the appropriate fields of `params` before calling `client.Auth.UserToken`. The `user-id` flag maps to `params.UserID`, `expires-in` maps to `params.ExpiresIn`, and `origin` maps to `params.Origin`.
| // 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. |
There was a problem hiding this comment.
Correctness: InnerFlag.IsSet() unconditionally returns false (as seen in innerflag.go), so the flag.IsSet() guard never skips inner flags — any inner flag explicitly set on the command line will be silently overwritten by piped stdin data.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In internal/requestflag/requestflag.go, the ApplyStdinDataToFlags function (starting around line 144) checks `flag.IsSet()` to skip already-set flags before processing inner flags. However, InnerFlag.IsSet() in internal/requestflag/innerflag.go always returns false, so this guard never fires for inner flags. As a result, any inner flag that was explicitly provided on the command line will be overwritten by piped stdin data. Fix this by adding a separate IsSet-like check for inner flags — for example, track whether an InnerFlag has been set via a dedicated field (similar to hasBeenSet on Flag[T]) and expose it through an interface, then check that interface in ApplyStdinDataToFlags before attempting to set the inner flag from stdin.
bfb2be0 to
fc88702
Compare
fc88702 to
cf22745
Compare
There was a problem hiding this comment.
This PR delivers the v0.2.0 release of the Hyperspell CLI SDK with major infrastructure improvements and new user-facing features.
--raw-outputflag: Added tocmd.go; propagated viaShowJSONOpts.RawOutputthrough all command handlers for jq-style string unquoting- Nullable scalar flags:
requestflag.goandinnerflag.goextended with pointer-typed generics (*string,*bool,*float64,*int64,*DateTimeValue, etc.) supportingnullliteral parsing and proper JSON serialization - Stdin/pipe data aliasing:
flagoptions.gointroducesonceStdinReader,applyDataAliases, and callsApplyStdinDataToFlagsto map piped YAML/JSON onto unset CLI flags by key/alias matching ShowJSONOptsrefactor:cmdutil.goconsolidatesShowJSON/ShowJSONIteratorparameters into a struct with injectablestdout/stderr,ExplicitFormat,RawOutput, and TTY-aware format fallback; all command files updated accordinglyPathParamrouting: Added to flag definitions inconnection.go,evaluate.go,folder.go,integration.go, andmemory.gofor URL path parameter binding- Base URL validation:
ValidateBaseURLadded tocmdutil.goand wired intocmd.goflag andmain.gostartup - File upload metadata:
fileUploadstruct inflagoptions.gowraps readers withfilenameand MIMEcontentTypefor richer multipart encoding - Zsh autocompletion fix:
zsh_autocomplete.zshrefactored for dual-modefpath/direct-source loading - Parallel tests:
t.Parallel()added acrossapiform,apiquery,autocomplete,jsonview,requestflag, andflagoptionstest files - Dependency bump:
@stdy/cliupdated from0.20.2to0.22.1inscripts/mockandscripts/test - Version bump: Manifest,
version.go, andCHANGELOG.mdupdated to0.2.0
cf22745 to
9eee0f2
Compare
There was a problem hiding this comment.
This PR delivers the v0.2.0 release of the Hyperspell CLI with significant feature additions, type safety improvements, and broad refactoring.
- Stdin support: Adds
-as a stdin alias in file/data flags;onceStdinReaderprevents double-consumption; piped YAML/JSON keys are dispatched to matching CLI flags viaApplyStdinDataToFlags --raw-output/-rflag: Strips JSON quotes from string results for shell-friendly outputShowJSONOptsrefactor: Replaces positional arguments across allShowJSON/ShowJSONIteratorcall sites with a structured options struct addingExplicitFormatandRawOutputsupport- Nullable scalar typing: Introduces pointer-typed
Flag[*string],Flag[*float64], etc. for tri-state (unset/null/value) nullable OpenAPI schema support - CLI data aliasing:
DataAliasesfield on flags enables YAML key normalization to canonical API names - File upload metadata:
fileUploadstruct wraps readers with filename and MIME content-type derived from extension PathParamannotations: Path-parameter flags across all command files now declarePathParambindings instead of inline param construction- Base URL validation:
ValidateBaseURLenforces URL scheme at startup and via--base-urlflag - Zsh autocompletion fix: Replaces hardcoded shebang with
#compdefdirective and adds dual-method dispatch - Test parallelism:
t.Parallel()added across all test files; new test suites forValidateBaseURL, null literal handling, inner flag dispatch, andApplyStdinDataToFlags - Tooling:
workflow_dispatchadded to release workflow; bootstrap script fixed forset -u;scripts/linkgains module validation guard;@stdy/clibumped to0.22.1
| return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) | ||
| } |
There was a problem hiding this comment.
Correctness: Removing params := hyperspell.FolderSetPoliciesParams{} while the complete file still passes params to client.Folders.SetPolicies(...) causes an undefined variable compile error.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In pkg/cmd/folder.go, the diff at lines 264-265 removes the `params := hyperspell.FolderSetPoliciesParams{}` declaration from `handleFoldersSetPolicies`, but `params` is still passed as an argument to `client.Folders.SetPolicies(...)` later in the same function. Either restore the `params` declaration, or also remove (or replace) the `params` argument in the `client.Folders.SetPolicies` call to match the intended change.
9eee0f2 to
3f4a0ea
Compare
3f4a0ea to
8845376
Compare
8845376 to
f1ec9cb
Compare
| for _, flag := range cmd.Flags { | ||
| if flag.IsSet() { | ||
| continue |
There was a problem hiding this comment.
InnerFlag.IsSet() unconditionally returns false (innerflag.go:108–109), so the flag.IsSet() guard never skips inner flags regardless of whether the user provided them on the command line. If a user passes --parent.field value via CLI and stdin YAML also contains a matching nested object under outer.GetBodyPath(), ApplyStdinDataToFlags will call flag.Set() on the inner flag, invoking SetInnerField which overwrites the outer map entry with the stdin value.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In file `internal/requestflag/requestflag.go`, in the `ApplyStdinDataToFlags` function (around line 154), after the check `if inner, ok := flag.(HasOuterFlag); ok {`, add a guard to skip inner flags whose outer flag has already been set via the command line. Specifically, after confirming `inner, ok := flag.(HasOuterFlag)` and before looking up values in the data map, check whether the outer flag has already been set for this inner flag's field. One approach: after resolving `outer` at line 155, check `if outer, ok2 := inner.GetOuterFlag().(interface{ IsSet() bool }); ok2 && outer.IsSet() { continue }`. This prevents stdin data from overwriting values the user explicitly provided on the CLI for inner flags, which currently have no protection because `InnerFlag.IsSet()` always returns `false`.
f1ec9cb to
894f210
Compare
894f210 to
57ea61c
Compare
57ea61c to
26ccd3a
Compare
| show_err := cmd.ShowJSON(json, cmd.ShowJSONOpts{ | ||
| ExplicitFormat: app.IsSet("format-error"), | ||
| Format: format, | ||
| Title: "Error", | ||
| Transform: app.String("transform-error"), | ||
| }) |
There was a problem hiding this comment.
RawOutput: cmd.Root().Bool("raw-output") in their ShowJSONOpts. The error-display path in main.go (lines 46–51) constructs ShowJSONOpts without RawOutput, so it always defaults to false. A user pairing --raw-output with --transform-error to extract a plain string from an API error JSON body will get JSON-quoted output instead of the raw string, contradicting the behaviour they see on the success path.
| "cookie", | ||
| "set-cookie", |
There was a problem hiding this comment.
set-cookie entry from sensitiveHeaders — sensitiveHeaders is consumed exclusively in redactRequest() (line 93), which operates on request headers. set-cookie is a response header; the response is dumped at line 61 via httputil.DumpResponse with no redaction pass, so this entry is a no-op and Set-Cookie values (session tokens, auth cookies) are logged in plaintext when debug mode is active.
🤖 AI Agent Prompt for Cursor/Windsurf
📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue
In `internal/debugmiddleware/debug_middleware.go`, the entry `"set-cookie"` at line 27–28 inside `sensitiveHeaders` has no effect. The `sensitiveHeaders` slice is only used in `redactRequest()` (line 93), which processes request headers, but `set-cookie` is a response header. The response is logged at line 61 via `httputil.DumpResponse(resp, true)` with no redaction. To actually redact `Set-Cookie` from responses, add a `redactResponse(*http.Response) (*http.Response, error)` method mirroring `redactRequest`, iterate `m.sensitiveHeaders` against `resp.Header`, and call it before `httputil.DumpResponse` in `Middleware()`. Until then, remove `"set-cookie"` from `sensitiveHeaders` to avoid creating a false sense of security.
26ccd3a to
0b95788
Compare
Automated Release PR
0.2.0 (2026-05-08)
Full Changelog: v0.1.0...v0.2.0
Features
-as value representing stdin to binary-only file parameters in CLIs (40329ac)*_BASE_URL/--base-url(665e431)--raw-output/-roption to print raw (non-JSON) strings (4222549)x-stainless-cli-data-alias(fc1ebc7)Bug Fixes
Chores
ShowJSONIterator(5784e71)--format rawbe used in conjunction with--transform(d9c0c77)ShowJSONOptsas argument toformatJSONinstead of many positionals (9e0c109)t.Parallel()(5fc2402)os.Stdoutisn't necessary (b7fd553)os.Chdirtot.Chdir(2f49e45)Documentation
This pull request is managed by Stainless's GitHub App.
The semver version number is based on included commit messages. Alternatively, you can manually set the version number in the title of this pull request.
For a better experience, it is recommended to use either rebase-merge or squash-merge when merging this pull request.
🔗 Stainless website
📚 Read the docs
🙋 Reach out for help or questions