Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9b81964
fix: improved workflow for developing on branches
stainless-app[bot] Mar 16, 2026
bb63352
fix: better support passing client args in any position
stainless-app[bot] Mar 16, 2026
02e3fa0
fix: no longer require an API key when building on production repos
stainless-app[bot] Mar 16, 2026
e8ef06a
chore(internal): tweak CI branches
stainless-app[bot] Mar 16, 2026
522880b
fix: avoid reading from stdin unless request body is form encoded or …
stainless-app[bot] Mar 17, 2026
6e60912
feat(api): stable beta
stainless-app[bot] Mar 18, 2026
52f718e
fix(api): update flags
stainless-app[bot] Mar 18, 2026
2c06cc2
codegen metadata
stainless-app[bot] Mar 18, 2026
5a65fdf
codegen metadata
stainless-app[bot] Mar 18, 2026
4e5604b
codegen metadata
stainless-app[bot] Mar 18, 2026
a1bc861
codegen metadata
stainless-app[bot] Mar 18, 2026
74a06aa
codegen metadata
stainless-app[bot] Mar 18, 2026
abb8149
codegen metadata
stainless-app[bot] Mar 18, 2026
80069fa
feat(ssh): add ephemeral certificate SSH helper
windsornguyen Mar 18, 2026
ce03f86
fix(ssh): add ephemeral certificate SSH helper (#2)
windsornguyen Mar 18, 2026
d2b4387
Merge remote-tracking branch 'origin/main' into next
stainless-app[bot] Mar 18, 2026
75391d4
fix(ssh): align types with dedalus-go SDK (#4)
windsornguyen Mar 18, 2026
69e4687
fix: improve linking behavior when developing on a branch not in the …
stainless-app[bot] Mar 18, 2026
654e2de
chore(api): update homebrew tap and code samples
stainless-app[bot] Mar 18, 2026
dbb9330
codegen metadata
stainless-app[bot] Mar 18, 2026
1ec3852
Merge remote-tracking branch 'origin/main' into next
stainless-app[bot] Mar 18, 2026
2059194
chore: add curl install script (#6)
windsornguyen Mar 18, 2026
a5a9877
refactor(tests): switch from prism to steady
stainless-app[bot] Mar 19, 2026
a0028a5
chore(tests): bump steady to v0.19.4
stainless-app[bot] Mar 20, 2026
47e0da2
fix(api): add stream-status to workspaces, remove ssh, migrate to steady
stainless-app[bot] Mar 20, 2026
c1794a0
feat: add default description for enum CLI flags without an explicit …
stainless-app[bot] Mar 20, 2026
938cce6
chore(tests): bump steady to v0.19.5
stainless-app[bot] Mar 20, 2026
596fc86
chore(internal): update gitignore
stainless-app[bot] Mar 23, 2026
4049dfc
chore(tests): bump steady to v0.19.6
stainless-app[bot] Mar 23, 2026
2407337
fix: cli no longer hangs when stdin is attached to a pipe with empty …
stainless-app[bot] Mar 23, 2026
3e27739
release: 0.0.2
stainless-app[bot] Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.prism.log
.stdy.log
dist/
/dedalus
*.exe
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 26
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dedalus-labs%2Fdedalus-2412fc9544367895508dc1f8377c71b177df7bbfdf0888087edb84e8067f62d9.yml
openapi_spec_hash: c3563b6ed3c5a0f8ff3a09f8c8725bc4
config_hash: d7156d9b45faaeba89feda6169c2c86a
configured_endpoints: 27
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dedalus-labs%2Fdedalus-a2b38be63dcddaea1a314843f9685b8e26c1f584b1696712f6a9668014afc0a7.yml
openapi_spec_hash: ba6a5b38ed5fa9d49b03b154e3b99b53
config_hash: a71704446fb82d83c7357258c182bdb5
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Changelog

## 0.0.2 (2026-03-23)

Full Changelog: [v0.0.2...v0.0.2](https://github.com/dedalus-labs/dedalus-cli/compare/v0.0.2...v0.0.2)

### Features

* add default description for enum CLI flags without an explicit description ([c1794a0](https://github.com/dedalus-labs/dedalus-cli/commit/c1794a04c9d918b68a9c7f9d1c2226e0ce806830))
* **api:** stable beta ([6e60912](https://github.com/dedalus-labs/dedalus-cli/commit/6e60912f712588b7e92f697809423b93d47cc8c2))
* **ssh:** add ephemeral certificate SSH helper ([80069fa](https://github.com/dedalus-labs/dedalus-cli/commit/80069fad14b33673812488a8bc10e3f9a2bf08e6))


### Bug Fixes

* **api:** add stream-status to workspaces, remove ssh, migrate to steady ([47e0da2](https://github.com/dedalus-labs/dedalus-cli/commit/47e0da24a8f412ef57a0f2e048fbe7ec3569734f))
* **api:** update flags ([52f718e](https://github.com/dedalus-labs/dedalus-cli/commit/52f718e5fa572db767c8697d8e7ababe6992ffc5))
* avoid reading from stdin unless request body is form encoded or json ([522880b](https://github.com/dedalus-labs/dedalus-cli/commit/522880bc900f99d3be158f708491a517af46e81b))
* better support passing client args in any position ([bb63352](https://github.com/dedalus-labs/dedalus-cli/commit/bb633524509e083e78807bf1b652280528bb41b5))
* cli no longer hangs when stdin is attached to a pipe with empty input ([2407337](https://github.com/dedalus-labs/dedalus-cli/commit/2407337d6735e5745f4b418090109d4b80b46fc9))
* improve linking behavior when developing on a branch not in the Go SDK ([69e4687](https://github.com/dedalus-labs/dedalus-cli/commit/69e468741b4d3abe8e710e047fb08633157ffdd2))
* improved workflow for developing on branches ([9b81964](https://github.com/dedalus-labs/dedalus-cli/commit/9b81964097fab54a63bdf1b46a4bd3f556d6b8d3))
* no longer require an API key when building on production repos ([02e3fa0](https://github.com/dedalus-labs/dedalus-cli/commit/02e3fa04b9c2d6229a0dee3218e5900b6cf4ae33))
* **ssh:** add ephemeral certificate SSH helper ([#2](https://github.com/dedalus-labs/dedalus-cli/issues/2)) ([ce03f86](https://github.com/dedalus-labs/dedalus-cli/commit/ce03f86f59032c24a916fc47c102f5d8ce9500ec))
* **ssh:** align types with dedalus-go SDK ([#4](https://github.com/dedalus-labs/dedalus-cli/issues/4)) ([75391d4](https://github.com/dedalus-labs/dedalus-cli/commit/75391d43a3f9491a2bff328f4b1ae5d5ce390e60))


### Chores

* add curl install script ([#6](https://github.com/dedalus-labs/dedalus-cli/issues/6)) ([2059194](https://github.com/dedalus-labs/dedalus-cli/commit/2059194992347690c1800282de0cc9730cf5c3dc))
* **api:** update homebrew tap and code samples ([654e2de](https://github.com/dedalus-labs/dedalus-cli/commit/654e2de50b0f85b285b4932e0238a95d48d07d1a))
* **internal:** tweak CI branches ([e8ef06a](https://github.com/dedalus-labs/dedalus-cli/commit/e8ef06ad15bcd56236656eae8b6f54587a989025))
* **internal:** update gitignore ([596fc86](https://github.com/dedalus-labs/dedalus-cli/commit/596fc86aa50ed8f8746315a9fb6bba13c033ad93))
* **tests:** bump steady to v0.19.4 ([a0028a5](https://github.com/dedalus-labs/dedalus-cli/commit/a0028a5b8d86175fe0e27c8944d9a3c79a26a71b))
* **tests:** bump steady to v0.19.5 ([938cce6](https://github.com/dedalus-labs/dedalus-cli/commit/938cce6410f6c941751494d770726a9d8d581e72))
* **tests:** bump steady to v0.19.6 ([4049dfc](https://github.com/dedalus-labs/dedalus-cli/commit/4049dfcd751f49d72966efcc3c42869a6a63d857))


### Refactors

* **tests:** switch from prism to steady ([a5a9877](https://github.com/dedalus-labs/dedalus-cli/commit/a5a98771bd92763cf690d2e737a52350029fc257))

## 0.0.2 (2026-03-18)

Full Changelog: [v0.0.1...v0.0.2](https://github.com/dedalus-labs/dedalus-cli/compare/v0.0.1...v0.0.2)
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func init() {
&workspacesUpdate,
&workspacesList,
&workspacesDelete,
&workspacesStreamStatus,
},
},
{
Expand Down
30 changes: 28 additions & 2 deletions pkg/cmd/cmdutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,35 @@ var debugMiddlewareOption = option.WithMiddleware(
},
)

// isInputPiped tries to check for input being piped into the CLI which tells us that we should try to read
// from stdin. This can be a bit tricky in some cases like when an stdin is connected to a pipe but nothing is
// being piped in (this may happen in some environments like Cursor's integration terminal or CI), which is
// why this function is a little more elaborate than it'd be otherwise.
func isInputPiped() bool {
stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) == 0
stat, err := os.Stdin.Stat()
if err != nil {
return false
}

mode := stat.Mode()

// Regular file (redirect like < file.txt) — only if non-empty.
//
// Notably, on Unix the case like `< /dev/null` is handled below because `/dev/null` is not a regular
// file. On Windows, NUL appears as a regular file with size 0, so it's also handled correctly.
if mode.IsRegular() && stat.Size() > 0 {
return true
}

// For pipes/sockets (e.g. `echo foo | stainlesscli`), use an OS-specific check to determine whether
// data is actually available. Some environments like Cursor's integrated terminal connect stdin as a
// pipe even when nothing is being piped.
if mode&(os.ModeNamedPipe|os.ModeSocket) != 0 {
// Defined in either cmdutil_unix.go or cmdutil_windows.go.
return isPipedDataAvailableOSSpecific()
}

return false
}

func isTerminal(w io.Writer) bool {
Expand Down
11 changes: 11 additions & 0 deletions pkg/cmd/cmdutil_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import (
"golang.org/x/sys/unix"
)

func isPipedDataAvailableOSSpecific() bool {
// Try to determine if there's non-empty data being piped into the command by polling for data for a short
// amount of time. This is necessary because some environments (e.g. Cursor's integrated terminal) connect
// stdin as a pipe even when nothing is being piped, which would cause the command to block indefinitely
// waiting for input that will never come. The 10 ms timeout is arbitrary -- designed to be long enough to
// allow data to be detected, but short enough that it shouldn't cause a noticeable delay in command runs.
fds := []unix.PollFd{{Fd: int32(os.Stdin.Fd()), Events: unix.POLLIN}}
n, _ := unix.Poll(fds, 10 /* ms */)
return n > 0
}

func streamOutputOSSpecific(label string, generateOutput func(w *os.File) error) error {
// Try to use socket pair for better buffer control
pagerInput, pid, err := openSocketPairPager(label)
Expand Down
26 changes: 25 additions & 1 deletion pkg/cmd/cmdutil_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,31 @@

package cmd

import "os"
import (
"os"
"syscall"
"unsafe"
)

var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procPeekNamedPipe = kernel32.NewProc("PeekNamedPipe")
)

func isPipedDataAvailableOSSpecific() bool {
// On Windows, unix.Poll is not available. Use PeekNamedPipe to check if data is available
// on the pipe without consuming it.
var available uint32
r, _, _ := procPeekNamedPipe.Call(
os.Stdin.Fd(),
0,
0,
0,
uintptr(unsafe.Pointer(&available)),
0,
)
return r != 0 && available > 0
}

func streamOutputOSSpecific(label string, generateOutput func(w *os.File) error) error {
// We have a trick with sockets that we use when possible on Unix-like systems. Those APIs aren't
Expand Down
61 changes: 61 additions & 0 deletions pkg/cmd/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@
HideHelpCommand: true,
}

var workspacesStreamStatus = cli.Command{
Name: "stream-status",
Usage: "Streams workspace lifecycle updates over Server-Sent Events. Each `status` event\ncontains a full `LifecycleResponse` payload. The stream closes after the\nworkspace reaches its current desired state.",
Suggest: true,
Flags: []cli.Flag{
&requestflag.Flag[string]{
Name: "workspace-id",
Required: true,
},
&requestflag.Flag[string]{
Name: "last-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: handleWorkspacesStreamStatus,
HideHelpCommand: true,
}

func handleWorkspacesCreate(ctx context.Context, cmd *cli.Command) error {
client := dedalus.NewClient(getDefaultRequestOptions(cmd)...)
unusedArgs := cmd.Args().Slice()
Expand Down Expand Up @@ -324,3 +346,42 @@
transform := cmd.Root().String("transform")
return ShowJSON(os.Stdout, "workspaces delete", obj, format, transform)
}

func handleWorkspacesStreamStatus(ctx context.Context, cmd *cli.Command) error {
client := dedalus.NewClient(getDefaultRequestOptions(cmd)...)
unusedArgs := cmd.Args().Slice()
if !cmd.IsSet("workspace-id") && len(unusedArgs) > 0 {
cmd.Set("workspace-id", unusedArgs[0])
unusedArgs = unusedArgs[1:]
}
if len(unusedArgs) > 0 {
return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs)
}

params := dedalus.WorkspaceStreamStatusParams{}

Check failure on line 361 in pkg/cmd/workspace.go

View workflow job for this annotation

GitHub Actions / lint

undefined: dedalus.WorkspaceStreamStatusParams

Check failure on line 361 in pkg/cmd/workspace.go

View workflow job for this annotation

GitHub Actions / test

undefined: dedalus.WorkspaceStreamStatusParams

options, err := flagOptions(
cmd,
apiquery.NestedQueryFormatBrackets,
apiquery.ArrayQueryFormatComma,
EmptyBody,
false,
)
if err != nil {
return err
}

format := cmd.Root().String("format")
transform := cmd.Root().String("transform")
stream := client.Workspaces.StreamStatusStreaming(

Check failure on line 376 in pkg/cmd/workspace.go

View workflow job for this annotation

GitHub Actions / lint

client.Workspaces.StreamStatusStreaming undefined (type dedalus.WorkspaceService has no field or method StreamStatusStreaming)

Check failure on line 376 in pkg/cmd/workspace.go

View workflow job for this annotation

GitHub Actions / test

client.Workspaces.StreamStatusStreaming undefined (type dedalus.WorkspaceService has no field or method StreamStatusStreaming)
ctx,
cmd.Value("workspace-id").(string),
params,
options...,
)
maxItems := int64(-1)
if cmd.IsSet("max-items") {
maxItems = cmd.Value("max-items").(int64)
}
return ShowJSONIterator(os.Stdout, "workspaces stream-status", stream, format, transform, maxItems)
}
13 changes: 13 additions & 0 deletions pkg/cmd/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,16 @@ func TestWorkspacesDelete(t *testing.T) {
)
})
}

func TestWorkspacesStreamStatus(t *testing.T) {
t.Run("regular flags", func(t *testing.T) {
mocktest.TestRunMockTestWithFlags(
t,
"--api-key", "string",
"workspaces", "stream-status",
"--max-items", "10",
"--workspace-id", "workspace_id",
"--last-event-id", "Last-Event-ID",
)
})
}
1 change: 1 addition & 0 deletions pkg/cmd/workspacepreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var workspacesPreviewsCreate = cli.Command{
},
&requestflag.Flag[string]{
Name: "protocol",
Usage: `Allowed values: "http", "https".`,
BodyPath: "protocol",
},
&requestflag.Flag[bool]{
Expand Down
Loading
Loading