From 50afe863f2a606f9be967d4c4dbecab89ab36e64 Mon Sep 17 00:00:00 2001 From: scalarbot Date: Mon, 10 Aug 2026 20:28:09 +0000 Subject: [PATCH 1/3] feat(api): initial SDK generation Initial generated SDK release. Build: R9CjqkJYS9JPbv1YHcWj7 --- .github/CODEOWNERS | 4 - .github/actions/setup-go/action.yml | 29 - .github/workflows/ci.yml | 116 - .github/workflows/publish-release.yml | 39 - .github/workflows/sdk-ci.yml | 16 + .gitignore | 8 +- .goreleaser.yml | 110 - .stats.yml | 4 - LICENSE | 202 +- README.md | 191 +- SECURITY.md | 27 - VERSIONING.md | 7 + api.md | 250 + cmd/dedalus/main.go | 83 - examples/.gitkeep | 0 go.mod | 48 - go.sum | 95 - internal/apiform/encoder.go | 236 - internal/apiform/form.go | 20 - internal/apiform/form_test.go | 113 - internal/apiquery/encoder.go | 166 - internal/apiquery/query.go | 53 - internal/apiquery/query_test.go | 132 - internal/autocomplete/autocomplete.go | 361 - internal/autocomplete/autocomplete_test.go | 433 - .../shellscripts/bash_autocomplete.bash | 59 - .../shellscripts/fish_autocomplete.fish | 51 - .../shellscripts/pwsh_autocomplete.ps1 | 97 - .../shellscripts/zsh_autocomplete.zsh | 56 - internal/binaryparam/binary_param.go | 30 - internal/binaryparam/binary_param_test.go | 59 - internal/debugmiddleware/debug_middleware.go | 132 - .../debugmiddleware/debug_middleware_test.go | 201 - internal/jsonview/explorer.go | 807 - internal/jsonview/explorer_test.go | 66 - internal/jsonview/staticdisplay.go | 135 - internal/mocktest/mocktest.go | 101 - internal/requestflag/innerflag.go | 289 - internal/requestflag/innerflag_test.go | 347 - internal/requestflag/requestflag.go | 992 -- internal/requestflag/requestflag_test.go | 1227 -- openapi.augmented.json | 12227 ++++++++++++++++ package.json | 49 + pkg/cmd/cmd.go | 259 - pkg/cmd/cmdutil.go | 531 - pkg/cmd/cmdutil_test.go | 388 - pkg/cmd/cmdutil_unix.go | 127 - pkg/cmd/cmdutil_windows.go | 35 - pkg/cmd/flagoptions.go | 692 - pkg/cmd/flagoptions_test.go | 392 - pkg/cmd/machine.go | 539 - pkg/cmd/machine_test.go | 137 - pkg/cmd/machineartifact.go | 227 - pkg/cmd/machineartifact_test.go | 47 - pkg/cmd/machineexecution.go | 460 - pkg/cmd/machineexecution_test.go | 108 - pkg/cmd/machinepreview.go | 300 - pkg/cmd/machinepreview_test.go | 75 - pkg/cmd/machinessh.go | 290 - pkg/cmd/machinessh_test.go | 70 - pkg/cmd/machineterminal.go | 307 - pkg/cmd/machineterminal_test.go | 80 - pkg/cmd/nesting.go | 45 - pkg/cmd/nesting_test.go | 122 - pkg/cmd/ssh.go | 228 - pkg/cmd/ssh_test.go | 171 - pkg/cmd/startup_update.go | 382 - pkg/cmd/startup_update_test.go | 204 - pkg/cmd/suggest.go | 126 - pkg/cmd/update.go | 377 - pkg/cmd/update_test.go | 323 - pkg/cmd/usage.go | 208 - pkg/cmd/usage_test.go | 47 - pkg/cmd/version.go | 5 - release-please-config.json | 70 - scalar-sdk.manifest.json | 8407 +++++++++++ scripts/bootstrap | 22 - scripts/build | 11 - scripts/finalize-build.mjs | 50 + scripts/format | 8 - scripts/install.ps1 | 289 - scripts/install.sh | 159 - scripts/link | 17 - scripts/lint | 11 - scripts/mock | 52 - scripts/run | 10 - scripts/test | 64 - scripts/unlink | 8 - scripts/utils/upload-artifact.sh | 59 - src/bin.ts | 6 + src/cli/runtime.ts | 707 + src/commands/index.ts | 1602 ++ src/index.ts | 9 + src/sdk/api-promise.ts | 4 + src/sdk/client.ts | 1005 ++ src/sdk/core/EventEmitter.ts | 50 + src/sdk/core/api-promise.ts | 92 + src/sdk/core/error.ts | 130 + src/sdk/core/streaming.ts | 333 + src/sdk/core/uploads.ts | 4 + src/sdk/error.ts | 4 + src/sdk/index.ts | 23 + src/sdk/internal/README.md | 3 + src/sdk/internal/builtin-types.ts | 93 + src/sdk/internal/decoders/line.ts | 135 + src/sdk/internal/detect-platform.ts | 196 + src/sdk/internal/errors.ts | 33 + src/sdk/internal/headers.ts | 97 + src/sdk/internal/parse.ts | 76 + src/sdk/internal/qs/LICENSE.md | 13 + src/sdk/internal/qs/README.md | 3 + src/sdk/internal/qs/formats.ts | 10 + src/sdk/internal/qs/index.ts | 13 + src/sdk/internal/qs/stringify.ts | 385 + src/sdk/internal/qs/types.ts | 71 + src/sdk/internal/qs/utils.ts | 265 + src/sdk/internal/request-options.ts | 93 + src/sdk/internal/shim-types.ts | 26 + src/sdk/internal/shims.ts | 107 + src/sdk/internal/to-file.ts | 154 + src/sdk/internal/types.ts | 93 + src/sdk/internal/uploads.ts | 201 + src/sdk/internal/utils.ts | 8 + src/sdk/internal/utils/base64.ts | 40 + src/sdk/internal/utils/bytes.ts | 32 + src/sdk/internal/utils/env.ts | 18 + src/sdk/internal/utils/log.ts | 128 + src/sdk/internal/utils/path.ts | 122 + src/sdk/internal/utils/sleep.ts | 3 + src/sdk/internal/utils/uuid.ts | 17 + src/sdk/internal/utils/values.ts | 105 + src/sdk/internal/ws-adapter-browser.ts | 123 + src/sdk/internal/ws-adapter-node.ts | 105 + src/sdk/internal/ws-adapter.ts | 30 + src/sdk/internal/ws.ts | 193 + src/sdk/resource.ts | 11 + src/sdk/resources/index.ts | 8 + src/sdk/resources/machine-lifecycle.ts | 3 + src/sdk/resources/machine-lifecycle/index.ts | 6 + .../machine-lifecycle/internal-base.ts | 105 + .../machine-lifecycle/machine-lifecycle.ts | 2616 ++++ .../resources/machine-lifecycle/ws-base.ts | 264 + src/sdk/resources/machine-lifecycle/ws.ts | 42 + src/sdk/resources/usage.ts | 3 + src/sdk/resources/usage/index.ts | 6 + src/sdk/resources/usage/machines.ts | 244 + src/sdk/resources/usage/usage.ts | 88 + src/sdk/streaming.ts | 4 + src/sdk/uploads.ts | 3 + src/sdk/version.ts | 3 + tests/smoke-test.ts | 337 + tsconfig.cjs.json | 26 + tsconfig.json | 27 + 153 files changed, 32044 insertions(+), 14919 deletions(-) delete mode 100644 .github/CODEOWNERS delete mode 100644 .github/actions/setup-go/action.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/publish-release.yml create mode 100644 .github/workflows/sdk-ci.yml delete mode 100644 .goreleaser.yml delete mode 100644 .stats.yml delete mode 100644 SECURITY.md create mode 100644 VERSIONING.md create mode 100644 api.md delete mode 100644 cmd/dedalus/main.go delete mode 100644 examples/.gitkeep delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/apiform/encoder.go delete mode 100644 internal/apiform/form.go delete mode 100644 internal/apiform/form_test.go delete mode 100644 internal/apiquery/encoder.go delete mode 100644 internal/apiquery/query.go delete mode 100644 internal/apiquery/query_test.go delete mode 100644 internal/autocomplete/autocomplete.go delete mode 100644 internal/autocomplete/autocomplete_test.go delete mode 100755 internal/autocomplete/shellscripts/bash_autocomplete.bash delete mode 100644 internal/autocomplete/shellscripts/fish_autocomplete.fish delete mode 100644 internal/autocomplete/shellscripts/pwsh_autocomplete.ps1 delete mode 100644 internal/autocomplete/shellscripts/zsh_autocomplete.zsh delete mode 100644 internal/binaryparam/binary_param.go delete mode 100644 internal/binaryparam/binary_param_test.go delete mode 100644 internal/debugmiddleware/debug_middleware.go delete mode 100644 internal/debugmiddleware/debug_middleware_test.go delete mode 100644 internal/jsonview/explorer.go delete mode 100644 internal/jsonview/explorer_test.go delete mode 100644 internal/jsonview/staticdisplay.go delete mode 100644 internal/mocktest/mocktest.go delete mode 100644 internal/requestflag/innerflag.go delete mode 100644 internal/requestflag/innerflag_test.go delete mode 100644 internal/requestflag/requestflag.go delete mode 100644 internal/requestflag/requestflag_test.go create mode 100644 openapi.augmented.json create mode 100644 package.json delete mode 100644 pkg/cmd/cmd.go delete mode 100644 pkg/cmd/cmdutil.go delete mode 100644 pkg/cmd/cmdutil_test.go delete mode 100644 pkg/cmd/cmdutil_unix.go delete mode 100644 pkg/cmd/cmdutil_windows.go delete mode 100644 pkg/cmd/flagoptions.go delete mode 100644 pkg/cmd/flagoptions_test.go delete mode 100644 pkg/cmd/machine.go delete mode 100644 pkg/cmd/machine_test.go delete mode 100644 pkg/cmd/machineartifact.go delete mode 100644 pkg/cmd/machineartifact_test.go delete mode 100644 pkg/cmd/machineexecution.go delete mode 100644 pkg/cmd/machineexecution_test.go delete mode 100644 pkg/cmd/machinepreview.go delete mode 100644 pkg/cmd/machinepreview_test.go delete mode 100644 pkg/cmd/machinessh.go delete mode 100644 pkg/cmd/machinessh_test.go delete mode 100644 pkg/cmd/machineterminal.go delete mode 100644 pkg/cmd/machineterminal_test.go delete mode 100644 pkg/cmd/nesting.go delete mode 100644 pkg/cmd/nesting_test.go delete mode 100644 pkg/cmd/ssh.go delete mode 100644 pkg/cmd/ssh_test.go delete mode 100644 pkg/cmd/startup_update.go delete mode 100644 pkg/cmd/startup_update_test.go delete mode 100644 pkg/cmd/suggest.go delete mode 100644 pkg/cmd/update.go delete mode 100644 pkg/cmd/update_test.go delete mode 100644 pkg/cmd/usage.go delete mode 100644 pkg/cmd/usage_test.go delete mode 100644 pkg/cmd/version.go delete mode 100644 release-please-config.json create mode 100644 scalar-sdk.manifest.json delete mode 100755 scripts/bootstrap delete mode 100755 scripts/build create mode 100644 scripts/finalize-build.mjs delete mode 100755 scripts/format delete mode 100644 scripts/install.ps1 delete mode 100755 scripts/install.sh delete mode 100755 scripts/link delete mode 100755 scripts/lint delete mode 100755 scripts/mock delete mode 100755 scripts/run delete mode 100755 scripts/test delete mode 100755 scripts/unlink delete mode 100755 scripts/utils/upload-artifact.sh create mode 100644 src/bin.ts create mode 100644 src/cli/runtime.ts create mode 100644 src/commands/index.ts create mode 100644 src/index.ts create mode 100644 src/sdk/api-promise.ts create mode 100644 src/sdk/client.ts create mode 100644 src/sdk/core/EventEmitter.ts create mode 100644 src/sdk/core/api-promise.ts create mode 100644 src/sdk/core/error.ts create mode 100644 src/sdk/core/streaming.ts create mode 100644 src/sdk/core/uploads.ts create mode 100644 src/sdk/error.ts create mode 100644 src/sdk/index.ts create mode 100644 src/sdk/internal/README.md create mode 100644 src/sdk/internal/builtin-types.ts create mode 100644 src/sdk/internal/decoders/line.ts create mode 100644 src/sdk/internal/detect-platform.ts create mode 100644 src/sdk/internal/errors.ts create mode 100644 src/sdk/internal/headers.ts create mode 100644 src/sdk/internal/parse.ts create mode 100644 src/sdk/internal/qs/LICENSE.md create mode 100644 src/sdk/internal/qs/README.md create mode 100644 src/sdk/internal/qs/formats.ts create mode 100644 src/sdk/internal/qs/index.ts create mode 100644 src/sdk/internal/qs/stringify.ts create mode 100644 src/sdk/internal/qs/types.ts create mode 100644 src/sdk/internal/qs/utils.ts create mode 100644 src/sdk/internal/request-options.ts create mode 100644 src/sdk/internal/shim-types.ts create mode 100644 src/sdk/internal/shims.ts create mode 100644 src/sdk/internal/to-file.ts create mode 100644 src/sdk/internal/types.ts create mode 100644 src/sdk/internal/uploads.ts create mode 100644 src/sdk/internal/utils.ts create mode 100644 src/sdk/internal/utils/base64.ts create mode 100644 src/sdk/internal/utils/bytes.ts create mode 100644 src/sdk/internal/utils/env.ts create mode 100644 src/sdk/internal/utils/log.ts create mode 100644 src/sdk/internal/utils/path.ts create mode 100644 src/sdk/internal/utils/sleep.ts create mode 100644 src/sdk/internal/utils/uuid.ts create mode 100644 src/sdk/internal/utils/values.ts create mode 100644 src/sdk/internal/ws-adapter-browser.ts create mode 100644 src/sdk/internal/ws-adapter-node.ts create mode 100644 src/sdk/internal/ws-adapter.ts create mode 100644 src/sdk/internal/ws.ts create mode 100644 src/sdk/resource.ts create mode 100644 src/sdk/resources/index.ts create mode 100644 src/sdk/resources/machine-lifecycle.ts create mode 100644 src/sdk/resources/machine-lifecycle/index.ts create mode 100644 src/sdk/resources/machine-lifecycle/internal-base.ts create mode 100644 src/sdk/resources/machine-lifecycle/machine-lifecycle.ts create mode 100644 src/sdk/resources/machine-lifecycle/ws-base.ts create mode 100644 src/sdk/resources/machine-lifecycle/ws.ts create mode 100644 src/sdk/resources/usage.ts create mode 100644 src/sdk/resources/usage/index.ts create mode 100644 src/sdk/resources/usage/machines.ts create mode 100644 src/sdk/resources/usage/usage.ts create mode 100644 src/sdk/streaming.ts create mode 100644 src/sdk/uploads.ts create mode 100644 src/sdk/version.ts create mode 100644 tests/smoke-test.ts create mode 100644 tsconfig.cjs.json create mode 100644 tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 0dd0347..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# This file is used to automatically assign reviewers to PRs -# For more information see: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners - -* @windsornguyen diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml deleted file mode 100644 index 1c80417..0000000 --- a/.github/actions/setup-go/action.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Setup Go -description: 'Sets up Go environment with private modules' -inputs: - stainless-api-key: - required: false - description: the value of the STAINLESS_API_KEY secret -runs: - using: composite - steps: - - uses: stainless-api/retrieve-github-access-token@1f03f929b746c5b03dcdafa2bebbb18ca5672e1a # v1.0.0 - if: github.repository == 'stainless-sdks/dedalus-cli' - id: get_token - with: - repo: stainless-sdks/dedalus-go - stainless-api-key: ${{ inputs.stainless-api-key }} - - - name: Configure Git for access to the Go SDK's staging repo - if: github.repository == 'stainless-sdks/dedalus-cli' - shell: bash - run: git config --global url."https://x-access-token:${{ steps.get_token.outputs.github_access_token }}@github.com/stainless-sdks/dedalus-go".insteadOf "https://github.com/stainless-sdks/dedalus-go" - - - name: Setup go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 - with: - go-version-file: ./go.mod - - - name: Bootstrap - shell: bash - run: ./scripts/bootstrap diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 3e23906..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: CI -on: - push: - branches: - - '**' - - '!integrated/**' - - '!stl-preview-head/**' - - '!stl-preview-base/**' - - '!generated' - - '!codegen/**' - - 'codegen/stl/**' - pull_request: - branches-ignore: - - 'stl-preview-head/**' - - 'stl-preview-base/**' - -env: - GOPRIVATE: github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go - -jobs: - lint: - timeout-minutes: 10 - name: lint - runs-on: ${{ github.repository == 'stainless-sdks/dedalus-cli' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') - - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: ./.github/actions/setup-go - with: - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Link staging branch - if: github.repository == 'stainless-sdks/dedalus-cli' - run: | - ./scripts/link 'github.com/stainless-sdks/dedalus-go@${{ github.ref_name }}' || true - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Run lints - run: ./scripts/lint - - build: - timeout-minutes: 10 - name: build - permissions: - contents: read - id-token: write - runs-on: ${{ github.repository == 'stainless-sdks/dedalus-cli' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: ./.github/actions/setup-go - with: - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Link staging branch - if: github.repository == 'stainless-sdks/dedalus-cli' - run: | - ./scripts/link 'github.com/stainless-sdks/dedalus-go@${{ github.ref_name }}' || true - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Run goreleaser - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 - with: - version: latest - args: release --snapshot --clean --skip=publish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get GitHub OIDC Token - if: |- - github.repository == 'stainless-sdks/dedalus-cli' && - !startsWith(github.ref, 'refs/heads/stl/') - id: github-oidc - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: core.setOutput('github_token', await core.getIDToken()); - - - name: Upload tarball - if: |- - github.repository == 'stainless-sdks/dedalus-cli' && - !startsWith(github.ref, 'refs/heads/stl/') - env: - URL: https://pkg.stainless.com/s - AUTH: ${{ steps.github-oidc.outputs.github_token }} - SHA: ${{ github.sha }} - run: ./scripts/utils/upload-artifact.sh - - test: - timeout-minutes: 10 - name: test - runs-on: ${{ github.repository == 'stainless-sdks/dedalus-cli' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - uses: ./.github/actions/setup-go - with: - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Link staging branch - if: github.repository == 'stainless-sdks/dedalus-cli' - run: | - ./scripts/link 'github.com/stainless-sdks/dedalus-go@${{ github.ref_name }}' || true - - - name: Bootstrap - run: ./scripts/bootstrap - - - name: Run tests - run: ./scripts/test diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml deleted file mode 100644 index aaa5171..0000000 --- a/.github/workflows/publish-release.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Publish Release -permissions: - contents: write - -concurrency: - group: publish - -on: - push: - tags: - - "v*" - workflow_dispatch: {} -jobs: - goreleaser: - runs-on: ubuntu-latest - environment: production - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 - with: - go-version-file: "go.mod" - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} - MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} - MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} - MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} - MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} \ No newline at end of file diff --git a/.github/workflows/sdk-ci.yml b/.github/workflows/sdk-ci.yml new file mode 100644 index 0000000..2418049 --- /dev/null +++ b/.github/workflows/sdk-ci.yml @@ -0,0 +1,16 @@ +name: CLI SDK CI + +on: + push: + pull_request: + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "20" + - run: npm install + - run: npm run build diff --git a/.gitignore b/.gitignore index 3911bfe..a8fdd94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -.prism.log -.stdy.log +node_modules/ dist/ -/dedalus -*.exe +*.tsbuildinfo +.env +.env.* diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 3a2e2ed..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,110 +0,0 @@ -project_name: dedalus -version: 2 - -before: - hooks: - - mkdir -p completions - - sh -c "go run ./cmd/dedalus/main.go @completion bash > completions/dedalus.bash" - - sh -c "go run ./cmd/dedalus/main.go @completion zsh > completions/dedalus.zsh" - - sh -c "go run ./cmd/dedalus/main.go @completion fish > completions/dedalus.fish" - - sh -c "go run ./cmd/dedalus/main.go @manpages -o man" - -builds: - - id: macos - goos: [darwin] - goarch: [amd64, arm64] - binary: '{{ .ProjectName }}' - main: ./cmd/dedalus/main.go - mod_timestamp: '{{ .CommitTimestamp }}' - ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' - - - id: linux - goos: [linux] - goarch: ['386', arm, amd64, arm64] - env: - - CGO_ENABLED=0 - binary: '{{ .ProjectName }}' - main: ./cmd/dedalus/main.go - mod_timestamp: '{{ .CommitTimestamp }}' - ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' - - - id: windows - goos: [windows] - goarch: ['386', amd64, arm64] - binary: '{{ .ProjectName }}' - main: ./cmd/dedalus/main.go - mod_timestamp: '{{ .CommitTimestamp }}' - ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' - -archives: - - id: linux-archive - ids: [linux] - name_template: '{{ .ProjectName }}_{{ .Version }}_linux_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - formats: [tar.gz] - files: - - completions/* - - man/*/* - - id: macos-archive - ids: [macos] - name_template: '{{ .ProjectName }}_{{ .Version }}_macos_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - formats: [zip] - files: - - completions/* - - man/*/* - - id: windows-archive - ids: [windows] - name_template: '{{ .ProjectName }}_{{ .Version }}_windows_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - formats: [zip] - files: - - completions/* - - man/*/* - -snapshot: - version_template: '{{ .Tag }}-next' - -nfpms: - - license: MIT - maintainer: oss@dedaluslabs.ai - bindir: /usr - formats: - - apk - - deb - - rpm - - termux.deb - - archlinux - contents: - - src: man/man1/*.1.gz - dst: /usr/share/man/man1/ -homebrew_casks: - - name: dedalus - repository: - owner: dedalus-labs - name: homebrew-tap - token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" - homepage: https://docs.dedaluslabs.ai - description: The official CLI for Dedalus. - license: MIT - binary: "dedalus" - completions: - bash: "completions/dedalus.bash" - zsh: "completions/dedalus.zsh" - fish: "completions/dedalus.fish" - manpages: - - man/man1/dedalus.1.gz - -notarize: - macos: - - enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}' - ids: [macos] - - sign: - certificate: "{{.Env.MACOS_SIGN_P12}}" - password: "{{.Env.MACOS_SIGN_PASSWORD}}" - - notarize: - issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}" - key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}" - key: "{{.Env.MACOS_NOTARY_KEY}}" diff --git a/.stats.yml b/.stats.yml deleted file mode 100644 index dcb3502..0000000 --- a/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 32 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dedalus-labs/dedalus-32ccb3c17674e0ee68fd6eafbdd0f210bccfd09fce0702e28b8278e06678deec.yml -openapi_spec_hash: ccb02923079d91569a17162c88da590b -config_hash: 3b16603a18779d453842a0d56638384d diff --git a/LICENSE b/LICENSE index 3aa6139..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,201 @@ -Copyright 2026 Dedalus + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + 1. Definitions. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 316df30..4a4937b 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,132 @@ -# Dedalus CLI +# Dedalus -The official CLI for the [Dedalus REST API](https://docs.dedaluslabs.ai). +Generated CLI SDK for Dedalus API. +Controlplane API for Dedalus Cloud Services (DCS). -It is generated with [Stainless](https://www.stainless.com/). +
- +## Contents -## Installation +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](./api.md) +- [Streaming](#streaming) +- [WebSockets](#websockets) +- [Authentication](#authentication) +- [Errors](#errors) +- [Client Options](#client-options) +- [Retries and Timeouts](#retries-and-timeouts) +- [Helpers](#helpers) +- [Logging](#logging) +- [Requirements](#requirements) + +
-### Installing with Homebrew +## Installation ```sh -brew install dedalus-labs/tap/dedalus +npm install -g dedalus-cli ``` -### Installing with Go +
-To test or install the CLI locally, you need [Go](https://go.dev/doc/install) version 1.22 or later installed. +## Usage ```sh -go install 'github.com/dedalus-labs/dedalus-cli/cmd/dedalus@latest' +dedalus [resource] [command] [flags] + +dedalus machine-lifecycle list --bearer "$BEARER" ``` -Once you have run `go install`, the binary is placed in your Go bin directory: +The examples in the following sections assume a `client` configured as shown above. -- **Default location**: `$HOME/go/bin` (or `$GOPATH/bin` if GOPATH is set) -- **Check your path**: Run `go env GOPATH` to see the base directory +See the [API reference](./api.md) for every available operation. -If commands aren't found after installation, add the Go bin directory to your PATH: +
-```sh -# Add to your shell profile (.zshrc, .bashrc, etc.) -export PATH="$PATH:$(go env GOPATH)/bin" -``` +## Streaming - +Streaming commands emit one result per line as the server sends it. Use `--max-items ` to stop after N items. -### Updating +
-```sh -dedalus update -``` +## WebSockets -To check the latest available release without installing it: +WebSocket commands stay connected and stream messages. Use `--send ` to send a message (or pipe JSON/YAML on stdin) and `--max-items ` to bound output. -```sh -dedalus update --check -``` +
-The updater respects how the CLI was installed. Homebrew installs delegate to -`brew upgrade`, macOS/Linux curl installs rerun the install script for the -current executable directory, and Windows installs print the PowerShell installer -command because Windows cannot replace the running `dedalus.exe` process. +## Authentication -### Running Locally +Pass credentials to the generated client constructor. Environment variables are read automatically when supported by the target runtime. -After cloning the git repository for this project, you can use the -`scripts/run` script to run the tool locally: +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--api-key-auth` | `string \| provider` | - | API key authentication using X-API-Key header Defaults to API_KEY_AUTH. | +| `--bearer-auth` | `string \| provider` | - | Dedalus API key in Authorization: Bearer . Defaults to BEARER_AUTH. | +| `--bearer` | `string \| provider` | - | API key authentication using Bearer token Defaults to BEARER. | -```sh -./scripts/run args... -``` +Declared schemes: -## Usage +- `ApiKeyAuth` API key in header `x-api-key` +- `BearerAuth` bearer token +- `Bearer` bearer token -The CLI follows a resource-based command structure: +
-```sh -dedalus [resource] [flags...] -``` +## Errors -```sh -dedalus machines create \ - --api-key 'My API Key' \ - --memory-mib 2048 \ - --storage-gib 10 \ - --vcpu 1 -``` +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. -For details about specific commands, use the `--help` flag. +Documented error statuses: `400`, `401`, `403`, `409`, `429`, `500`, `502`, `503`, `default`. -### Environment variables +
-| Environment variable | Description | Required | Default value | -| -------------------- | --------------------------------------------- | -------- | ------------- | -| `DEDALUS_API_KEY` | Dedalus API key sent as Authorization Bearer. | no | `null` | -| `DEDALUS_X_API_KEY` | Dedalus API key sent as x-api-key header. | no | `null` | -| `DEDALUS_ORG_ID` | Organization ID header for all DCS requests. | no | `null` | +## Client Options -### Global flags +Configure the generated client by setting any of these options when you create it. -- `--api-key` - Dedalus API key sent as Authorization Bearer. (can also be set with `DEDALUS_API_KEY` env var) -- `--x-api-key` - Dedalus API key sent as x-api-key header. (can also be set with `DEDALUS_X_API_KEY` env var) -- `--dedalus-org-id` - Organization ID header for all DCS requests. (can also be set with `DEDALUS_ORG_ID` env var) -- `--help` - Show command line usage -- `--debug` - Enable debug logging (includes HTTP request/response details) -- `--version`, `-v` - Show the CLI version -- `--base-url` - Use a custom API backend URL -- `--format` - Change the output format (`auto`, `explore`, `json`, `jsonl`, `pretty`, `raw`, `yaml`) -- `--format-error` - Change the output format for errors (`auto`, `explore`, `json`, `jsonl`, `pretty`, `raw`, `yaml`) -- `--transform` - Transform the data output using [GJSON syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) -- `--transform-error` - Transform the error output using [GJSON syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) +| Option | Type | Default | Description | +| --- | --- | --- | --- | +| `--base-url` | `` | - | Override the base URL for API requests. | +| `--timeout` | `` | - | Request timeout in milliseconds. | +| `--max-retries` | `` | - | Number of retries for retryable failures. | +| `--debug` | `flag` | - | Enable SDK debug logging. | -### Passing files as arguments +
-To pass files to your API, you can use the `@myfile.ext` syntax: +## Retries and Timeouts -```bash -dedalus --arg @abe.jpg -``` +Generated clients support request timeouts and retry temporary failures such as network errors, 408, 409, 429, and 5xx responses. Retry delays honor `Retry-After` headers when present. Tune the retry and timeout client options shown above, or override them per request. -Files can also be passed inside JSON or YAML blobs: +
-```bash -dedalus --arg '{image: "@abe.jpg"}' -# Equivalent: -dedalus <` — output format: `auto`, `json`, `jsonl`, `pretty`, `raw`, or `yaml`. +- `--format-error ` — error output format: `auto`, `json`, `jsonl`, `pretty`, `raw`, or `yaml`. +- `--transform ` and `--transform-error ` — dot-path transform for data/error output. +- `--raw-output`, `-r` — print transformed string values without JSON quotes. +- `--max-items ` — bound iterator, streaming, and WebSocket command output. -```bash -dedalus --username '\@abe' -``` +
-#### Explicit encoding +## Logging -For JSON endpoints, the CLI tool does filetype sniffing to determine whether the -file contents should be sent as a string literal (for plain text files) or as a -base64-encoded string literal (for binary files). If you need to explicitly send -the file as either plain text or base64-encoded data, you can use -`@file://myfile.txt` (for string encoding) or `@data://myfile.dat` (for -base64-encoding). Note that absolute paths will begin with `@file://` or -`@data://`, followed by a third `/` (for example, `@file:///tmp/file.txt`). +- Pass `--debug` to any command to enable SDK debug logging on stderr. -```bash -dedalus --arg @data://file.txt -``` +
-## Linking different Go SDK versions +## Requirements -You can link the CLI against a different version of the Dedalus Go SDK -for development purposes using the `./scripts/link` script. +- Node.js 20 or newer -To link to a specific version from a repository (version can be a branch, -git tag, or commit hash): +Powered by Scalar. -```bash -./scripts/link github.com/org/repo@version -``` -To link to a local copy of the SDK: +## Contributions -```bash -./scripts/link ../path/to/dedalus-go -``` +This SDK is generated programmatically. Manual edits to generated files will be +overwritten on the next build. -If you run the link script without any arguments, it will default to `../dedalus-go`. +### SDK created by [Scalar](https://www.scalar.com/?utm_source=dedalus-cloud-services-api-cli&utm_campaign=sdk) diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 8efb336..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting Security Issues - -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. - -## Responsible Disclosure - -We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible -disclosure practices by allowing us a reasonable amount of time to investigate and address the issue -before making any information public. - -## Reporting Non-SDK Related Security Issues - -If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Dedalus, please follow the respective company's security reporting guidelines. - -### Dedalus Terms and Policies - -Please contact security@dedaluslabs.ai for any questions or concerns regarding the security of our services. - ---- - -Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..8f16c2c --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,7 @@ +# Versioning + +This SDK is configured with the `manual` versioning policy. + +- `manual`: package versions are set explicitly before release. +- `semver`: releases should follow semantic versioning based on API and SDK surface changes. +- `calendar`: releases should use a calendar-derived version chosen by the release workflow or maintainer. diff --git a/api.md b/api.md new file mode 100644 index 0000000..8810bf3 --- /dev/null +++ b/api.md @@ -0,0 +1,250 @@ +# Dedalus CLI API + +Complete reference of every operation, grouped by resource. See [the README](./README.md) for usage and configuration. + +## Contents + +- [`MachineLifecycle`](#machinelifecycle) + - [List machines](#list-machines) + - [Create machine](#create-machine) + - [Destroy machine](#destroy-machine) + - [Get machine](#get-machine) + - [Update machine](#update-machine) + - [List artifacts](#list-artifacts) + - [Delete artifact](#delete-artifact) + - [Get artifact](#get-artifact) + - [List executions](#list-executions) + - [Create execution](#create-execution) + - [Delete execution](#delete-execution) + - [Get execution](#get-execution) + - [List execution events](#list-execution-events) + - [Get execution output](#get-execution-output) + - [List previews](#list-previews) + - [Create preview](#create-preview) + - [Delete preview](#delete-preview) + - [Get preview](#get-preview) + - [Sleep a running machine](#sleep-a-running-machine) + - [List SSH sessions](#list-ssh-sessions) + - [Create SSH session](#create-ssh-session) + - [Delete SSH session](#delete-ssh-session) + - [Get SSH session](#get-ssh-session) + - [Watch machine lifecycle status](#watch-machine-lifecycle-status) + - [List terminals](#list-terminals) + - [Create terminal](#create-terminal) + - [Delete terminal](#delete-terminal) + - [Get terminal](#get-terminal) + - [Connect to terminal WebSocket stream](#connect-to-terminal-websocket-stream) + - [Wake a sleeping machine](#wake-a-sleeping-machine) +- [`Usage`](#usage) + - [Get usage summary](#get-usage-summary) + - [`Usage Machines`](#usage-machines) + - [List machine compute usage breakdown](#list-machine-compute-usage-breakdown) + - [List machine storage usage breakdown](#list-machine-storage-usage-breakdown) + +## `MachineLifecycle` + +### List machines + +```sh +dedalus machine-lifecycle list --bearer "$BEARER" +``` + +### Create machine + +```sh +dedalus machine-lifecycle create --bearer "$BEARER" --memory-mib '1' --storage-gib '1' --vcpu '1' +``` + +### Destroy machine + +```sh +dedalus machine-lifecycle delete --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Get machine + +```sh +dedalus machine-lifecycle retrieve --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Update machine + +```sh +dedalus machine-lifecycle patch --bearer "$BEARER" --machine-id 'machine_id' +``` + +### List artifacts + +```sh +dedalus machine-lifecycle list-artifacts --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Delete artifact + +```sh +dedalus machine-lifecycle delete-artifact --bearer "$BEARER" --machine-id 'machine_id' --artifact-id 'artifact_id' +``` + +### Get artifact + +```sh +dedalus machine-lifecycle retrieve-artifact --bearer "$BEARER" --machine-id 'machine_id' --artifact-id 'artifact_id' +``` + +### List executions + +```sh +dedalus machine-lifecycle list-executions --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Create execution + +```sh +dedalus machine-lifecycle create-execution --bearer "$BEARER" --machine-id 'machine_id' --command '["command"]' +``` + +### Delete execution + +```sh +dedalus machine-lifecycle delete-execution --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' +``` + +### Get execution + +```sh +dedalus machine-lifecycle retrieve-execution --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' +``` + +### List execution events + +```sh +dedalus machine-lifecycle list-execution-events --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' +``` + +### Get execution output + +```sh +dedalus machine-lifecycle list-execution-output --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' +``` + +### List previews + +```sh +dedalus machine-lifecycle list-previews --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Create preview + +```sh +dedalus machine-lifecycle create-preview --bearer "$BEARER" --machine-id 'machine_id' --port '1' +``` + +### Delete preview + +```sh +dedalus machine-lifecycle delete-preview --bearer "$BEARER" --machine-id 'machine_id' --preview-id 'preview_id' +``` + +### Get preview + +```sh +dedalus machine-lifecycle retrieve-preview --bearer "$BEARER" --machine-id 'machine_id' --preview-id 'preview_id' +``` + +### Sleep a running machine + +```sh +dedalus machine-lifecycle sleep --bearer "$BEARER" --machine-id 'machine_id' +``` + +### List SSH sessions + +```sh +dedalus machine-lifecycle list-ssh-sessions --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Create SSH session + +```sh +dedalus machine-lifecycle create-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --public-key 'public_key' +``` + +### Delete SSH session + +```sh +dedalus machine-lifecycle delete-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --session-id 'session_id' +``` + +### Get SSH session + +```sh +dedalus machine-lifecycle retrieve-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --session-id 'session_id' +``` + +### Watch machine lifecycle status + +Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state. + +```sh +dedalus machine-lifecycle watch-status --bearer "$BEARER" --machine-id 'machine_id' --max-items 10 +``` + +### List terminals + +```sh +dedalus machine-lifecycle list-terminals --bearer "$BEARER" --machine-id 'machine_id' +``` + +### Create terminal + +```sh +dedalus machine-lifecycle create-terminal --bearer "$BEARER" --machine-id 'machine_id' --height '1' --width '1' +``` + +### Delete terminal + +```sh +dedalus machine-lifecycle delete-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' +``` + +### Get terminal + +```sh +dedalus machine-lifecycle retrieve-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' +``` + +### Connect to terminal WebSocket stream + +Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields. + +```sh +dedalus machine-lifecycle connect-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' --max-items 10 +``` + +### Wake a sleeping machine + +```sh +dedalus machine-lifecycle wake --bearer "$BEARER" --machine-id 'machine_id' +``` + +## `Usage` + +### Get usage summary + +```sh +dedalus usage list --bearer "$BEARER" +``` + +### `Usage Machines` + +#### List machine compute usage breakdown + +```sh +dedalus usage:machines list-compute-usage --bearer "$BEARER" +``` + +#### List machine storage usage breakdown + +```sh +dedalus usage:machines list-storage-usage --bearer "$BEARER" +``` diff --git a/cmd/dedalus/main.go b/cmd/dedalus/main.go deleted file mode 100644 index 2ccba35..0000000 --- a/cmd/dedalus/main.go +++ /dev/null @@ -1,83 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package main - -import ( - "context" - "errors" - "fmt" - "net/http" - "os" - "slices" - - "github.com/dedalus-labs/dedalus-cli/pkg/cmd" - "github.com/dedalus-labs/dedalus-go" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -func main() { - app := cmd.Command - ctx := context.Background() - - if slices.Contains(os.Args, "__complete") { - prepareForAutocomplete(app) - } - - if baseURL, ok := os.LookupEnv("DEDALUS_BASE_URL"); ok { - if err := cmd.ValidateBaseURL(baseURL, "DEDALUS_BASE_URL"); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - os.Exit(1) - } - } - - updated, err := cmd.MaybeRunStartupUpdate(ctx, os.Args, os.Stdin, os.Stdout, os.Stderr) - if err == nil && updated { - return - } - - if err == nil { - err = app.Run(ctx, os.Args) - } - if err != nil { - exitCode := 1 - - // Check if error has a custom exit code - if exitErr, ok := err.(cli.ExitCoder); ok { - exitCode = exitErr.ExitCode() - } - - var apierr *dedalus.Error - if errors.As(err, &apierr) { - fmt.Fprintf(os.Stderr, "%s %q: %d %s\n", apierr.Request.Method, apierr.Request.URL, apierr.Response.StatusCode, http.StatusText(apierr.Response.StatusCode)) - format := app.String("format-error") - json := gjson.Parse(apierr.RawJSON()) - show_err := cmd.ShowJSON(json, cmd.ShowJSONOpts{ - ExplicitFormat: app.IsSet("format-error"), - Format: format, - Title: "Error", - Transform: app.String("transform-error"), - }) - if show_err != nil { - // Just print the original error: - fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - } - } else { - if cmd.CommandErrorBuffer.Len() > 0 { - os.Stderr.Write(cmd.CommandErrorBuffer.Bytes()) - } else { - fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - } - } - os.Exit(exitCode) - } -} - -func prepareForAutocomplete(cmd *cli.Command) { - // urfave/cli does not handle flag completions and will print an error if we inspect a command with invalid flags. - // This skips that sort of validation - cmd.SkipFlagParsing = true - for _, child := range cmd.Commands { - prepareForAutocomplete(child) - } -} diff --git a/examples/.gitkeep b/examples/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/go.mod b/go.mod deleted file mode 100644 index eb005e2..0000000 --- a/go.mod +++ /dev/null @@ -1,48 +0,0 @@ -module github.com/dedalus-labs/dedalus-cli - -go 1.25.0 - -require ( - github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.6 - github.com/charmbracelet/lipgloss v1.1.0 - github.com/charmbracelet/x/term v0.2.1 - github.com/dedalus-labs/dedalus-go v0.4.0 - github.com/goccy/go-yaml v1.18.0 - github.com/itchyny/json2yaml v0.1.4 - github.com/muesli/reflow v0.3.0 - github.com/stretchr/testify v1.10.0 - github.com/tidwall/gjson v1.18.0 - github.com/tidwall/pretty v1.2.1 - github.com/urfave/cli-docs/v3 v3.0.0-alpha6 - github.com/urfave/cli/v3 v3.3.2 - golang.org/x/crypto v0.49.0 - golang.org/x/sys v0.42.0 -) - -require ( - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/ansi v0.9.3 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.35.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 1beb577..0000000 --- a/go.sum +++ /dev/null @@ -1,95 +0,0 @@ -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= -github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= -github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= -github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= -github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dedalus-labs/dedalus-go v0.4.0 h1:4rrMfcXH48Y8d+mfIheZ8HnlqnpeRrB0iZnkZWWH2lc= -github.com/dedalus-labs/dedalus-go v0.4.0/go.mod h1:tcwRinHcyjTtLhXOiHaCPEHmm9tPwsAvagi5qiXaxDs= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/itchyny/json2yaml v0.1.4 h1:/pErVOXGG5iTyXHi/QKR4y3uzhLjGTEmmJIy97YT+k8= -github.com/itchyny/json2yaml v0.1.4/go.mod h1:6iudhBZdarpjLFRNj+clWLAkGft+9uCcjAZYXUH9eGI= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI= -github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= -github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o= -github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go deleted file mode 100644 index 857fe14..0000000 --- a/internal/apiform/encoder.go +++ /dev/null @@ -1,236 +0,0 @@ -package apiform - -import ( - "fmt" - "io" - "mime/multipart" - "net/textproto" - "path" - "reflect" - "sort" - "strconv" - "strings" -) - -// Marshal encodes a value as multipart form data using default settings -func Marshal(value any, writer *multipart.Writer) error { - e := &encoder{ - format: FormatRepeat, - } - return e.marshal(value, writer) -} - -// MarshalWithSettings encodes a value with custom array format -func MarshalWithSettings(value any, writer *multipart.Writer, arrayFormat FormFormat) error { - e := &encoder{ - format: arrayFormat, - } - return e.marshal(value, writer) -} - -type encoder struct { - format FormFormat -} - -func (e *encoder) marshal(value any, writer *multipart.Writer) error { - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil - } - return e.encodeValue("", val, writer) -} - -func (e *encoder) encodeValue(key string, val reflect.Value, writer *multipart.Writer) error { - if !val.IsValid() { - return writer.WriteField(key, "") - } - - t := val.Type() - - if t.Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) { - return e.encodeReader(key, val, writer) - } - - switch t.Kind() { - case reflect.Pointer: - if val.IsNil() || !val.IsValid() { - return writer.WriteField(key, "") - } - return e.encodeValue(key, val.Elem(), writer) - - case reflect.Slice, reflect.Array: - return e.encodeArray(key, val, writer) - - case reflect.Map: - return e.encodeMap(key, val, writer) - - case reflect.Interface: - if val.IsNil() { - return writer.WriteField(key, "") - } - return e.encodeValue(key, val.Elem(), writer) - - case reflect.String: - return writer.WriteField(key, val.String()) - - case reflect.Bool: - if val.Bool() { - return writer.WriteField(key, "true") - } - return writer.WriteField(key, "false") - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return writer.WriteField(key, strconv.FormatInt(val.Int(), 10)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return writer.WriteField(key, strconv.FormatUint(val.Uint(), 10)) - - case reflect.Float32: - return writer.WriteField(key, strconv.FormatFloat(val.Float(), 'f', -1, 32)) - - case reflect.Float64: - return writer.WriteField(key, strconv.FormatFloat(val.Float(), 'f', -1, 64)) - - default: - return fmt.Errorf("unknown type: %s", t.String()) - } -} - -func (e *encoder) encodeArray(key string, val reflect.Value, writer *multipart.Writer) error { - if e.format == FormatComma { - var values []string - for i := 0; i < val.Len(); i++ { - item := val.Index(i) - if (item.Kind() == reflect.Pointer || item.Kind() == reflect.Interface) && item.IsNil() { - // Null values are sent as an empty string - values = append(values, "") - continue - } - // If item is an interface, reduce it to the concrete type - if item.Kind() == reflect.Interface { - item = item.Elem() - } - var strValue string - switch item.Kind() { - case reflect.String: - strValue = item.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - strValue = strconv.FormatInt(item.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - strValue = strconv.FormatUint(item.Uint(), 10) - case reflect.Float32, reflect.Float64: - strValue = strconv.FormatFloat(item.Float(), 'f', -1, 64) - case reflect.Bool: - strValue = strconv.FormatBool(item.Bool()) - default: - return fmt.Errorf("comma format not supported for complex array elements") - } - values = append(values, strValue) - } - return writer.WriteField(key, strings.Join(values, ",")) - } - - for i := 0; i < val.Len(); i++ { - var formattedKey string - switch e.format { - case FormatRepeat: - formattedKey = key - case FormatBrackets: - formattedKey = key + "[]" - case FormatIndicesDots: - if key == "" { - formattedKey = strconv.Itoa(i) - } else { - formattedKey = key + "." + strconv.Itoa(i) - } - case FormatIndicesBrackets: - if key == "" { - formattedKey = strconv.Itoa(i) - } else { - formattedKey = key + "[" + strconv.Itoa(i) + "]" - } - default: - return fmt.Errorf("apiform: unsupported array format") - } - - if err := e.encodeValue(formattedKey, val.Index(i), writer); err != nil { - return err - } - } - return nil -} - -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} - -func (e *encoder) encodeReader(key string, val reflect.Value, writer *multipart.Writer) error { - reader, ok := val.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) - if !ok { - return nil - } - - // Set defaults - filename := "anonymous_file" - contentType := "application/octet-stream" - - // Get filename if available - if named, ok := reader.(interface{ Filename() string }); ok { - filename = named.Filename() - } else if named, ok := reader.(interface{ Name() string }); ok { - filename = path.Base(named.Name()) - } - - // Get content type if available - if typed, ok := reader.(interface{ ContentType() string }); ok { - contentType = typed.ContentType() - } - - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, - escapeQuotes(key), escapeQuotes(filename))) - h.Set("Content-Type", contentType) - - filewriter, err := writer.CreatePart(h) - if err != nil { - return err - } - _, err = io.Copy(filewriter, reader) - return err -} - -func (e *encoder) encodeMap(key string, val reflect.Value, writer *multipart.Writer) error { - type mapPair struct { - key string - value reflect.Value - } - - if key != "" { - key = key + "." - } - - // Collect and sort map entries for deterministic output - pairs := []mapPair{} - iter := val.MapRange() - for iter.Next() { - if iter.Key().Type().Kind() != reflect.String { - return fmt.Errorf("cannot encode a map with a non string key") - } - pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()}) - } - - sort.Slice(pairs, func(i, j int) bool { - return pairs[i].key < pairs[j].key - }) - - // Process sorted pairs - for _, p := range pairs { - if err := e.encodeValue(key+p.key, p.value, writer); err != nil { - return err - } - } - - return nil -} diff --git a/internal/apiform/form.go b/internal/apiform/form.go deleted file mode 100644 index 024de27..0000000 --- a/internal/apiform/form.go +++ /dev/null @@ -1,20 +0,0 @@ -package apiform - -type Marshaler interface { - MarshalMultipart() ([]byte, string, error) -} - -type FormFormat int - -const ( - // FormatRepeat represents arrays as repeated keys with the same value - FormatRepeat FormFormat = iota - // Comma-separated values 1,2,3 - FormatComma - // FormatBrackets uses the key[] notation for arrays - FormatBrackets - // FormatIndicesDots uses key.0, key.1, etc. notation - FormatIndicesDots - // FormatIndicesBrackets uses key[0], key[1], etc. notation - FormatIndicesBrackets -) diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go deleted file mode 100644 index f68cfd1..0000000 --- a/internal/apiform/form_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package apiform - -import ( - "bytes" - "mime/multipart" - "testing" -) - -// Define test cases -var tests = map[string]struct { - value any - format FormFormat - expected string -}{ - "nil": { - value: nil, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n--xxx--\r\n", - }, - "string": { - value: "hello", - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nhello\r\n--xxx--\r\n", - }, - "int": { - value: 42, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n42\r\n--xxx--\r\n", - }, - "float": { - value: 3.14, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n3.14\r\n--xxx--\r\n", - }, - "bool": { - value: true, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\ntrue\r\n--xxx--\r\n", - }, - "empty slice": { - value: []string{}, - expected: "\r\n--xxx--\r\n", - }, - "nil slice": { - value: []string(nil), - expected: "\r\n--xxx--\r\n", - }, - "slice with dot indices": { - value: []string{"a", "b", "c"}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.0\"\r\n\r\na\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.1\"\r\n\r\nb\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.2\"\r\n\r\nc\r\n--xxx--\r\n", - }, - "slice with bracket indices": { - value: []int{10, 20, 30}, - format: FormatIndicesBrackets, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo[0]\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo[1]\"\r\n\r\n20\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo[2]\"\r\n\r\n30\r\n--xxx--\r\n", - }, - "slice with repeat": { - value: []int{10, 20, 30}, - format: FormatRepeat, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n20\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n30\r\n--xxx--\r\n", - }, - "slice with commas": { - value: []int{10, 20, 30}, - format: FormatComma, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n10,20,30\r\n--xxx--\r\n", - }, - "empty map": { - value: map[string]any{}, - expected: "\r\n--xxx--\r\n", - }, - "nil map": { - value: map[string]any(nil), - expected: "\r\n--xxx--\r\n", - }, - "map": { - value: map[string]any{"key1": "value1", "key2": "value2"}, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.key1\"\r\n\r\nvalue1\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.key2\"\r\n\r\nvalue2\r\n--xxx--\r\n", - }, - "nested_map": { - value: map[string]any{"outer": map[string]int{"inner1": 10, "inner2": 20}}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.outer.inner1\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.outer.inner2\"\r\n\r\n20\r\n--xxx--\r\n", - }, - "mixed_map": { - value: map[string]any{"name": "John", "ages": []int{25, 30, 35}}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.0\"\r\n\r\n25\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.1\"\r\n\r\n30\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.2\"\r\n\r\n35\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.name\"\r\n\r\nJohn\r\n--xxx--\r\n", - }, -} - -func TestEncode(t *testing.T) { - t.Parallel() - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - buf := bytes.NewBuffer(nil) - writer := multipart.NewWriter(buf) - writer.SetBoundary("xxx") - - form := map[string]any{"foo": test.value} - err := MarshalWithSettings(form, writer, test.format) - if err != nil { - t.Errorf("serialization of %v failed with error %v", test.value, err) - } - err = writer.Close() - if err != nil { - t.Errorf("serialization of %v failed with error %v", test.value, err) - } - result := buf.String() - if result != test.expected { - t.Errorf("expected %+#v to serialize to:\n\t%q\nbut got:\n\t%q", test.value, test.expected, result) - } - }) - } -} diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go deleted file mode 100644 index 0d09dee..0000000 --- a/internal/apiquery/encoder.go +++ /dev/null @@ -1,166 +0,0 @@ -package apiquery - -import ( - "fmt" - "reflect" - "strconv" - "strings" -) - -type encoder struct { - settings QuerySettings -} - -type Pair struct { - key string - value string -} - -func (e *encoder) Encode(key string, value reflect.Value) ([]Pair, error) { - t := value.Type() - switch t.Kind() { - case reflect.Pointer: - if value.IsNil() || !value.IsValid() { - return []Pair{{key, ""}}, nil - } - return e.Encode(key, value.Elem()) - - case reflect.Array, reflect.Slice: - return e.encodeArray(key, value) - - case reflect.Map: - return e.encodeMap(key, value) - - case reflect.Interface: - if !value.Elem().IsValid() { - return []Pair{{key, ""}}, nil - } - return e.Encode(key, value.Elem()) - - default: - return e.encodePrimitive(key, value) - } -} - -func (e *encoder) encodeMap(key string, value reflect.Value) ([]Pair, error) { - var pairs []Pair - iter := value.MapRange() - for iter.Next() { - subkey := iter.Key().String() - keyPath := subkey - if len(key) > 0 { - if e.settings.NestedFormat == NestedQueryFormatDots { - keyPath = fmt.Sprintf("%s.%s", key, subkey) - } else { - keyPath = fmt.Sprintf("%s[%s]", key, subkey) - } - } - - subpairs, err := e.Encode(keyPath, iter.Value()) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil -} - -func (e *encoder) encodeArray(key string, value reflect.Value) ([]Pair, error) { - switch e.settings.ArrayFormat { - case ArrayQueryFormatComma: - elements := []string{} - for i := 0; i < value.Len(); i++ { - innerPairs, err := e.Encode("", value.Index(i)) - if err != nil { - return nil, err - } - for _, pair := range innerPairs { - elements = append(elements, pair.value) - } - } - return []Pair{{key, strings.Join(elements, ",")}}, nil - - case ArrayQueryFormatRepeat: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(key, value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - case ArrayQueryFormatIndices: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(fmt.Sprintf("%s[%d]", key, i), value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - case ArrayQueryFormatBrackets: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(key+"[]", value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - default: - panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat)) - } -} - -func (e *encoder) encodePrimitive(key string, value reflect.Value) ([]Pair, error) { - switch value.Kind() { - case reflect.Pointer: - if !value.IsValid() || value.IsNil() { - return nil, nil - } - return e.encodePrimitive(key, value.Elem()) - - case reflect.String: - return []Pair{{key, value.String()}}, nil - - case reflect.Bool: - if value.Bool() { - return []Pair{{key, "true"}}, nil - } - return []Pair{{key, "false"}}, nil - - case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - return []Pair{{key, strconv.FormatInt(value.Int(), 10)}}, nil - - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return []Pair{{key, strconv.FormatUint(value.Uint(), 10)}}, nil - - case reflect.Float32, reflect.Float64: - return []Pair{{key, strconv.FormatFloat(value.Float(), 'f', -1, 64)}}, nil - - default: - return nil, nil - } -} - -func (e *encoder) encodeField(key string, value reflect.Value) ([]Pair, error) { - present := value.FieldByName("Present") - if !present.Bool() { - return nil, nil - } - null := value.FieldByName("Null") - if null.Bool() { - return nil, fmt.Errorf("apiquery: field cannot be null") - } - raw := value.FieldByName("Raw") - if !raw.IsNil() { - return e.Encode(key, raw) - } - return e.Encode(key, value.FieldByName("Value")) -} diff --git a/internal/apiquery/query.go b/internal/apiquery/query.go deleted file mode 100644 index fd07a2f..0000000 --- a/internal/apiquery/query.go +++ /dev/null @@ -1,53 +0,0 @@ -package apiquery - -import ( - "net/url" - "reflect" -) - -func MarshalWithSettings(value any, settings QuerySettings) (url.Values, error) { - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil, nil - } - - e := encoder{settings} - pairs, err := e.Encode("", val) - if err != nil { - return nil, err - } - - kv := url.Values{} - for _, pair := range pairs { - kv.Add(pair.key, pair.value) - } - return kv, nil -} -func Marshal(value any) (url.Values, error) { - return MarshalWithSettings(value, QuerySettings{}) -} - -type Queryer interface { - URLQuery() (url.Values, error) -} - -type NestedQueryFormat int - -const ( - NestedQueryFormatBrackets NestedQueryFormat = iota - NestedQueryFormatDots -) - -type ArrayQueryFormat int - -const ( - ArrayQueryFormatComma ArrayQueryFormat = iota - ArrayQueryFormatRepeat - ArrayQueryFormatIndices - ArrayQueryFormatBrackets -) - -type QuerySettings struct { - NestedFormat NestedQueryFormat - ArrayFormat ArrayQueryFormat -} diff --git a/internal/apiquery/query_test.go b/internal/apiquery/query_test.go deleted file mode 100644 index 3791ec9..0000000 --- a/internal/apiquery/query_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package apiquery - -import ( - "net/url" - "testing" -) - -func TestEncode(t *testing.T) { - t.Parallel() - - tests := map[string]struct { - val any - settings QuerySettings - enc string - }{ - "null": { - val: nil, - enc: "query=", - }, - "string": { - val: "hello world", - enc: "query=hello world", - }, - "int": { - val: 42, - enc: "query=42", - }, - "float": { - val: 3.14, - enc: "query=3.14", - }, - "bool": { - val: true, - enc: "query=true", - }, - "empty_slice": { - val: []any{}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=", - }, - "nil_slice": { - val: []any(nil), - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=", - }, - "slice_of_ints": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=10,20,30", - }, - "slice_of_ints_repeat": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, - enc: "query=10&query=20&query=30", - }, - "slice_of_ints_indices": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatIndices}, - enc: "query[0]=10&query[1]=20&query[2]=30", - }, - "slice_of_ints_brackets": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, - enc: "query[]=10&query[]=20&query[]=30", - }, - "slice_of_strings": { - val: []any{"a", "b", "c"}, - settings: QuerySettings{}, - enc: "query=a,b,c", - }, - "empty_map": { - val: map[string]any{}, - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "", - }, - "nil_map": { - val: map[string]any(nil), - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "", - }, - "map_string_to_int_brackets": { - val: map[string]any{"one": 1, "two": 2}, - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "query[one]=1&query[two]=2", - }, - "map_string_to_int_dots": { - val: map[string]any{"one": 1, "two": 2}, - settings: QuerySettings{NestedFormat: NestedQueryFormatDots}, - enc: "query.one=1&query.two=2", - }, - "map_string_to_slice": { - val: map[string][]any{"nums": {10, 20, 30}}, - settings: QuerySettings{}, - enc: "query[nums]=10,20,30", - }, - "map_string_to_slice_repeat_dots": { - val: map[string][]any{"nums": {10, 20, 30}}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatRepeat, NestedFormat: NestedQueryFormatDots}, - enc: "query.nums=10&query.nums=20&query.nums=30", - }, - "map_with_empties": { - val: map[string]any{ - "empty-array": []any{}, - "nil-array": []any(nil), - "null": nil, - }, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma, NestedFormat: NestedQueryFormatDots}, - enc: "query.empty-array=&query.nil-array=&query.null=", - }, - "nested_map": { - val: map[string]map[string]any{"outer": {"inner": 42}}, - settings: QuerySettings{}, - enc: "query[outer][inner]=42", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - query := map[string]any{"query": test.val} - values, err := MarshalWithSettings(query, test.settings) - if err != nil { - t.Fatalf("failed to marshal url %s", err) - } - str, _ := url.QueryUnescape(values.Encode()) - if str != test.enc { - t.Fatalf("expected %+#v to serialize to:\n\t%q\nbut got:\n\t%q", test.val, test.enc, str) - } - }) - } -} diff --git a/internal/autocomplete/autocomplete.go b/internal/autocomplete/autocomplete.go deleted file mode 100644 index 97fe1a8..0000000 --- a/internal/autocomplete/autocomplete.go +++ /dev/null @@ -1,361 +0,0 @@ -package autocomplete - -import ( - "context" - "embed" - "fmt" - "os" - "slices" - "strings" - - "github.com/urfave/cli/v3" -) - -type CompletionStyle string - -const ( - CompletionStyleZsh CompletionStyle = "zsh" - CompletionStyleBash CompletionStyle = "bash" - CompletionStylePowershell CompletionStyle = "pwsh" - CompletionStyleFish CompletionStyle = "fish" -) - -type renderCompletion func(cmd *cli.Command, appName string) (string, error) - -var ( - //go:embed shellscripts - autoCompleteFS embed.FS - - shellCompletions = map[CompletionStyle]renderCompletion{ - "bash": func(c *cli.Command, appName string) (string, error) { - b, err := autoCompleteFS.ReadFile("shellscripts/bash_autocomplete.bash") - return strings.ReplaceAll(string(b), "__APPNAME__", appName), err - }, - "fish": func(c *cli.Command, appName string) (string, error) { - b, err := autoCompleteFS.ReadFile("shellscripts/fish_autocomplete.fish") - return strings.ReplaceAll(string(b), "__APPNAME__", appName), err - }, - "pwsh": func(c *cli.Command, appName string) (string, error) { - b, err := autoCompleteFS.ReadFile("shellscripts/pwsh_autocomplete.ps1") - return strings.ReplaceAll(string(b), "__APPNAME__", appName), err - }, - "zsh": func(c *cli.Command, appName string) (string, error) { - b, err := autoCompleteFS.ReadFile("shellscripts/zsh_autocomplete.zsh") - return strings.ReplaceAll(string(b), "__APPNAME__", appName), err - }, - } -) - -func OutputCompletionScript(ctx context.Context, cmd *cli.Command) error { - shells := make([]CompletionStyle, 0, len(shellCompletions)) - for k := range shellCompletions { - shells = append(shells, k) - } - - if cmd.Args().Len() == 0 { - return cli.Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1) - } - s := CompletionStyle(cmd.Args().First()) - - renderCompletion, ok := shellCompletions[s] - if !ok { - return cli.Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1) - } - - completionScript, err := renderCompletion(cmd, cmd.Root().Name) - if err != nil { - return cli.Exit(err, 1) - } - - _, err = cmd.Writer.Write([]byte(completionScript)) - if err != nil { - return cli.Exit(err, 1) - } - - return nil -} - -type ShellCompletion struct { - Name string - Usage string -} - -func NewShellCompletion(name string, usage string) ShellCompletion { - return ShellCompletion{Name: name, Usage: usage} -} - -type ShellCompletionBehavior int - -const ( - ShellCompletionBehaviorDefault ShellCompletionBehavior = iota - ShellCompletionBehaviorFile = 10 - ShellCompletionBehaviorNoComplete -) - -type CompletionResult struct { - Completions []ShellCompletion - Behavior ShellCompletionBehavior -} - -func isFlag(arg string) bool { - return strings.HasPrefix(arg, "-") -} - -func findFlag(cmd *cli.Command, arg string) *cli.Flag { - name := strings.TrimLeft(arg, "-") - for _, flag := range cmd.Flags { - if vf, ok := flag.(cli.VisibleFlag); ok && !vf.IsVisible() { - continue - } - - if slices.Contains(flag.Names(), name) { - return &flag - } - } - return nil -} - -func findChild(cmd *cli.Command, name string) *cli.Command { - for _, c := range cmd.Commands { - if !c.Hidden && c.Name == name { - return c - } - } - return nil -} - -type shellCompletionBuilder struct { - completionStyle CompletionStyle -} - -func (scb *shellCompletionBuilder) createFromCommand(input string, command *cli.Command, result []ShellCompletion) []ShellCompletion { - matchingNames := make([]string, 0, len(command.Names())) - - for _, name := range command.Names() { - if strings.HasPrefix(name, input) { - matchingNames = append(matchingNames, name) - } - } - - if scb.completionStyle == CompletionStyleBash { - index := strings.LastIndex(input, ":") + 1 - if index > 0 { - for _, name := range matchingNames { - result = append(result, NewShellCompletion(name[index:], command.Usage)) - } - return result - } - } - - for _, name := range matchingNames { - result = append(result, NewShellCompletion(name, command.Usage)) - } - return result -} - -func (scb *shellCompletionBuilder) createFromFlag(input string, flag *cli.Flag, result []ShellCompletion) []ShellCompletion { - matchingNames := make([]string, 0, len((*flag).Names())) - - for _, name := range (*flag).Names() { - withPrefix := "" - if len(name) == 1 { - withPrefix = "-" + name - } else { - withPrefix = "--" + name - } - - if strings.HasPrefix(withPrefix, input) { - matchingNames = append(matchingNames, withPrefix) - } - } - - usage := "" - if dgf, ok := (*flag).(cli.DocGenerationFlag); ok { - usage = dgf.GetUsage() - } - - for _, name := range matchingNames { - result = append(result, NewShellCompletion(name, usage)) - } - - return result -} - -func GetCompletions(completionStyle CompletionStyle, root *cli.Command, args []string) CompletionResult { - result := getAllPossibleCompletions(completionStyle, root, args) - - // If the user has not put in a colon, filter out colon commands - if len(args) > 0 && !strings.Contains(args[len(args)-1], ":") { - // Nothing with anything after a colon. Create a single entry for groups with the same colon subset - foundNames := make([]string, 0, len(result.Completions)) - filteredCompletions := make([]ShellCompletion, 0, len(result.Completions)) - - for _, completion := range result.Completions { - name := completion.Name - firstColonIndex := strings.Index(name, ":") - if firstColonIndex > -1 { - name = name[0:firstColonIndex] - completion.Name = name - completion.Usage = "" - } - - if !slices.Contains(foundNames, name) { - foundNames = append(foundNames, name) - filteredCompletions = append(filteredCompletions, completion) - } - } - - result.Completions = filteredCompletions - } - - return result -} - -func getAllPossibleCompletions(completionStyle CompletionStyle, root *cli.Command, args []string) CompletionResult { - builder := shellCompletionBuilder{completionStyle: completionStyle} - completions := make([]ShellCompletion, 0) - if len(args) == 0 { - for _, child := range root.Commands { - completions = builder.createFromCommand("", child, completions) - } - return CompletionResult{Completions: completions, Behavior: ShellCompletionBehaviorDefault} - } - - current := args[len(args)-1] - preceding := args[0 : len(args)-1] - cmd := root - i := 0 - for i < len(preceding) { - arg := preceding[i] - - if isFlag(arg) { - flag := findFlag(cmd, arg) - if flag == nil { - i++ - } else if docFlag, ok := (*flag).(cli.DocGenerationFlag); ok && docFlag.TakesValue() { - // All flags except for bool flags take values - i += 2 - } else { - i++ - } - } else { - child := findChild(cmd, arg) - if child != nil { - cmd = child - } - i++ - } - } - - // Check if the previous arg was a flag expecting a value - if len(preceding) > 0 { - prev := preceding[len(preceding)-1] - if isFlag(prev) { - flag := findFlag(cmd, prev) - if flag != nil { - if fb, ok := (*flag).(*cli.StringFlag); ok && fb.TakesFile { - return CompletionResult{Completions: completions, Behavior: ShellCompletionBehaviorFile} - } else if docFlag, ok := (*flag).(cli.DocGenerationFlag); ok && docFlag.TakesValue() { - return CompletionResult{Completions: completions, Behavior: ShellCompletionBehaviorNoComplete} - } - } - } - } - - // Completing a flag name - if isFlag(current) { - for _, flag := range cmd.Flags { - completions = builder.createFromFlag(current, &flag, completions) - } - } - - for _, child := range cmd.Commands { - if !child.Hidden { - completions = builder.createFromCommand(current, child, completions) - } - } - - return CompletionResult{ - Completions: completions, - Behavior: ShellCompletionBehaviorDefault, - } -} - -func ExecuteShellCompletion(ctx context.Context, cmd *cli.Command) error { - root := cmd.Root() - args := rebuildColonSeparatedArgs(root.Args().Slice()[1:]) - - var completionStyle CompletionStyle - if style, ok := os.LookupEnv("COMPLETION_STYLE"); ok { - switch style { - case "bash": - completionStyle = CompletionStyleBash - case "zsh": - completionStyle = CompletionStyleZsh - case "pwsh": - completionStyle = CompletionStylePowershell - case "fish": - completionStyle = CompletionStyleFish - default: - return cli.Exit("COMPLETION_STYLE must be set to 'bash', 'zsh', 'pwsh', or 'fish'", 1) - } - } else { - return cli.Exit("COMPLETION_STYLE must be set to 'bash', 'zsh', 'pwsh', 'fish'", 1) - } - - result := GetCompletions(completionStyle, root, args) - - for _, completion := range result.Completions { - name := completion.Name - if completionStyle == CompletionStyleZsh { - name = strings.ReplaceAll(name, ":", "\\:") - } - if completionStyle == CompletionStyleZsh && len(completion.Usage) > 0 { - _, _ = fmt.Fprintf(cmd.Writer, "%s:%s\n", name, completion.Usage) - } else if completionStyle == CompletionStyleFish && len(completion.Usage) > 0 { - _, _ = fmt.Fprintf(cmd.Writer, "%s\t%s\n", name, completion.Usage) - } else { - _, _ = fmt.Fprintf(cmd.Writer, "%s\n", name) - } - } - return cli.Exit("", int(result.Behavior)) -} - -// When CLI arguments are passed in, they are separated on word barriers. -// Most commonly this is whitespace but in some cases that may also be colons. -// We wish to allow arguments with colons. To handle this, we append/prepend colons to their neighboring -// arguments. -// -// Example: `rebuildColonSeparatedArgs(["a", "b", ":", "c", "d"])` => `["a", "b:c", "d"]` -func rebuildColonSeparatedArgs(args []string) []string { - if len(args) == 0 { - return args - } - - result := []string{} - i := 0 - - for i < len(args) { - current := args[i] - - // Keep joining while the next element is ":" or the current element ends with ":" - for i+1 < len(args) && (args[i+1] == ":" || strings.HasSuffix(current, ":")) { - if args[i+1] == ":" { - current += ":" - i++ - // Check if there's a following element after the ":" - if i+1 < len(args) && args[i+1] != ":" { - current += args[i+1] - i++ - } - } else { - break - } - } - - result = append(result, current) - i++ - } - - return result -} diff --git a/internal/autocomplete/autocomplete_test.go b/internal/autocomplete/autocomplete_test.go deleted file mode 100644 index 2338924..0000000 --- a/internal/autocomplete/autocomplete_test.go +++ /dev/null @@ -1,433 +0,0 @@ -package autocomplete - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v3" -) - -func TestGetCompletions_EmptyArgs(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "generate", Usage: "Generate SDK"}, - {Name: "test", Usage: "Run tests"}, - {Name: "build", Usage: "Build project"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 3) - assert.Contains(t, result.Completions, ShellCompletion{Name: "generate", Usage: "Generate SDK"}) - assert.Contains(t, result.Completions, ShellCompletion{Name: "test", Usage: "Run tests"}) - assert.Contains(t, result.Completions, ShellCompletion{Name: "build", Usage: "Build project"}) -} - -func TestGetCompletions_SubcommandPrefix(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "generate", Usage: "Generate SDK"}, - {Name: "test", Usage: "Run tests"}, - {Name: "build", Usage: "Build project"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"ge"}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 1) - assert.Equal(t, "generate", result.Completions[0].Name) - assert.Equal(t, "Generate SDK", result.Completions[0].Usage) -} - -func TestGetCompletions_HiddenCommand(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "visible", Usage: "Visible command"}, - {Name: "hidden", Usage: "Hidden command", Hidden: true}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{""}) - - assert.Len(t, result.Completions, 1) - assert.Equal(t, "visible", result.Completions[0].Name) -} - -func TestGetCompletions_NestedSubcommand(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "config", - Usage: "Configuration commands", - Commands: []*cli.Command{ - {Name: "get", Usage: "Get config value"}, - {Name: "set", Usage: "Set config value"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"config", "s"}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 1) - assert.Equal(t, "set", result.Completions[0].Name) - assert.Equal(t, "Set config value", result.Completions[0].Usage) -} - -func TestGetCompletions_FlagCompletion(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "Output directory"}, - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "Verbose output"}, - &cli.StringFlag{Name: "format", Usage: "Output format"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "--o"}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 1) - assert.Equal(t, "--output", result.Completions[0].Name) - assert.Equal(t, "Output directory", result.Completions[0].Usage) -} - -func TestGetCompletions_ShortFlagCompletion(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "Output directory"}, - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "Verbose output"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "-v"}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 1) - assert.Equal(t, "-v", result.Completions[0].Name) -} - -func TestGetCompletions_FileFlagBehavior(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "Config file", TakesFile: true}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "--config", ""}) - - assert.EqualValues(t, ShellCompletionBehaviorFile, result.Behavior) - assert.Empty(t, result.Completions) -} - -func TestGetCompletions_NonBoolFlagValue(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "format", Usage: "Output format"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "--format", ""}) - - assert.EqualValues(t, ShellCompletionBehaviorNoComplete, result.Behavior) - assert.Empty(t, result.Completions) -} - -func TestGetCompletions_BoolFlagDoesNotBlockCompletion(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "Verbose output"}, - }, - Commands: []*cli.Command{ - {Name: "typescript", Usage: "Generate TypeScript SDK"}, - {Name: "python", Usage: "Generate Python SDK"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "--verbose", "ty"}) - - assert.Equal(t, ShellCompletionBehaviorDefault, result.Behavior) - assert.Len(t, result.Completions, 1) - assert.Equal(t, "typescript", result.Completions[0].Name) -} - -func TestGetCompletions_ColonCommands_NoColonTyped(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - {Name: "config:list", Usage: "List config values"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"co"}) - - // Should collapse to single "config" entry without usage - assert.Len(t, result.Completions, 1) - assert.Equal(t, "config", result.Completions[0].Name) - assert.Equal(t, "", result.Completions[0].Usage) -} - -func TestGetCompletions_ColonCommands_ColonTyped_Bash(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - {Name: "config:list", Usage: "List config values"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"config:"}) - - // For bash, should show suffixes only - assert.Len(t, result.Completions, 3) - names := []string{result.Completions[0].Name, result.Completions[1].Name, result.Completions[2].Name} - assert.Contains(t, names, "get") - assert.Contains(t, names, "set") - assert.Contains(t, names, "list") -} - -func TestGetCompletions_ColonCommands_ColonTyped_Zsh(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - {Name: "config:list", Usage: "List config values"}, - }, - } - - result := GetCompletions(CompletionStyleZsh, root, []string{"config:"}) - - // For zsh, should show full names - assert.Len(t, result.Completions, 3) - names := []string{result.Completions[0].Name, result.Completions[1].Name, result.Completions[2].Name} - assert.Contains(t, names, "config:get") - assert.Contains(t, names, "config:set") - assert.Contains(t, names, "config:list") -} - -func TestGetCompletions_BashStyleColonCompletion(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"config:g"}) - - // For bash, should return suffix from after the colon in the input - // Input "config:g" has colon at index 6, so we take name[7:] from matched commands - assert.Len(t, result.Completions, 1) - assert.Equal(t, "get", result.Completions[0].Name) - assert.Equal(t, "Get config value", result.Completions[0].Usage) -} - -func TestGetCompletions_BashStyleColonCompletion_NoMatch(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"other:g"}) - - // No matches - assert.Len(t, result.Completions, 0) -} - -func TestGetCompletions_ZshStyleColonCompletion(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - }, - } - - result := GetCompletions(CompletionStyleZsh, root, []string{"config:g"}) - - // For zsh, should return full name - assert.Len(t, result.Completions, 1) - assert.Equal(t, "config:get", result.Completions[0].Name) - assert.Equal(t, "Get config value", result.Completions[0].Usage) -} - -func TestGetCompletions_MixedColonAndRegularCommands(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "generate", Usage: "Generate SDK"}, - {Name: "config:get", Usage: "Get config value"}, - {Name: "config:set", Usage: "Set config value"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{""}) - - // Should show "generate" and "config" (collapsed) - assert.Len(t, result.Completions, 2) - names := []string{result.Completions[0].Name, result.Completions[1].Name} - assert.Contains(t, names, "generate") - assert.Contains(t, names, "config") -} - -func TestGetCompletions_FlagWithBoolFlagSkipsValue(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}}, - &cli.StringFlag{Name: "output", Aliases: []string{"o"}}, - }, - Commands: []*cli.Command{ - {Name: "typescript", Usage: "TypeScript SDK"}, - }, - }, - }, - } - - // Bool flag should not consume the next arg as a value - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "-v", "ty"}) - - assert.Len(t, result.Completions, 1) - assert.Equal(t, "typescript", result.Completions[0].Name) -} - -func TestGetCompletions_MultipleFlagsBeforeSubcommand(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "config", Aliases: []string{"c"}}, - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}}, - }, - Commands: []*cli.Command{ - {Name: "typescript", Usage: "TypeScript SDK"}, - {Name: "python", Usage: "Python SDK"}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "-c", "config.yml", "-v", "py"}) - - assert.Len(t, result.Completions, 1) - assert.Equal(t, "python", result.Completions[0].Name) -} - -func TestGetCompletions_CommandAliases(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - {Name: "generate", Aliases: []string{"gen", "g"}, Usage: "Generate SDK"}, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"g"}) - - // Should match all aliases that start with "g" - assert.GreaterOrEqual(t, len(result.Completions), 2) // "generate" and "gen", possibly "g" too - names := []string{} - for _, c := range result.Completions { - names = append(names, c.Name) - } - assert.Contains(t, names, "generate") - assert.Contains(t, names, "gen") -} - -func TestGetCompletions_AllFlagsWhenNoPrefix(t *testing.T) { - t.Parallel() - - root := &cli.Command{ - Commands: []*cli.Command{ - { - Name: "generate", - Usage: "Generate SDK", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "output", Aliases: []string{"o"}}, - &cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}}, - &cli.StringFlag{Name: "format", Aliases: []string{"f"}}, - }, - }, - }, - } - - result := GetCompletions(CompletionStyleBash, root, []string{"generate", "-"}) - - // Should show all flag variations - assert.GreaterOrEqual(t, len(result.Completions), 6) // -o, --output, -v, --verbose, -f, --format -} diff --git a/internal/autocomplete/shellscripts/bash_autocomplete.bash b/internal/autocomplete/shellscripts/bash_autocomplete.bash deleted file mode 100755 index 8fb7b0b..0000000 --- a/internal/autocomplete/shellscripts/bash_autocomplete.bash +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -____APPNAME___bash_autocomplete() { - if [[ "${COMP_WORDS[0]}" != "source" ]]; then - local cur completions exit_code - local IFS=$'\n' - cur="${COMP_WORDS[COMP_CWORD]}" - - completions=$(COMPLETION_STYLE=bash "${COMP_WORDS[0]}" __complete -- "${COMP_WORDS[@]:1:$COMP_CWORD-1}" "$cur" 2>/dev/null) - exit_code=$? - - local last_token="$cur" - - # If the last token has been split apart by a ':', join it back together. - # Ex: 'a:b' will be represented in COMP_WORDS as 'a', ':', 'b' - if [[ $COMP_CWORD -ge 2 ]]; then - local prev2="${COMP_WORDS[COMP_CWORD - 2]}" - local prev1="${COMP_WORDS[COMP_CWORD - 1]}" - if [[ "$prev2" =~ ^@(file|data)$ && "$prev1" == ":" && "$cur" =~ ^// ]]; then - last_token="$prev2:$cur" - fi - fi - - # Check for custom file completion patterns - local prefix="" - local file_part="$cur" - local force_file_completion=false - if [[ "$last_token" =~ (.*)@(file://|data://)?(.*)$ ]]; then - local before_at="${BASH_REMATCH[1]}" - local protocol="${BASH_REMATCH[2]}" - file_part="${BASH_REMATCH[3]}" - - if [[ "$protocol" == "" ]]; then - prefix="$before_at@" - else - if [[ "$before_at" == "" ]]; then - prefix="//" - else - prefix="$before_at@$protocol" - fi - fi - - force_file_completion=true - fi - - if [[ "$force_file_completion" == true ]]; then - mapfile -t COMPREPLY < <(compgen -f -- "$file_part" | sed "s|^|$prefix|") - else - case $exit_code in - 10) mapfile -t COMPREPLY < <(compgen -f -- "$cur") ;; # file completion - 11) COMPREPLY=() ;; # no completion - 0) mapfile -t COMPREPLY <<<"$completions" ;; # use returned completions - esac - fi - return 0 - fi -} - -complete -F ____APPNAME___bash_autocomplete __APPNAME__ diff --git a/internal/autocomplete/shellscripts/fish_autocomplete.fish b/internal/autocomplete/shellscripts/fish_autocomplete.fish deleted file mode 100644 index b853057..0000000 --- a/internal/autocomplete/shellscripts/fish_autocomplete.fish +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env fish - -function ____APPNAME___fish_autocomplete - set -l tokens (commandline -xpc) - set -l current (commandline -ct) - - set -l cmd $tokens[1] - set -l args $tokens[2..-1] - - set -l completions (env COMPLETION_STYLE=fish $cmd __complete -- $args $current 2>>/tmp/fish-debug.log) - set -l exit_code $status - - # Check for custom file completion patterns - # Patterns can appear anywhere in the word (e.g., inside quotes: 'my file is @file://path') - set -l prefix "" - set -l file_part "$current" - set -l force_file_completion 0 - - if string match -gqr '^(?.*)@(?file://|data://)?(?.*)$' -- $current - if string match -qr '^[\'"]' -- $before - # Ensures we don't insert an extra quote when the user is building an argument in quotes - set before (string sub -s 2 -- $before) - end - - set prefix "$before@$protocol" - set force_file_completion 1 - end - - if test $force_file_completion -eq 1 - for path in (__fish_complete_path "$file_part") - echo $prefix$path - end - else - switch $exit_code - case 10 - # File completion - __fish_complete_path "$current" - case 11 - # No completion - return 0 - case 0 - # Use returned completions - for completion in $completions - echo $completion - end - end - end -end - -complete -c __APPNAME__ -f -a '(____APPNAME___fish_autocomplete)' - diff --git a/internal/autocomplete/shellscripts/pwsh_autocomplete.ps1 b/internal/autocomplete/shellscripts/pwsh_autocomplete.ps1 deleted file mode 100644 index 7cd6e62..0000000 --- a/internal/autocomplete/shellscripts/pwsh_autocomplete.ps1 +++ /dev/null @@ -1,97 +0,0 @@ -Register-ArgumentCompleter -Native -CommandName __APPNAME__ -ScriptBlock { - param($wordToComplete, $commandAst, $cursorPosition) - - $elements = $commandAst.CommandElements - $completionArgs = @() - - # Extract each of the arguments - for ($i = 0; $i -lt $elements.Count; $i++) { - $completionArgs += $elements[$i].Extent.Text - } - - # Add empty string if there's a trailing space (wordToComplete is empty but cursor is after space) - # Necessary for differentiating between getting completions for namespaced commands vs. subcommands - if ($wordToComplete.Length -eq 0 -and $elements.Count -gt 0) { - $completionArgs += "" - } - - $output = & { - $env:COMPLETION_STYLE = 'pwsh' - __APPNAME__ __complete @completionArgs 2>&1 - } - $exitCode = $LASTEXITCODE - - # Check for custom file completion patterns - # Patterns can appear anywhere in the word (e.g., inside quotes: 'my file is @file://path') - $prefix = "" - $filePart = $wordToComplete - $forceFileCompletion = $false - - # PowerShell includes quotes in $wordToComplete - strip them for pattern matching - # but preserve them in the prefix for the completion result - $wordContent = $wordToComplete - $leadingQuote = "" - if ($wordToComplete -match '^([''"])(.*)(\1)$') { - # Fully quoted: "content" or 'content' - $leadingQuote = $Matches[1] - $wordContent = $Matches[2] - } elseif ($wordToComplete -match '^([''"])(.*)$') { - # Opening quote only: "content or 'content - $leadingQuote = $Matches[1] - $wordContent = $Matches[2] - } - - if ($wordContent -match '^(.*)@(file://|data://)?(.*)$') { - $prefix = $leadingQuote + $Matches[1] + '@' + $Matches[2] - $filePart = $Matches[3] - $forceFileCompletion = $true - } - - if ($forceFileCompletion) { - # Handle empty filePart (e.g., "@" or "@file://") by listing current directory - $items = if ([string]::IsNullOrEmpty($filePart)) { - Get-ChildItem -ErrorAction SilentlyContinue - } else { - Get-ChildItem -Path "$filePart*" -ErrorAction SilentlyContinue - } - $items | ForEach-Object { - $completionText = if ($_.PSIsContainer) { $prefix + $_.Name + "/" } else { $prefix + $_.Name } - [System.Management.Automation.CompletionResult]::new( - $completionText, - $completionText, - 'ProviderItem', - $completionText - ) - } - } else { - switch ($exitCode) { - 10 { - # File completion behavior - $items = if ([string]::IsNullOrEmpty($wordToComplete)) { - Get-ChildItem -ErrorAction SilentlyContinue - } else { - Get-ChildItem -Path "$wordToComplete*" -ErrorAction SilentlyContinue - } - $items | ForEach-Object { - $completionText = if ($_.PSIsContainer) { $_.Name + "/" } else { $_.Name } - [System.Management.Automation.CompletionResult]::new( - $completionText, - $completionText, - 'ProviderItem', - $completionText - ) - } - } - 11 { - # No reasonable suggestions - [System.Management.Automation.CompletionResult]::new(' ', ' ', 'ParameterValue', ' ') - } - default { - # Default behavior - show command completions - $output | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) - } - } - } - } -} diff --git a/internal/autocomplete/shellscripts/zsh_autocomplete.zsh b/internal/autocomplete/shellscripts/zsh_autocomplete.zsh deleted file mode 100644 index d937171..0000000 --- a/internal/autocomplete/shellscripts/zsh_autocomplete.zsh +++ /dev/null @@ -1,56 +0,0 @@ -#compdef __APPNAME__ - -____APPNAME___zsh_autocomplete() { - - local -a opts - local temp - local exit_code - - temp=$(COMPLETION_STYLE=zsh "${words[1]}" __complete "${words[@]:1}") - exit_code=$? - - # Check for custom file completion patterns - # Patterns can appear anywhere in the word (e.g., inside quotes: 'my file is @file://path') - local cur="${words[CURRENT]}" - - if [[ "$cur" = *'@'* ]]; then - # Extract everything after the last @ - local after_last_at="${cur##*@}" - - if [[ $after_last_at =~ ^(file://|data://) ]]; then - compset -P "*$MATCH" - _files - else - compset -P '*@' - _files - fi - return - fi - - case $exit_code in - 10) - # File completion behavior - _files - ;; - 11) - # No completion behavior - return nothing - return 1 - ;; - 0) - # Default behavior - show command completions - opts=("${(@f)temp}") - _describe 'values' opts - ;; - 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/binaryparam/binary_param.go b/internal/binaryparam/binary_param.go deleted file mode 100644 index 40d4ecf..0000000 --- a/internal/binaryparam/binary_param.go +++ /dev/null @@ -1,30 +0,0 @@ -package binaryparam - -import ( - "io" - "os" -) - -const stdinGlyph = "-" - -// FileOrStdin opens the file at the given path for reading. If the path is "-", stdin is returned instead. -// -// It's the caller's responsibility to close the returned ReadCloser (usually with `defer`). -// -// Returns a boolean indicating whether stdin is being used. If true, no other components of the calling -// program should attempt to read from stdin for anything else. -func FileOrStdin(stdin io.ReadCloser, path string) (io.ReadCloser, bool, error) { - // When the special glyph "-" is used, read from stdin. Although probably less necessary, also support - // special Unix files that refer to stdin. - switch path { - case "", stdinGlyph, "/dev/fd/0", "/dev/stdin": - return stdin, true, nil - } - - readCloser, err := os.Open(path) - if err != nil { - return nil, false, err - } - - return readCloser, false, err -} diff --git a/internal/binaryparam/binary_param_test.go b/internal/binaryparam/binary_param_test.go deleted file mode 100644 index 7a66682..0000000 --- a/internal/binaryparam/binary_param_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package binaryparam - -import ( - "io" - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestFileOrStdin(t *testing.T) { - t.Parallel() - - const expectedContents = "test file contents" - - t.Run("WithFile", func(t *testing.T) { - tempFile := t.TempDir() + "/test_file.txt" - require.NoError(t, os.WriteFile(tempFile, []byte(expectedContents), 0600)) - - readCloser, stdinInUse, err := FileOrStdin(os.Stdin, tempFile) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, readCloser.Close()) }) - - actualContents, err := io.ReadAll(readCloser) - require.NoError(t, err) - require.Equal(t, expectedContents, string(actualContents)) - - require.False(t, stdinInUse) - }) - - stdinTests := []struct { - testName string - path string - }{ - {"TestEmptyString", ""}, - {"TestDash", "-"}, - {"TestDevStdin", "/dev/stdin"}, - {"TestDevFD0", "/dev/fd/0"}, - } - for _, test := range stdinTests { - t.Run(test.testName, func(t *testing.T) { - tempFile := t.TempDir() + "/test_file.txt" - require.NoError(t, os.WriteFile(tempFile, []byte(expectedContents), 0600)) - - stubStdin, err := os.Open(tempFile) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, stubStdin.Close()) }) - - readCloser, stdinInUse, err := FileOrStdin(stubStdin, test.path) - require.NoError(t, err) - - actualContents, err := io.ReadAll(readCloser) - require.NoError(t, err) - require.Equal(t, expectedContents, string(actualContents)) - - require.True(t, stdinInUse) - }) - } -} diff --git a/internal/debugmiddleware/debug_middleware.go b/internal/debugmiddleware/debug_middleware.go deleted file mode 100644 index 647f1de..0000000 --- a/internal/debugmiddleware/debug_middleware.go +++ /dev/null @@ -1,132 +0,0 @@ -package debugmiddleware - -import ( - "bytes" - "io" - "log" - "net/http" - "net/http/httputil" - "reflect" - "strings" -) - -// For the time being these type definitions are duplicated here so that we can -// test this file in a non-generated context. -type ( - Middleware = func(*http.Request, MiddlewareNext) (*http.Response, error) - MiddlewareNext = func(*http.Request) (*http.Response, error) -) - -const redactedPlaceholder = "" - -// Headers known to contain sensitive information like an API key. Note that this exclude `Authorization`, -// which is handled specially in `redactRequest` below. -var sensitiveHeaders = []string{ - "api-key", - "x-api-key", - "cookie", - "set-cookie", -} - -// RequestLogger is a middleware that logs HTTP requests and responses. -type RequestLogger struct { - logger interface{ Printf(string, ...any) } // field for testability; usually log.Default() - sensitiveHeaders []string // field for testability; usually sensitiveHeaders -} - -// NewRequestLogger returns a new RequestLogger instance with default options. -func NewRequestLogger() *RequestLogger { - return &RequestLogger{ - logger: log.Default(), - sensitiveHeaders: sensitiveHeaders, - } -} - -func (m *RequestLogger) Middleware() Middleware { - return func(req *http.Request, mn MiddlewareNext) (*http.Response, error) { - redacted, err := m.redactRequest(req) - if err != nil { - return nil, err - } - if reqBytes, err := httputil.DumpRequest(redacted, true); err == nil { - m.logger.Printf("Request Content:\n%s\n", reqBytes) - } - - resp, err := mn(req) - if err != nil { - return resp, err - } - - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { - m.logger.Printf("Response Content:\n%s\n", respBytes) - } - - return resp, err - } -} - -// redactRequest redacts sensitive information from the request for logging -// purposes. If redaction is necessary, the request is cloned before mutating -// the original and that clone is returned. As a small optimization, the -// original is request is returned unchanged if no redaction is necessary. -func (m *RequestLogger) redactRequest(req *http.Request) (*http.Request, error) { - redactedHeaders := req.Header.Clone() - - // Notably, the clauses below are written so they can redact multiple - // headers of the same name if necessary. - if values := redactedHeaders.Values("Authorization"); len(values) > 0 { - redactedHeaders.Del("Authorization") - - for _, value := range values { - // In case we're using something like a bearer token (e.g. `Bearer - // `), keep the `Bearer` part for more debugging - // information. - if authKind, _, ok := strings.Cut(value, " "); ok { - redactedHeaders.Add("Authorization", authKind+" "+redactedPlaceholder) - } else { - redactedHeaders.Add("Authorization", redactedPlaceholder) - } - } - } - - for _, header := range m.sensitiveHeaders { - values := redactedHeaders.Values(header) - if len(values) == 0 { - continue - } - - redactedHeaders.Del(header) - - for range values { - redactedHeaders.Add(header, redactedPlaceholder) - } - } - - if reflect.DeepEqual(req.Header, redactedHeaders) { - return req, nil - } - - redacted := req.Clone(req.Context()) - redacted.Header = redactedHeaders - var err error - redacted.Body, req.Body, err = cloneBody(req.Body) - return redacted, err -} - -// This function returns two copies of an HTTP request body that can each be -// read independently without affecting the other. -// This logic is taken from `drainBody` in net/http/httputil. -func cloneBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { - if b == nil || b == http.NoBody { - // No copying needed. Preserve the magic sentinel meaning of NoBody. - return http.NoBody, http.NoBody, nil - } - var buf bytes.Buffer - if _, err = buf.ReadFrom(b); err != nil { - return nil, b, err - } - if err = b.Close(); err != nil { - return nil, b, err - } - return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil -} diff --git a/internal/debugmiddleware/debug_middleware_test.go b/internal/debugmiddleware/debug_middleware_test.go deleted file mode 100644 index 4e46fbc..0000000 --- a/internal/debugmiddleware/debug_middleware_test.go +++ /dev/null @@ -1,201 +0,0 @@ -package debugmiddleware - -import ( - "bytes" - "io" - "log" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDebugMiddleware(t *testing.T) { - t.Parallel() - - setup := func() (*RequestLogger, *bytes.Buffer) { - var ( - logBuf bytes.Buffer - middleware = NewRequestLogger() - ) - middleware.logger = log.New(&logBuf, "", 0) - return middleware, &logBuf - } - - t.Run("DoesNotRedactMostHeaders", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - const stainlessUserAgent = "Stainless" - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Set("User-Agent", stainlessUserAgent) - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, stainlessUserAgent, req.Header.Get("User-Agent")) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Contains(t, logBuf.String(), "User-Agent: "+stainlessUserAgent) - }) - - const secretToken = "secret-token" - - t.Run("RedactsAuthorizationHeader", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Set("Authorization", secretToken) - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, secretToken, req.Header.Get("Authorization")) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Contains(t, logBuf.String(), "Authorization: "+redactedPlaceholder) - }) - - t.Run("RedactsOnlySecretInAuthorizationHeader", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Set("Authorization", "Bearer "+secretToken) - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Contains(t, logBuf.String(), "Authorization: Bearer "+redactedPlaceholder) - }) - - t.Run("RedactsMultipleAuthorizationHeaders", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Add("Authorization", secretToken+"1") - req.Header.Add("Authorization", secretToken+"2") - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, []string{secretToken + "1", secretToken + "2"}, req.Header.Values("Authorization")) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - - if strings.Count(logBuf.String(), "Authorization: "+redactedPlaceholder) != 2 { - t.Error("expected exactly two redacted placeholders in authorization headers") - } - }) - - const customAPIKeyHeader = "X-My-Api-Key" - - t.Run("RedactsSensitiveHeaders", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - middleware.sensitiveHeaders = []string{customAPIKeyHeader} - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Set(customAPIKeyHeader, secretToken) - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, secretToken, req.Header.Get(customAPIKeyHeader)) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Contains(t, logBuf.String(), customAPIKeyHeader+": "+redactedPlaceholder) - }) - - t.Run("RedactsMultipleSensitiveHeaders", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - - middleware.sensitiveHeaders = []string{customAPIKeyHeader} - - req := httptest.NewRequest("GET", "https://example.com", nil) - req.Header.Add(customAPIKeyHeader, secretToken+"1") - req.Header.Add(customAPIKeyHeader, secretToken+"2") - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, []string{secretToken + "1", secretToken + "2"}, req.Header.Values(customAPIKeyHeader)) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Equal(t, 2, strings.Count(logBuf.String(), customAPIKeyHeader+": "+redactedPlaceholder)) - }) - - t.Run("DoesNotConsumeRequestBodyWhenIoReader", func(t *testing.T) { - t.Parallel() - - middleware, logBuf := setup() - middleware.sensitiveHeaders = []string{customAPIKeyHeader} - - const bodyContent = "test request body content" - bodyReader := strings.NewReader(bodyContent) - - req := httptest.NewRequest("POST", "https://example.com", bodyReader) - req.Header.Set("Authorization", secretToken) - - var nextMiddlewareRan bool - middleware.Middleware()(req, func(req *http.Request) (*http.Response, error) { - nextMiddlewareRan = true - - // The request body should still be fully readable after the middleware runs - body, err := io.ReadAll(req.Body) - require.NoError(t, err) - require.Equal(t, bodyContent, string(body)) - - // The request sent down through middleware shouldn't be mutated. - require.Equal(t, secretToken, req.Header.Get("Authorization")) - - return &http.Response{}, nil - }) - - require.True(t, nextMiddlewareRan) - require.Contains(t, logBuf.String(), "Authorization: "+redactedPlaceholder) - }) -} diff --git a/internal/jsonview/explorer.go b/internal/jsonview/explorer.go deleted file mode 100644 index 836bb2c..0000000 --- a/internal/jsonview/explorer.go +++ /dev/null @@ -1,807 +0,0 @@ -package jsonview - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math" - "os" - "strings" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/table" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/x/term" - "github.com/muesli/reflow/truncate" - "github.com/muesli/reflow/wordwrap" - "github.com/tidwall/gjson" -) - -const ( - // UI layout constants - borderPadding = 2 - heightOffset = 5 - tableMinHeight = 2 - titlePaddingLeft = 2 - titlePaddingTop = 0 - footerPaddingLeft = 1 - - // Column width constants - defaultColumnWidth = 10 - keyColumnWidth = 3 - valueColumnWidth = 5 - - // String formatting constants - maxStringLength = 100 - maxPreviewLength = 24 - - arrayColor = lipgloss.Color("1") - stringColor = lipgloss.Color("5") - objectColor = lipgloss.Color("4") -) - -type keyMap struct { - Up key.Binding - Down key.Binding - Enter key.Binding - Back key.Binding - PrintValue key.Binding - Raw key.Binding - Quit key.Binding -} - -func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Quit, k.Up, k.Down, k.Back, k.Enter, k.PrintValue, k.Raw} -} - -func (k keyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{k.ShortHelp()} -} - -var keys = keyMap{ - Up: key.NewBinding( - key.WithKeys("up", "k"), - key.WithHelp("↑/k", "up"), - ), - Down: key.NewBinding( - key.WithKeys("down", "j"), - key.WithHelp("↓/j", "down"), - ), - Back: key.NewBinding( - key.WithKeys("left", "h", "backspace"), - key.WithHelp("←/h", "go back"), - ), - Enter: key.NewBinding( - key.WithKeys("right", "l"), - key.WithHelp("→/l", "expand"), - ), - PrintValue: key.NewBinding( - key.WithKeys("p"), - key.WithHelp("p", "print and exit"), - ), - Raw: key.NewBinding( - key.WithKeys("r"), - key.WithHelp("r", "toggle raw JSON"), - ), - Quit: key.NewBinding( - key.WithKeys("q", "esc", "ctrl+c", "enter"), - key.WithHelp("q/enter", "quit"), - ), -} - -var ( - titleStyle = lipgloss.NewStyle().Bold(true).PaddingLeft(titlePaddingLeft).PaddingTop(titlePaddingTop) - arrayStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()).BorderForeground(arrayColor) - stringStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()).BorderForeground(stringColor) - objectStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()).BorderForeground(objectColor) - stringLiteralStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("2")) -) - -type JSONView interface { - GetPath() string - GetData() gjson.Result - Update(tea.Msg, bool) tea.Cmd - View() string - Resize(width, height int) -} - -type TableView struct { - width int - height int - path string - data gjson.Result - table table.Model - rowData []gjson.Result - iterator AnyIterator - isLoading bool - columns []table.Column -} - -func (tv *TableView) GetPath() string { return tv.path } -func (tv *TableView) GetData() gjson.Result { return tv.data } -func (tv *TableView) View() string { return tv.table.View() } - -func (tv *TableView) Update(msg tea.Msg, raw bool) tea.Cmd { - var cmd tea.Cmd - tv.table, cmd = tv.table.Update(msg) - - // Check if we need to load more data - if tv.iterator != nil && !tv.isLoading && tv.data.IsArray() { - cursor := tv.table.Cursor() - totalRows := len(tv.table.Rows()) - - // Load more when we're at the last row - if cursor == totalRows-1 { - tv.isLoading = true - return tv.loadMoreData(raw) - } - } - - return cmd -} - -func (tv *TableView) loadMoreData(raw bool) tea.Cmd { - return func() tea.Msg { - if tv.iterator == nil { - return nil - } - - if !tv.iterator.Next() { - tv.isLoading = false - return tv.iterator.Err() - } - - obj := tv.iterator.Current() - var result gjson.Result - if jsonBytes, err := json.Marshal(obj); err != nil { - return err - } else { - result = gjson.ParseBytes(jsonBytes) - } - - if !result.Exists() { - tv.isLoading = false - return nil - } - - // Add the new item to our data - tv.rowData = append(tv.rowData, result) - - // Add new row to the table - newRow := table.Row{formatValue(result, raw)} - - // For array of objects, we need to format according to columns - if len(tv.columns) > 1 && result.IsObject() { - newRow = make(table.Row, len(tv.columns)) - for i, col := range tv.columns { - newRow[i] = formatValue(result.Get(col.Title), raw) - } - } - - rows := tv.table.Rows() - rows = append(rows, newRow) - tv.table.SetRows(rows) - - // Resize columns to accommodate the new data - tv.Resize(tv.width, tv.height) - - tv.isLoading = false - return nil - } -} - -func (tv *TableView) Resize(width, height int) { - tv.width = width - tv.height = height - tv.updateColumnWidths(width) - tv.table.SetHeight(min(height-heightOffset, tableMinHeight+len(tv.table.Rows()))) -} - -func (tv *TableView) updateColumnWidths(width int) { - columns := tv.table.Columns() - widths := make([]int, len(columns)) - - // Calculate required widths from headers and content - for i, col := range columns { - widths[i] = lipgloss.Width(col.Title) - } - - for _, row := range tv.table.Rows() { - for i, cell := range row { - if i < len(widths) { - widths[i] = max(widths[i], lipgloss.Width(cell)) - } - } - } - - totalWidth := sum(widths) - available := width - borderPadding*len(columns) - - if totalWidth <= available { - for i, w := range widths { - columns[i].Width = w - } - return - } - - fairShare := float64(available) / float64(len(columns)) - shrinkable := 0.0 - - for _, w := range widths { - if float64(w) > fairShare { - shrinkable += float64(w) - fairShare - } - } - - if shrinkable > 0 { - excess := float64(totalWidth - available) - for i, w := range widths { - if float64(w) > fairShare { - reduction := (float64(w) - fairShare) * (excess / shrinkable) - widths[i] = int(math.Round(float64(w) - reduction)) - } - } - } - - for i, w := range widths { - columns[i].Width = w - } - - tv.table.SetColumns(columns) -} - -type TextView struct { - path string - data gjson.Result - viewport viewport.Model - ready bool -} - -func (tv *TextView) GetPath() string { return tv.path } -func (tv *TextView) GetData() gjson.Result { return tv.data } -func (tv *TextView) View() string { return tv.viewport.View() } - -func (tv *TextView) Update(msg tea.Msg, raw bool) tea.Cmd { - var cmd tea.Cmd - tv.viewport, cmd = tv.viewport.Update(msg) - return cmd -} - -func (tv *TextView) Resize(width, height int) { - h := height - heightOffset - if !tv.ready { - tv.viewport = viewport.New(width, h) - tv.viewport.SetContent(wordwrap.String(tv.data.String(), width)) - tv.ready = true - return - } - tv.viewport.Width = width - tv.viewport.Height = h -} - -type JSONViewer struct { - stack []JSONView - root string - width int - height int - rawMode bool - message string - help help.Model -} - -// ExploreJSON explores a single JSON value known ahead of time -func ExploreJSON(title string, json gjson.Result) error { - view, err := newView("", json, false) - if err != nil { - return err - } - - viewer := &JSONViewer{stack: []JSONView{view}, root: title, rawMode: false, help: help.New()} - - _, err = tea.NewProgram(viewer).Run() - if viewer.message != "" { - _, msgErr := fmt.Println("\n" + viewer.message) - err = errors.Join(err, msgErr) - } - return err -} - -type hasRawJSON interface { - RawJSON() string -} - -// ExploreJSONStream explores JSON data loaded incrementally via an iterator -func ExploreJSONStream[T any](title string, it Iterator[T]) error { - anyIt := genericToAnyIterator(it) - - preloadCount := 20 - if termHeight, _, err := term.GetSize(os.Stdout.Fd()); err == nil { - preloadCount = termHeight - } - - items := make([]any, 0, preloadCount) - for i := 0; i < preloadCount && anyIt.Next(); i++ { - items = append(items, anyIt.Current()) - } - - if err := anyIt.Err(); err != nil { - return err - } - - arrayJSONBytes, err := marshalItemsToJSONArray(items) - if err != nil { - return err - } - - arrayJSON := gjson.ParseBytes(arrayJSONBytes) - view, err := newTableView("", arrayJSON, false) - if err != nil { - return err - } - - // Set iterator if there might be more data - if len(items) == preloadCount { - view.iterator = anyIt - } - - viewer := &JSONViewer{stack: []JSONView{view}, root: title, rawMode: false, help: help.New()} - _, err = tea.NewProgram(viewer).Run() - if viewer.message != "" { - _, msgErr := fmt.Println("\n" + viewer.message) - err = errors.Join(err, msgErr) - } - return err -} - -func marshalItemsToJSONArray(items []any) ([]byte, error) { - var buf bytes.Buffer - buf.WriteByte('[') - - for i, item := range items { - if i > 0 { - buf.WriteByte(',') - } - if hasRaw, ok := item.(hasRawJSON); ok { - buf.WriteString(hasRaw.RawJSON()) - } else { - jsonData, err := json.Marshal(item) - if err != nil { - return nil, err - } - buf.Write(jsonData) - } - } - - buf.WriteByte(']') - return buf.Bytes(), nil -} - -func (v *JSONViewer) current() JSONView { return v.stack[len(v.stack)-1] } -func (v *JSONViewer) Init() tea.Cmd { return nil } - -func (v *JSONViewer) resize(width, height int) { - v.width, v.height = width, height - v.help.Width = width - for i := range v.stack { - v.stack[i].Resize(width, height) - } -} - -func (v *JSONViewer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - v.resize(msg.Width-borderPadding, msg.Height) - return v, nil - case tea.KeyMsg: - switch { - case key.Matches(msg, keys.Quit): - return v, tea.Quit - case key.Matches(msg, keys.Enter): - return v.navigateForward() - case key.Matches(msg, keys.Back): - return v.navigateBack() - case key.Matches(msg, keys.Raw): - return v.toggleRaw() - case key.Matches(msg, keys.PrintValue): - v.message = v.getSelectedContent() - return v, tea.Quit - } - } - - return v, v.current().Update(msg, v.rawMode) -} - -func (v *JSONViewer) getSelectedContent() string { - tableView, ok := v.current().(*TableView) - if !ok { - return v.current().GetData().Raw - } - - selected := tableView.rowData[tableView.table.Cursor()] - if selected.Type == gjson.String { - return selected.String() - } - return selected.Raw -} - -func (v *JSONViewer) navigateForward() (tea.Model, tea.Cmd) { - tableView, ok := v.current().(*TableView) - if !ok { - return v, nil - } - - if len(tableView.rowData) < 1 { - return v, nil - } - - cursor := tableView.table.Cursor() - selected := tableView.rowData[cursor] - if !v.canNavigateInto(selected) { - return v, nil - } - - path := v.buildNavigationPath(tableView, cursor) - forwardView, err := newView(path, selected, v.rawMode) - if err != nil { - return v, nil - } - - v.stack = append(v.stack, forwardView) - v.resize(v.width, v.height) - return v, nil -} - -func (v *JSONViewer) buildNavigationPath(tableView *TableView, cursor int) string { - if tableView.data.IsArray() { - return fmt.Sprintf("%s[%d]", tableView.path, cursor) - } - key := tableView.data.Get("@keys").Array()[cursor].Str - return fmt.Sprintf("%s[%s]", tableView.path, quoteString(key)) -} - -func quoteString(s string) string { - // Replace backslashes and quotes with escaped versions - s = strings.ReplaceAll(s, "\\", "\\\\") - s = strings.ReplaceAll(s, "\"", "\\\"") - return stringLiteralStyle.Render("\"" + s + "\"") -} - -func (v *JSONViewer) canNavigateInto(data gjson.Result) bool { - switch { - case data.IsArray(): - return len(data.Array()) > 0 - case data.IsObject(): - return len(data.Map()) > 0 - case data.Type == gjson.String: - str := data.String() - return strings.Contains(str, "\n") || lipgloss.Width(str) >= maxStringLength - } - return false -} - -func (v *JSONViewer) navigateBack() (tea.Model, tea.Cmd) { - if len(v.stack) > 1 { - v.stack = v.stack[:len(v.stack)-1] - } - return v, nil -} - -func (v *JSONViewer) toggleRaw() (tea.Model, tea.Cmd) { - v.rawMode = !v.rawMode - - for i, view := range v.stack { - viewWithRaw, err := newView(view.GetPath(), view.GetData(), v.rawMode) - if err != nil { - return v, tea.Printf("Error: %s", err) - } - if newTV, ok := viewWithRaw.(*TableView); ok { - if tv, ok := view.(*TableView); ok && tv.iterator != nil { - newTV.iterator = tv.iterator - } - } - v.stack[i] = viewWithRaw - } - - v.resize(v.width, v.height) - return v, nil -} - -func (v *JSONViewer) View() string { - view := v.current() - title := v.buildTitle(view) - content := titleStyle.Render(title) - style := v.getStyleForData(view.GetData()) - content += "\n" + style.Render(view.View()) - content += "\n" + v.help.View(keys) - return content -} - -func (v *JSONViewer) buildTitle(view JSONView) string { - title := v.root - if len(view.GetPath()) > 0 { - title += " → " + view.GetPath() - } - if v.rawMode { - title += " (JSON)" - } - return title -} - -func (v *JSONViewer) getStyleForData(data gjson.Result) lipgloss.Style { - switch { - case data.Type == gjson.String: - return stringStyle - case data.IsArray(): - return arrayStyle - default: - return objectStyle - } -} - -func newView(path string, data gjson.Result, raw bool) (JSONView, error) { - if data.Type == gjson.String { - return newTextView(path, data) - } - return newTableView(path, data, raw) -} - -func newTextView(path string, data gjson.Result) (*TextView, error) { - if !data.Exists() || data.Type != gjson.String { - return nil, fmt.Errorf("invalid text JSON") - } - return &TextView{path: path, data: data}, nil -} - -func newTableView(path string, data gjson.Result, raw bool) (*TableView, error) { - if !data.Exists() || data.Type != gjson.JSON { - return nil, fmt.Errorf("invalid table JSON") - } - - switch { - case data.IsArray(): - array := data.Array() - if isArrayOfObjects(array) { - return newArrayOfObjectsTableView(path, data, array, raw), nil - } else { - return newArrayTableView(path, data, array, raw), nil - } - case data.IsObject(): - return newObjectTableView(path, data, raw), nil - default: - return nil, fmt.Errorf("unsupported JSON type") - } -} - -func newArrayTableView(path string, data gjson.Result, array []gjson.Result, raw bool) *TableView { - columns := []table.Column{{Title: "Items", Width: defaultColumnWidth}} - rows := make([]table.Row, 0, len(array)) - rowData := make([]gjson.Result, 0, len(array)) - - for _, item := range array { - rows = append(rows, table.Row{formatValue(item, raw)}) - rowData = append(rowData, item) - } - - t := createTable(columns, rows, arrayColor) - return &TableView{ - path: path, - data: data, - table: t, - rowData: rowData, - columns: columns, - } -} - -func newArrayOfObjectsTableView(path string, data gjson.Result, array []gjson.Result, raw bool) *TableView { - // Collect unique keys - keySet := make(map[string]struct{}) - var columns []table.Column - - for _, item := range array { - for _, key := range item.Get("@keys").Array() { - if _, exists := keySet[key.Str]; !exists { - keySet[key.Str] = struct{}{} - title := key.Str - columns = append(columns, table.Column{Title: title, Width: defaultColumnWidth}) - } - } - } - - rows := make([]table.Row, 0, len(array)) - rowData := make([]gjson.Result, 0, len(array)) - - for _, item := range array { - row := make(table.Row, len(columns)) - for i, col := range columns { - row[i] = formatValue(item.Get(col.Title), raw) - } - rows = append(rows, row) - rowData = append(rowData, item) - } - - t := createTable(columns, rows, arrayColor) - return &TableView{ - path: path, - data: data, - table: t, - rowData: rowData, - columns: columns, - } -} - -func newObjectTableView(path string, data gjson.Result, raw bool) *TableView { - columns := []table.Column{{Title: "Object"}, {}} - - keys := data.Get("@keys").Array() - rows := make([]table.Row, 0, len(keys)) - rowData := make([]gjson.Result, 0, len(keys)) - - for _, key := range keys { - value := data.Get(key.Str) - title := key.Str - rows = append(rows, table.Row{title, formatValue(value, raw)}) - rowData = append(rowData, value) - } - - // Adjust column widths based on content - for _, row := range rows { - for i, cell := range row { - if i < len(columns) { - columns[i].Width = max(columns[i].Width, lipgloss.Width(cell)) - } - } - } - - t := createTable(columns, rows, objectColor) - return &TableView{ - path: path, - data: data, - table: t, - rowData: rowData, - columns: columns, - } -} - -func createTable(columns []table.Column, rows []table.Row, bgColor lipgloss.Color) table.Model { - t := table.New( - table.WithColumns(columns), - table.WithRows(rows), - table.WithFocused(true), - ) - - // Set common table styles - s := table.DefaultStyles() - s.Header = s.Header. - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). - BorderBottom(true). - Bold(true) - s.Selected = s.Selected. - Foreground(lipgloss.Color("229")). - Background(bgColor). - Bold(false) - t.SetStyles(s) - - return t -} - -func formatValue(value gjson.Result, raw bool) string { - if raw { - return value.Get("@ugly").Raw - } - - switch { - case value.IsObject(): - return formatObject(value) - case value.IsArray(): - return formatArray(value) - case value.Type == gjson.String: - return value.Str - default: - return value.Raw - } -} - -func formatObject(value gjson.Result) string { - keys := value.Get("@keys").Array() - keyStrs := make([]string, len(keys)) - - for i, key := range keys { - val := value.Get(key.Str) - keyStrs[i] = formatObjectKey(key.Str, val) - } - - return "{" + strings.Join(keyStrs, ", ") + "}" -} - -func formatObjectKey(key string, val gjson.Result) string { - switch { - case val.IsObject(): - return key + ":{…}" - case val.IsArray(): - return key + ":[…]" - case val.Type == gjson.String: - str := val.Str - if lipgloss.Width(str) <= maxPreviewLength { - return fmt.Sprintf(`%s:"%s"`, key, str) - } - return fmt.Sprintf(`%s:"%s…"`, key, truncate.String(str, uint(maxPreviewLength))) - default: - return key + ":" + val.Raw - } -} - -func formatArray(value gjson.Result) string { - switch count := len(value.Array()); count { - case 0: - return "[]" - case 1: - return "[...1 item...]" - default: - return fmt.Sprintf("[...%d items...]", count) - } -} - -func isArrayOfObjects(array []gjson.Result) bool { - for _, item := range array { - if !item.IsObject() { - return false - } - } - return len(array) > 0 -} - -func sum(ints []int) int { - total := 0 - for _, n := range ints { - total += n - } - return total -} - -// An iterator over `any` values -type AnyIterator interface { - Next() bool - Err() error - Current() any -} - -// A generic iterator interface that is used by the `genericIterator` struct -// below to convert iterators over specific types to an AnyIterator -type Iterator[T any] interface { - Next() bool - Err() error - Current() T -} - -// genericIterator adapts a generic Iterator[T] to an AnyIterator. -type genericIterator[T any] struct { - iterator Iterator[T] - current any -} - -func (g *genericIterator[T]) Next() bool { - if !g.iterator.Next() { - return false - } - g.current = g.iterator.Current() - return true -} - -func (g *genericIterator[T]) Err() error { - return g.iterator.Err() -} - -func (g *genericIterator[T]) Current() any { - return g.current -} - -func genericToAnyIterator[T any](it Iterator[T]) AnyIterator { - return &genericIterator[T]{ - iterator: it, - } -} diff --git a/internal/jsonview/explorer_test.go b/internal/jsonview/explorer_test.go deleted file mode 100644 index 67ee730..0000000 --- a/internal/jsonview/explorer_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package jsonview - -import ( - "testing" - - "github.com/charmbracelet/bubbles/help" - "github.com/tidwall/gjson" - - "github.com/stretchr/testify/require" -) - -func TestNavigateForward_EmptyRowData(t *testing.T) { - t.Parallel() - - // An empty JSON array produces a TableView with no rows. - emptyArray := gjson.Parse("[]") - view, err := newTableView("", emptyArray, false) - require.NoError(t, err) - - viewer := &JSONViewer{ - stack: []JSONView{view}, - root: "test", - help: help.New(), - } - - // Should return without panicking despite the empty data set. - model, cmd := viewer.navigateForward() - require.Equal(t, model, viewer, "expected same viewer model returned") - require.Nil(t, cmd) - - // Stack should remain unchanged (no new view pushed). - require.Equal(t, 1, len(viewer.stack), "expected stack length 1, got %d", len(viewer.stack)) -} - -// rawJSONItem implements HasRawJSON, returning pre-built JSON. -type rawJSONItem struct { - raw string -} - -func (r rawJSONItem) RawJSON() string { return r.raw } - -func TestMarshalItemsToJSONArray_WithHasRawJSON(t *testing.T) { - t.Parallel() - - items := []any{ - rawJSONItem{raw: `{"id":1,"name":"alice"}`}, - rawJSONItem{raw: `{"id":2,"name":"bob"}`}, - } - - got, err := marshalItemsToJSONArray(items) - require.NoError(t, err) - require.JSONEq(t, `[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]`, string(got)) -} - -func TestMarshalItemsToJSONArray_WithoutHasRawJSON(t *testing.T) { - t.Parallel() - - items := []any{ - map[string]any{"id": 1, "name": "alice"}, - map[string]any{"id": 2, "name": "bob"}, - } - - got, err := marshalItemsToJSONArray(items) - require.NoError(t, err) - require.JSONEq(t, `[{"id":1,"name":"alice"},{"id":2,"name":"bob"}]`, string(got)) -} diff --git a/internal/jsonview/staticdisplay.go b/internal/jsonview/staticdisplay.go deleted file mode 100644 index 768ea34..0000000 --- a/internal/jsonview/staticdisplay.go +++ /dev/null @@ -1,135 +0,0 @@ -package jsonview - -import ( - "fmt" - "os" - "strings" - - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/x/term" - "github.com/muesli/reflow/truncate" - "github.com/tidwall/gjson" -) - -const ( - tabWidth = 2 -) - -var ( - keyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("75")).Bold(false) - stringValueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("113")) - numberValueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")) - boolValueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("207")) - nullValueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Italic(true) - bulletStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("242")) - containerStyle = lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("63")). - Padding(0, 1) -) - -func formatJSON(json gjson.Result, width int) string { - if !json.Exists() { - return nullValueStyle.Render("Invalid JSON") - } - return formatResult(json, 0, width) -} - -func formatResult(result gjson.Result, indent, width int) string { - switch result.Type { - case gjson.String: - str := result.Str - if str == "" { - return nullValueStyle.Render("(empty)") - } - if lipgloss.Width(str) > width { - str = truncate.String(str, uint(width-1)) + "…" - } - return stringValueStyle.Render(str) - case gjson.Number: - return numberValueStyle.Render(result.Raw) - case gjson.True: - return boolValueStyle.Render("yes") - case gjson.False: - return boolValueStyle.Render("no") - case gjson.Null: - return nullValueStyle.Render("null") - case gjson.JSON: - if result.IsArray() { - return formatJSONArray(result, indent, width) - } - return formatJSONObject(result, indent, width) - default: - return stringValueStyle.Render(result.String()) - } -} - -func isSingleLine(result gjson.Result, indent int) bool { - return !(result.IsObject() || result.IsArray()) -} - -func formatJSONArray(result gjson.Result, indent, width int) string { - items := result.Array() - if len(items) == 0 { - return nullValueStyle.Render(" (none)") - } - - numberWidth := lipgloss.Width(fmt.Sprintf("%d. ", len(items))) - - var formattedItems []string - for i, item := range items { - number := fmt.Sprintf("%d.", i+1) - numbering := getIndent(indent) + bulletStyle.Render(number) - - // If the item will be a one-liner, put it inline after the numbering, - // otherwise it starts with a newline and goes below the numbering. - itemWidth := width - if isSingleLine(item, indent+1) { - // Add right-padding: - numbering += strings.Repeat(" ", numberWidth-lipgloss.Width(number)) - itemWidth = width - lipgloss.Width(numbering) - } - value := formatResult(item, indent+1, itemWidth) - formattedItems = append(formattedItems, numbering+value) - } - return "\n" + strings.Join(formattedItems, "\n") -} - -func formatJSONObject(result gjson.Result, indent, width int) string { - keys := result.Get("@keys").Array() - if len(keys) == 0 { - return nullValueStyle.Render("(empty)") - } - - var items []string - for _, key := range keys { - value := result.Get(key.String()) - keyStr := getIndent(indent) + keyStyle.Render(key.String()+":") - // If item will be a one-liner, put it inline after the key, otherwise - // it starts with a newline and goes below the key. - itemWidth := width - if isSingleLine(value, indent+1) { - keyStr += " " - itemWidth = width - lipgloss.Width(keyStr) - } - formattedValue := formatResult(value, indent+1, itemWidth) - items = append(items, keyStr+formattedValue) - } - - return "\n" + strings.Join(items, "\n") -} - -func getIndent(indent int) string { - return strings.Repeat(" ", indent*tabWidth) -} - -func RenderJSON(title string, json gjson.Result) string { - width, _, err := term.GetSize(os.Stdout.Fd()) - if err != nil { - width = 80 - } - width -= containerStyle.GetBorderLeftSize() + containerStyle.GetBorderRightSize() + - containerStyle.GetPaddingLeft() + containerStyle.GetPaddingRight() - content := strings.TrimLeft(formatJSON(json, width), "\n") - return titleStyle.Render(title) + "\n" + containerStyle.Render(content) -} diff --git a/internal/mocktest/mocktest.go b/internal/mocktest/mocktest.go deleted file mode 100644 index a897833..0000000 --- a/internal/mocktest/mocktest.go +++ /dev/null @@ -1,101 +0,0 @@ -package mocktest - -import ( - "bytes" - "context" - "fmt" - "net" - "net/http" - "net/url" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var mockServerURL *url.URL - -func init() { - mockServerURL, _ = url.Parse("http://localhost:4010") - if testURL := os.Getenv("TEST_API_BASE_URL"); testURL != "" { - if parsed, err := url.Parse(testURL); err == nil { - mockServerURL = parsed - } - } -} - -// OnlyMockServerDialer only allows network connections to the mock server -type OnlyMockServerDialer struct{} - -func (d *OnlyMockServerDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - if address == mockServerURL.Host { - return (&net.Dialer{}).DialContext(ctx, network, address) - } - - return nil, fmt.Errorf("BLOCKED: connection to %s not allowed (only allowed: %s)", address, mockServerURL.Host) -} - -func blockNetworkExceptMockServer() (http.RoundTripper, http.RoundTripper) { - restricted := &http.Transport{ - DialContext: (&OnlyMockServerDialer{}).DialContext, - } - - origClient, origDefault := http.DefaultClient.Transport, http.DefaultTransport - http.DefaultClient.Transport, http.DefaultTransport = restricted, restricted - return origClient, origDefault -} - -func restoreNetwork(origClient, origDefault http.RoundTripper) { - http.DefaultClient.Transport, http.DefaultTransport = origClient, origDefault -} - -// TestRunMockTestWithFlags runs a test against a mock server with the provided -// CLI args and ensures it succeeds -func TestRunMockTestWithFlags(t *testing.T, args ...string) { - TestRunMockTestWithPipeAndFlags(t, nil, args...) -} - -// TestRunMockTestWithPipeAndFlags runs a test against a mock server with the provided -// data piped over stdin and CLI args and ensures it succeeds -func TestRunMockTestWithPipeAndFlags(t *testing.T, pipeData []byte, args ...string) { - origClient, origDefault := blockNetworkExceptMockServer() - defer restoreNetwork(origClient, origDefault) - - // Check if mock server is running - conn, err := net.DialTimeout("tcp", mockServerURL.Host, 2*time.Second) - if err != nil { - require.Fail(t, "Mock server is not running on "+mockServerURL.Host+". Please start the mock server before running tests.") - } else { - conn.Close() - } - - // Get the path to the main command - _, filename, _, ok := runtime.Caller(0) - require.True(t, ok, "Could not get current file path") - dirPath := filepath.Dir(filename) - project := filepath.Join(dirPath, "..", "..", "cmd", "dedalus") - - args = append([]string{"run", project, "--base-url", mockServerURL.String()}, args...) - - t.Logf("Testing command: go run ./cmd/dedalus %s", strings.Join(args[2:], " ")) - - cmd := exec.Command("go", args...) - cmd.Stdin = bytes.NewReader(pipeData) - output, err := cmd.CombinedOutput() - assert.NoError(t, err, "Test failed\nError: %v\nOutput: %s", err, output) - - t.Logf("Test passed successfully\nOutput:\n%s", string(output)) -} - -func TestFile(t *testing.T, contents string) string { - tmpDir := t.TempDir() - filename := filepath.Join(tmpDir, "file.txt") - require.NoError(t, os.WriteFile(filename, []byte(contents), 0644)) - return filename -} diff --git a/internal/requestflag/innerflag.go b/internal/requestflag/innerflag.go deleted file mode 100644 index 528915f..0000000 --- a/internal/requestflag/innerflag.go +++ /dev/null @@ -1,289 +0,0 @@ -package requestflag - -import ( - "fmt" - "reflect" - "strings" - - "github.com/urfave/cli/v3" -) - -// InnerFlag[T] represents a CLI flag for the urfave/cli package that allows setting -// nested fields within other flags. For example, using `--foo.baz` will set the "baz" -// field on a parent flag named `--foo`. -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 | *DateTimeValue | *DateValue | *TimeValue, -] struct { - Name string // name of the flag - DefaultText string // default text of the flag for usage purposes - Usage string // usage string for help output - Aliases []string // aliases that are allowed for this flag - Validator func(T) error // custom function to validate this flag value - - 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. -func (f *InnerFlag[T]) GetDataAliases() []string { - return f.DataAliases -} - -// GetInnerField returns the API field name that this inner flag sets on its outer flag's value. -// For example, the flag --parent.foo targeting a parameter whose OpenAPI property name is "foo" -// would return "foo". This is distinct from the flag's CLI name and from any DataAliases entries. -func (f *InnerFlag[T]) GetInnerField() string { - return f.InnerField -} - -type HasOuterFlag interface { - cli.Flag - SetOuterFlag(cli.Flag) - GetOuterFlag() cli.Flag - GetInnerField() string - GetDataAliases() []string -} - -func (f *InnerFlag[T]) SetOuterFlag(flag cli.Flag) { - f.OuterFlag = flag -} - -func (f *InnerFlag[T]) GetOuterFlag() cli.Flag { - return f.OuterFlag -} - -// Implementation of the cli.Flag interface -var _ cli.Flag = (*InnerFlag[any])(nil) // Type assertion to ensure interface compliance - -func (f *InnerFlag[T]) PreParse() error { - return nil -} - -func (f *InnerFlag[T]) PostParse() error { - return nil -} - -func (f *InnerFlag[T]) Set(name string, rawVal string) error { - if parsedValue, err := parseCLIArg[T](rawVal); err != nil { - return err - } else { - if f.Validator != nil { - if err := f.Validator(parsedValue); err != nil { - return err - } - } - - if seeder, ok := f.OuterFlag.(InnerFieldSeeder); ok { - seeder.SeedInnerCollection(f.OuterIsArrayOfObjects) - } - - if settableInnerField, ok := f.OuterFlag.(SettableInnerField); ok { - settableInnerField.SetInnerField(f.InnerField, parsedValue) - } else { - return fmt.Errorf("Cannot set inner field on %v", f.OuterFlag) - } - return nil - } -} - -func (f *InnerFlag[T]) Get() any { - var zeroValue T - return zeroValue -} - -func (f *InnerFlag[T]) String() string { - return cli.FlagStringer(f) -} - -func (f *InnerFlag[T]) IsSet() bool { - return false -} - -func (f *InnerFlag[T]) Names() []string { - return cli.FlagNames(f.Name, f.Aliases) -} - -// Implementation for the cli.DocGenerationFlag interface -var _ cli.DocGenerationFlag = (*InnerFlag[any])(nil) // Type assertion to ensure interface compliance - -func (f *InnerFlag[T]) TakesValue() bool { - var t T - return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool -} - -func (f *InnerFlag[T]) GetUsage() string { - return f.Usage -} - -func (f *InnerFlag[T]) GetValue() string { - return "" -} - -func (f *InnerFlag[T]) GetDefaultText() string { - return f.DefaultText -} - -func (f *InnerFlag[T]) GetEnvVars() []string { - return nil -} - -func (f *InnerFlag[T]) IsDefaultVisible() bool { - return false -} - -func (f *InnerFlag[T]) TypeName() string { - var zeroValue T - ty := reflect.TypeOf(zeroValue) - 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 { - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return "int" - case reflect.Float32, reflect.Float64: - return "float" - case reflect.Bool: - return "boolean" - case reflect.String: - switch t.Name() { - case "DateTimeValue": - return "datetime" - case "DateValue": - return "date" - case "TimeValue": - return "time" - default: - return "string" - } - default: - if t.Name() == "" { - return "any" - } - return strings.ToLower(t.Name()) - } - } - - switch ty.Kind() { - case reflect.Slice: - elemType := ty.Elem() - return getTypeName(elemType) - case reflect.Map: - keyType := ty.Key() - valueType := ty.Elem() - return fmt.Sprintf("%s=%s", getTypeName(keyType), getTypeName(valueType)) - default: - return getTypeName(ty) - } -} - -// Implementation for the cli.DocGenerationMultiValueFlag interface -var _ cli.DocGenerationMultiValueFlag = (*InnerFlag[any])(nil) // Type assertion to ensure interface compliance - -func (f *InnerFlag[T]) IsMultiValueFlag() bool { - return false -} - -func (f *InnerFlag[T]) IsBoolFlag() bool { - var zeroValue T - _, isBool := any(zeroValue).(bool) - return isBool -} - -// WithInnerFlags takes a command and a map of flag names to inner flags, -// and returns a modified command with the appropriate inner flags set. -func WithInnerFlags(cmd cli.Command, innerFlagMap map[string][]HasOuterFlag) cli.Command { - if len(innerFlagMap) == 0 { - return cmd - } - - // If any keys are unused by the end, we know that they were not valid - unusedInnerFlagKeys := make(map[string]struct{}) - for name := range innerFlagMap { - unusedInnerFlagKeys[name] = struct{}{} - } - - updatedFlags := make([]cli.Flag, 0, len(cmd.Flags)) - for _, flag := range cmd.Flags { - updatedFlags = append(updatedFlags, flag) - for _, name := range flag.Names() { - // Check if this flag has inner flags in our map - innerFlags, hasInnerFlags := innerFlagMap[name] - if !hasInnerFlags { - continue - } - - // Mark this inner flag key as used - delete(unusedInnerFlagKeys, name) - - for _, innerFlag := range innerFlags { - innerFlag.SetOuterFlag(flag) - updatedFlags = append(updatedFlags, innerFlag) - } - } - } - - // If there are inner flags that don't correspond to any valid outer flag - // names, then panic because the user probably made a typo or forgot to - // delete inner flags that correspond to missing outer flags. - if len(unusedInnerFlagKeys) > 0 { - unusedKeys := make([]string, 0, len(unusedInnerFlagKeys)) - for key := range unusedInnerFlagKeys { - unusedKeys = append(unusedKeys, key) - } - panic(fmt.Sprintf("Missing outer flags to use with inner flags: %v", unusedKeys)) - } - - result := cmd - result.Flags = updatedFlags - return result -} - -// Helper function to verify that all inner flags have an outer flag set and -// follow the --foo.baz prefix format -func CheckInnerFlags(cmd cli.Command) error { - var errors []string - for _, flag := range cmd.Flags { - if innerFlag, ok := flag.(HasOuterFlag); ok { - outerFlag := innerFlag.GetOuterFlag() - if outerFlag == nil { - errors = append(errors, fmt.Sprintf("inner flag %s is missing an outer flag", flag.Names())) - continue - } - - innerFlagName := flag.Names()[0] - valid := false - for _, outerName := range outerFlag.Names() { - if strings.HasPrefix(innerFlagName, outerName+".") { - valid = true - break - } - } - - if !valid { - errors = append(errors, fmt.Sprintf("inner flag %s must start with one of its outer flag's names followed by a dot", innerFlagName)) - } - } - } - - if len(errors) > 0 { - return fmt.Errorf("%s", strings.Join(errors, "; ")) - } - return nil -} diff --git a/internal/requestflag/innerflag_test.go b/internal/requestflag/innerflag_test.go deleted file mode 100644 index 133e8b4..0000000 --- a/internal/requestflag/innerflag_test.go +++ /dev/null @@ -1,347 +0,0 @@ -package requestflag - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v3" -) - -func TestInnerFlagSet(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - flagType string - inputVal string - expected any - expectErr bool - }{ - {"string", "string", "hello", "hello", false}, - {"int64", "int64", "42", int64(42), false}, - {"float64", "float64", "3.14", float64(3.14), false}, - {"bool", "bool", "true", true, false}, - {"invalid int", "int64", "not-a-number", nil, true}, - {"invalid float", "float64", "not-a-float", nil, true}, - {"invalid bool", "bool", "not-a-bool", nil, true}, - {"yaml map", "map", "key: value", map[string]any{"key": "value"}, false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{ - Name: "test-flag", - } - - var innerFlag cli.Flag - switch tt.flagType { - case "string": - innerFlag = &InnerFlag[string]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - } - case "int64": - innerFlag = &InnerFlag[int64]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - } - case "float64": - innerFlag = &InnerFlag[float64]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - } - case "bool": - innerFlag = &InnerFlag[bool]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - } - case "map": - innerFlag = &InnerFlag[map[string]any]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - } - } - - err := innerFlag.Set(innerFlag.Names()[0], tt.inputVal) - - if tt.expectErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - actual, ok := outerFlag.Get().(map[string]any)["test_field"] - assert.True(t, ok, "Field 'test_field' should exist in the map") - assert.Equal(t, tt.expected, actual, "Expected %v (%T), got %v (%T)", tt.expected, tt.expected, actual, actual) - }) - } -} - -func TestInnerFlagValidator(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "test-flag"} - - innerFlag := &InnerFlag[int64]{ - Name: "test-flag.test-field", - OuterFlag: outerFlag, - InnerField: "test_field", - Validator: func(val int64) error { - if val < 0 { - return cli.Exit("Value must be non-negative", 1) - } - return nil - }, - } - - // Valid case - err := innerFlag.Set(innerFlag.Name, "42") - assert.NoError(t, err, "Expected no error for valid value, got: %v", err) - - // Should trigger validator error - err = innerFlag.Set(innerFlag.Name, "-5") - assert.Error(t, err, "Expected error for invalid value, got none") -} - -func TestWithInnerFlags(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "outer"} - innerFlag := &InnerFlag[string]{ - Name: "outer.baz", - InnerField: "baz", - } - - cmd := WithInnerFlags(cli.Command{ - Name: "test-command", - Flags: []cli.Flag{outerFlag}, - }, map[string][]HasOuterFlag{ - "outer": {innerFlag}, - }) - - // Verify that the command now has both the original flag and inner flag - assert.Len(t, cmd.Flags, 2, "Expected 2 flags, got %d", len(cmd.Flags)) - assert.Equal(t, outerFlag, cmd.Flags[0], "First flag should be outerFlag") - assert.Equal(t, innerFlag, cmd.Flags[1], "Second flag should be innerFlag") - assert.Same(t, outerFlag, innerFlag.OuterFlag, "innerFlag.OuterFlag should point to outerFlag") -} - -func TestInnerFlagTypeNames(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - flag cli.DocGenerationFlag - expected string - }{ - {"string", &InnerFlag[string]{}, "string"}, - {"int64", &InnerFlag[int64]{}, "int"}, - {"float64", &InnerFlag[float64]{}, "float"}, - {"bool", &InnerFlag[bool]{}, "boolean"}, - {"string slice", &InnerFlag[[]string]{}, "string"}, - {"date", &InnerFlag[DateValue]{}, "date"}, - {"datetime", &InnerFlag[DateTimeValue]{}, "datetime"}, - {"time", &InnerFlag[TimeValue]{}, "time"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - typeName := tt.flag.TypeName() - assert.Equal(t, tt.expected, typeName, "Expected type name %q, got %q", tt.expected, typeName) - }) - } -} - -func TestInnerYamlHandling(t *testing.T) { - t.Parallel() - - // Test with map value - t.Run("Parse YAML to map", func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "outer"} - innerFlag := &InnerFlag[map[string]any]{ - Name: "outer.baz", - OuterFlag: outerFlag, - InnerField: "baz", - } - - err := innerFlag.Set(innerFlag.Name, "{name: test, value: 42}") - assert.NoError(t, err) - - // Retrieve and check the parsed YAML map - result, ok := outerFlag.Get().(map[string]any) - assert.True(t, ok, "Expected map[string]any from outerFlag.Get()") - yamlField, ok := result["baz"].(map[string]any) - assert.True(t, ok, "Expected map[string]any, got %T", result["baz"]) - val := yamlField - - if ok { - assert.Equal(t, map[string]any{"name": "test", "value": uint64(42)}, val) - } - }) - - // Test with invalid YAML - t.Run("Parse invalid YAML", func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "outer"} - innerFlag := &InnerFlag[map[string]any]{ - Name: "outer.baz", - OuterFlag: outerFlag, - InnerField: "baz", - } - - invalidYaml := `[not closed` - err := innerFlag.Set(innerFlag.Name, invalidYaml) - assert.Error(t, err) - }) - - // Test setting inner flags on a map multiple times - t.Run("Set inner flags on map multiple times", func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "outer"} - - // Set first inner flag - firstInnerFlag := &InnerFlag[string]{ - Name: "outer.first-flag", - OuterFlag: outerFlag, - InnerField: "first_field", - } - - err := firstInnerFlag.Set(firstInnerFlag.Name, "first-value") - assert.NoError(t, err) - - // Set second inner flag - secondInnerFlag := &InnerFlag[int64]{ - Name: "outer.second-flag", - OuterFlag: outerFlag, - InnerField: "second_field", - } - - err = secondInnerFlag.Set(secondInnerFlag.Name, "42") - assert.NoError(t, err) - - // Verify both fields are set correctly - result := outerFlag.Get().(map[string]any) - assert.Equal(t, map[string]any{"first_field": "first-value", "second_field": int64(42)}, result) - }) - - // Test setting YAML and then an inner flag - t.Run("Set YAML and then inner flag", func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[map[string]any]{Name: "outer"} - - // First set the outer flag with YAML - err := outerFlag.Set(outerFlag.Name, `{existing: value, another: field}`) - assert.NoError(t, err) - - // Then set an inner flag - innerFlag := &InnerFlag[string]{ - Name: "outer.inner-flag", - OuterFlag: outerFlag, - InnerField: "new_field", - } - - err = innerFlag.Set(innerFlag.Name, "inner-value") - assert.NoError(t, err) - - // Verify both the YAML content and inner flag value - result := outerFlag.Get().(map[string]any) - assert.Equal(t, map[string]any{ - "existing": "value", - "another": "field", - "new_field": "inner-value", - }, result) - }) -} - -func TestInnerFlagWithSliceType(t *testing.T) { - t.Parallel() - - t.Run("Setting inner flags on slice of maps", func(t *testing.T) { - t.Parallel() - - outerFlag := &Flag[[]map[string]any]{Name: "outer"} - - // Set first inner flag (should create first item) - firstInnerFlag := &InnerFlag[string]{ - Name: "outer.name-flag", - OuterFlag: outerFlag, - InnerField: "name", - } - - err := firstInnerFlag.Set(firstInnerFlag.Name, "item1") - assert.NoError(t, err) - - // Set second inner flag (should modify first item) - secondInnerFlag := &InnerFlag[int64]{ - Name: "outer.count-flag", - OuterFlag: outerFlag, - InnerField: "count", - } - - err = secondInnerFlag.Set(secondInnerFlag.Name, "42") - assert.NoError(t, err) - - // Set name flag again (should create second item) - err = firstInnerFlag.Set(firstInnerFlag.Name, "item2") - assert.NoError(t, err) - - // Verify the slice has two items with correct values - result := outerFlag.Get().([]map[string]any) - - assert.Equal(t, []map[string]any{ - {"name": "item1", "count": int64(42)}, - {"name": "item2"}, - }, result) - assert.Nil(t, result[1]["count"], "Second item should not have count field") - }) - - t.Run("Appending to existing slice", func(t *testing.T) { - t.Parallel() - - // Initialize with existing items - outerFlag := &Flag[[]map[string]any]{Name: "outer"} - err := outerFlag.Set(outerFlag.Name, `{name: initial}`) - assert.NoError(t, err) - - // Set inner flag to modify existing item - modifyFlag := &InnerFlag[string]{ - Name: "outer.value-flag", - OuterFlag: outerFlag, - InnerField: "value", - } - - err = modifyFlag.Set(modifyFlag.Name, "updated") - assert.NoError(t, err) - - // Set inner flag to create new item - newItemFlag := &InnerFlag[string]{ - Name: "outer.name-flag", - OuterFlag: outerFlag, - InnerField: "name", - } - - err = newItemFlag.Set(newItemFlag.Name, "second") - assert.NoError(t, err) - - // Verify both items - result := outerFlag.Get().([]map[string]any) - assert.Equal(t, []map[string]any{ - {"name": "initial", "value": "updated"}, - {"name": "second"}, - }, result) - }) -} diff --git a/internal/requestflag/requestflag.go b/internal/requestflag/requestflag.go deleted file mode 100644 index 77c4f1f..0000000 --- a/internal/requestflag/requestflag.go +++ /dev/null @@ -1,992 +0,0 @@ -package requestflag - -import ( - "encoding/json" - "fmt" - "reflect" - "strconv" - "strings" - "time" - "unicode" - - "github.com/goccy/go-yaml" - "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 | *DateTimeValue | *DateValue | *TimeValue, -] struct { - Name string // name of the flag - Category string // category of the flag, if any - DefaultText string // default text of the flag for usage purposes - HideDefault bool // whether to hide the default value in output - Usage string // usage string for help output - Sources cli.ValueSourceChain // sources to load flag value from - Required bool // whether the flag is required or not - Hidden bool // whether to hide the flag in help output - Default T // default value for this flag if not set by from any source - Aliases []string // aliases that are allowed for this flag - Validator func(T) error // custom function to validate this flag value - - QueryPath string // location in the request query string to put this flag's value - HeaderPath string // location in the request header to put this flag's value - 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, - // but isn't required to provide it. This is used for single-value enums and `x-stainless-const` - // parameters. - Const bool - - // FileInput, when true, indicates that the flag value is always treated as a file path. The file is read - // automatically without requiring the "@" prefix. This is used for parameters with `type: string, format: - // binary` in the OpenAPI spec. - FileInput bool - - // DataAliases is a list of alternate names for this parameter recognized when parsing piped YAML/JSON - // input. Values keyed by any alias are translated to the canonical API name before being sent. - DataAliases []string - - // unexported fields for internal use - count int // number of times the flag has been set - hasBeenSet bool // whether the flag has been set from env or file - applied bool // whether the flag has been applied to a flag set already - value cli.Value // value representing this flag's value -} - -// Type assertions to verify we implement the relevant urfave/cli interfaces -var _ cli.CategorizableFlag = (*Flag[any])(nil) - -// InRequest interface for flags that should be included in HTTP requests -type InRequest interface { - GetQueryPath() string - GetHeaderPath() string - GetBodyPath() string - GetPathParam() string - IsBodyRoot() bool - IsFileInput() bool - GetDataAliases() []string -} - -func (f Flag[T]) GetQueryPath() string { - return f.QueryPath -} - -func (f Flag[T]) GetHeaderPath() string { - return f.HeaderPath -} - -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 -} - -func (f Flag[T]) IsFileInput() bool { - return f.FileInput -} - -func (f Flag[T]) GetDataAliases() []string { - return f.DataAliases -} - -// The values that will be sent in different parts of a request. -type RequestContents struct { - Queries map[string]any - Headers map[string]any - Body any -} - -// 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{ - Queries: make(map[string]any), - Headers: make(map[string]any), - Body: bodyMap, - } - - for _, flag := range cmd.Flags { - if !flag.IsSet() { - continue - } - - value := flag.Get() - if toSend, ok := flag.(InRequest); ok { - if queryPath := toSend.GetQueryPath(); queryPath != "" { - res.Queries[queryPath] = value - } - if headerPath := toSend.GetHeaderPath(); headerPath != "" { - res.Headers[headerPath] = value - } - if toSend.IsBodyRoot() { - res.Body = value - } else if bodyPath := toSend.GetBodyPath(); bodyPath != "" { - bodyMap[bodyPath] = value - } - } - } - return res -} - -func GetMissingRequiredFlags(cmd *cli.Command, body any) []cli.Flag { - missing := []cli.Flag{} - for _, flag := range cmd.Flags { - if flag.IsSet() { - continue - } - - if required, ok := flag.(cli.RequiredFlag); ok && required.IsRequired() { - missing = append(missing, flag) - continue - } - - if r, ok := flag.(RequiredFlagOrStdin); !ok || !r.IsRequiredAsFlagOrStdin() { - continue - } - - if toSend, ok := flag.(InRequest); ok { - if toSend.IsBodyRoot() { - if body != nil { - continue - } - } else if bodyPath := toSend.GetBodyPath(); bodyPath != "" { - if bodyMap, ok := body.(map[string]any); ok { - if _, found := bodyMap[bodyPath]; found { - continue - } - } - } - } - missing = append(missing, flag) - } - return missing -} - -// Implementation of the cli.Flag interface -var _ cli.Flag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) PreParse() error { - newVal := f.Default - f.value = &cliValue[T]{newVal} - - // Validate the given default or values set from external sources as well - if f.Validator != nil { - if err := f.Validator(f.value.Get().(T)); err != nil { - return err - } - } - f.applied = true - return nil -} - -func (f *Flag[T]) PostParse() error { - if !f.hasBeenSet { - if val, source, found := f.Sources.LookupWithSource(); found { - if val != "" || reflect.TypeOf(f.value).Kind() == reflect.String { - if err := f.Set(f.Name, val); err != nil { - return fmt.Errorf( - "could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s", - val, f.value, source, f.Name, err, - ) - } - } else if val == "" && reflect.TypeOf(f.value).Kind() == reflect.Bool { - _ = f.Set(f.Name, "false") - } - - f.hasBeenSet = true - } - } - return nil -} - -func (f *Flag[T]) Set(name string, val string) error { - // Initialize flag if needed - if !f.applied { - if err := f.PreParse(); err != nil { - return err - } - f.applied = true - } - - f.count++ - - // If this is the first time setting a slice type, reset it to empty - // to avoid appending to the default value - if f.count == 1 && f.value != nil { - typ := reflect.TypeOf(f.Default) - if typ != nil && typ.Kind() == reflect.Slice { - // Create a new empty slice of the same type and set it - emptySlice := reflect.MakeSlice(typ, 0, 0).Interface() - f.value = &cliValue[T]{emptySlice.(T)} - } - } - - if err := f.value.Set(val); err != nil { - return err - } - - f.hasBeenSet = true - - if f.Validator != nil { - if err := f.Validator(f.value.Get().(T)); err != nil { - return err - } - } - return nil -} - -func (f *Flag[T]) Get() any { - if f.value != nil { - return f.value.Get() - } - return f.Default -} - -func (f *Flag[T]) String() string { - return cli.FlagStringer(f) -} - -func (f *Flag[T]) IsSet() bool { - return f.hasBeenSet || f.Const -} - -func (f *Flag[T]) Names() []string { - return cli.FlagNames(f.Name, f.Aliases) -} - -// Implementation for the cli.VisibleFlag interface -var _ cli.VisibleFlag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) IsVisible() bool { - return !f.Hidden -} - -func (f *Flag[T]) GetCategory() string { - return f.Category -} - -func (f *Flag[T]) SetCategory(c string) { - f.Category = c -} - -// Implementation for the cli.RequiredFlag interface -var _ cli.RequiredFlag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) IsRequired() bool { - // Const flags are always auto-set, so never required from the user. - if f.Const { - return false - } - // Intentionally don't use `f.Required`, because request flags may be passed - // over stdin as well as by flag. - if f.BodyPath != "" || f.BodyRoot || f.PathParam != "" || f.QueryPath != "" || f.HeaderPath != "" { - return false - } - return f.Required -} - -type RequiredFlagOrStdin interface { - IsRequiredAsFlagOrStdin() bool -} - -func (f *Flag[T]) IsRequiredAsFlagOrStdin() bool { - // Const flags are always auto-set, so never required from the user. - if f.Const { - return false - } - return f.Required -} - -// Implementation for the cli.DocGenerationFlag interface -var _ cli.DocGenerationFlag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) TakesValue() bool { - var t T - return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool -} - -func (f *Flag[T]) GetUsage() string { - return f.Usage -} - -func (f *Flag[T]) GetValue() string { - if f.value == nil { - return "" - } - return f.value.String() -} - -func (f *Flag[T]) GetDefaultText() string { - return f.DefaultText -} - -// GetEnvVars returns the env vars for this flag -func (f *Flag[T]) GetEnvVars() []string { - return f.Sources.EnvKeys() -} - -func (f *Flag[T]) IsDefaultVisible() bool { - return !f.HideDefault -} - -func (f *Flag[T]) TypeName() string { - ty := reflect.TypeOf(f.Default) - 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 { - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return "int" - case reflect.Float32, reflect.Float64: - return "float" - case reflect.Bool: - return "boolean" - case reflect.String: - switch t.Name() { - case "DateTimeValue": - return "datetime" - case "DateValue": - return "date" - case "TimeValue": - return "time" - default: - return "string" - } - default: - if t.Name() == "" { - return "any" - } - return strings.ToLower(t.Name()) - } - } - - switch ty.Kind() { - case reflect.Slice: - elemType := ty.Elem() - return getTypeName(elemType) - case reflect.Map: - keyType := ty.Key() - valueType := ty.Elem() - return fmt.Sprintf("%s=%s", getTypeName(keyType), getTypeName(valueType)) - default: - return getTypeName(ty) - } -} - -// Implementation for the cli.DocGenerationMultiValueFlag interface -var _ cli.DocGenerationMultiValueFlag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) IsMultiValueFlag() bool { - if reflect.TypeOf(f.Default) == nil { - return false - } - kind := reflect.TypeOf(f.Default).Kind() - return kind == reflect.Slice || kind == reflect.Map -} - -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 -} - -// Implementation for the cli.Countable interface -var _ cli.Countable = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f *Flag[T]) Count() int { - return f.count -} - -// Implementation for the cli.LocalFlag interface -var _ cli.LocalFlag = (*Flag[any])(nil) // Type assertion to ensure interface compliance - -func (f Flag[T]) IsLocal() bool { - // By default, all request flags are local, i.e. can be provided at any part of the CLI command. - return true -} - -// cliValue is a generic implementation of cli.Value for common types -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 | - *string | *float64 | *int64 | *bool | *DateTimeValue | *DateValue | *TimeValue, -] struct { - value T -} - -// Take an argument string for a single argument and convert it into a typed -// value for one of the supported CLI argument types -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 | - *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 - case int64: - parsedValue, err = strconv.ParseInt(value, 0, 64) - case float64: - parsedValue, err = strconv.ParseFloat(value, 64) - case bool: - parsedValue, err = strconv.ParseBool(value) - 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 - } - - // 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 - parsedValue = value - } else { - var yamlValue T - err = yaml.Unmarshal([]byte(value), &yamlValue) - if err == nil { - parsedValue = yamlValue - } else if allowAsLiteralString(value) { - parsedValue = value - } else { - parsedValue = nil - err = fmt.Errorf("failed to parse as YAML: %w", err) - } - } - } - - // Nil needs to be handled specially because unmarshalling a YAML `null` - // causes problems when doing type assertions. - if parsedValue == nil { - parsedValue = (*struct{})(nil) - } - - if err == nil { - if typedValue, ok := parsedValue.(T); ok { - return typedValue, nil - } else { - expectedType := reflect.TypeFor[T]() - err = fmt.Errorf("Couldn't convert %q (%v) to expected type %v", value, parsedValue, expectedType) - } - } - return empty, err - -} - -// 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`), -// base64 (`aGVsbG8=`), and qualified identifiers (`color.Red`). This should -// not include strings that look like mistyped YAML (e.g. `{key:`) -func allowAsLiteralString(s string) bool { - for _, c := range s { - if !unicode.IsLetter(c) && !unicode.IsDigit(c) && - c != '_' && c != '-' && c != '.' && c != '=' { - return false - } - } - return true -} - -// Parse the input string and set result as the cliValue's value -func (c *cliValue[T]) Set(value string) error { - valueType := reflect.TypeOf(c.value) - // When setting slice values, we append to the existing values - // e.g. --foo 10 --foo 20 --foo 30 => [10, 20, 30] - if valueType != nil && valueType.Kind() == reflect.Slice { - elemType := valueType.Elem() - - var singleElem any - var err error - switch elemType.Kind() { - case reflect.String: - singleElem, err = parseCLIArg[string](value) - case reflect.Int64: - singleElem, err = parseCLIArg[int64](value) - case reflect.Float64: - singleElem, err = parseCLIArg[float64](value) - case reflect.Bool: - singleElem, err = parseCLIArg[bool](value) - default: - // Check for special types by name - switch elemType.Name() { - case "DateTimeValue": - singleElem, err = parseCLIArg[DateTimeValue](value) - case "DateValue": - singleElem, err = parseCLIArg[DateValue](value) - case "TimeValue": - singleElem, err = parseCLIArg[TimeValue](value) - default: - // This handles []map[string]any - if elemType.Kind() == reflect.Map && elemType.Key().Kind() == reflect.String { - singleElem, err = parseCLIArg[map[string]any](value) - } else { - singleElem, err = parseCLIArg[any](value) - } - } - } - - if err != nil { - return err - } - - // Append the new element to the slice - sliceValue := reflect.ValueOf(c.value) - if !sliceValue.IsValid() || sliceValue.IsNil() { - // Create a new slice if the current one is nil - sliceValue = reflect.MakeSlice(valueType, 0, 1) - } - - // Append the new element - newElem := reflect.ValueOf(singleElem) - sliceValue = reflect.Append(sliceValue, newElem) - - // Set the updated slice back to c.value - c.value = sliceValue.Interface().(T) - } else { - // For non-slice types, simply parse and set the value - if parsedValue, err := parseCLIArg[T](value); err != nil { - return err - } else { - c.value = parsedValue - } - } - - return nil -} - -func (c *cliValue[T]) Get() any { - return c.value -} - -func (c *cliValue[T]) String() string { - switch v := any(c.value).(type) { - case string, int, int64, float64, bool, DateTimeValue, DateValue, TimeValue, - []string, []int, []int64, []float64, []bool, []DateTimeValue, []DateValue, []TimeValue: - // 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)) - if err != nil { - // Fall back to standard format if YAML conversion fails - return fmt.Sprintf("%v", c.value) - } - return string(yamlBytes) - } -} - -func (c *cliValue[T]) IsBoolFlag() bool { - _, ok := any(c.value).(bool) - return ok -} - -// Time-related value types -type DateValue string -type DateTimeValue string -type TimeValue string - -// String methods for time-related types -func (d DateValue) String() string { - return string(d) -} - -func (d DateTimeValue) String() string { - return string(d) -} - -func (t TimeValue) String() string { - return string(t) -} - -// parseTimeWithFormats attempts to parse a string using multiple formats -func parseTimeWithFormats(s string, formats []string) (time.Time, error) { - var lastErr error - for _, format := range formats { - t, err := time.Parse(format, s) - if err == nil { - return t, nil - } - lastErr = err - } - return time.Time{}, lastErr -} - -// Parse methods for time-related types -func (d *DateValue) Parse(s string) error { - formats := []string{ - "2006-01-02", - "01/02/2006", - "Jan 2, 2006", - "January 2, 2006", - "2-Jan-2006", - } - - t, err := parseTimeWithFormats(s, formats) - if err != nil { - return fmt.Errorf("unable to parse date: %v", err) - } - - *d = DateValue(t.Format("2006-01-02")) - return nil -} - -func (d *DateTimeValue) Parse(s string) error { - formats := []string{ - time.RFC3339, - time.RFC3339Nano, - "2006-01-02T15:04:05", - "2006-01-02 15:04:05", - time.RFC1123, - time.RFC822, - time.ANSIC, - } - - t, err := parseTimeWithFormats(s, formats) - if err != nil { - return fmt.Errorf("unable to parse datetime: %v", err) - } - - *d = DateTimeValue(t.Format(time.RFC3339)) - return nil -} - -func (t *TimeValue) Parse(s string) error { - formats := []string{ - "15:04:05", - "15:04:05.999999999Z07:00", - "3:04:05PM", - "3:04 PM", - "15:04", - time.Kitchen, - } - - parsedTime, err := parseTimeWithFormats(s, formats) - if err != nil { - return fmt.Errorf("unable to parse time: %v", err) - } - - *t = TimeValue(parsedTime.Format("15:04:05")) - return nil -} - -// Allow setting inner fields on other flags (e.g. --foo.baz can set the "baz" -// field on the --foo flag) -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]{} - } - - if settableInnerField, ok := f.value.(SettableInnerField); ok { - settableInnerField.SetInnerField(field, val) - f.hasBeenSet = true - } else { - panic(fmt.Sprintf("Cannot set inner field: %v", f.value)) - } -} - -// 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) - switch flagValReflect.Kind() { - case reflect.Slice: - if flagValReflect.Type().Elem().Kind() != reflect.Map { - return - } - - sliceLen := flagValReflect.Len() - if sliceLen > 0 { - // Check if the last element already has the InnerField - lastElement := flagValReflect.Index(sliceLen - 1).Interface().(map[string]any) - if _, hasInnerField := lastElement[field]; !hasInnerField { - // Last element doesn't have the field, set it - lastElement[field] = val - return - } - } - - // Create a new map and append it to the slice - newMap := map[string]any{field: val} - switch sliceVal := any(c.value).(type) { - case []map[string]any: - c.value = any(append(sliceVal, newMap)).(T) - case []any: - c.value = any(append(sliceVal, newMap)).(T) - } - - case reflect.Map: - mapVal, ok := any(flagVal).(map[string]any) - if !ok || mapVal == nil { - mapVal = map[string]any{field: val} - c.value = any(mapVal).(T) - } else { - mapVal[field] = val - } - } -} diff --git a/internal/requestflag/requestflag_test.go b/internal/requestflag/requestflag_test.go deleted file mode 100644 index 779bd57..0000000 --- a/internal/requestflag/requestflag_test.go +++ /dev/null @@ -1,1227 +0,0 @@ -package requestflag - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/goccy/go-yaml" - "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v3" -) - -func TestDateValueParse(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - want string - wantErr bool - }{ - { - name: "ISO format", - input: "2023-05-15", - want: "2023-05-15", - wantErr: false, - }, - { - name: "US format", - input: "05/15/2023", - want: "2023-05-15", - wantErr: false, - }, - { - name: "Short month format", - input: "May 15, 2023", - want: "2023-05-15", - wantErr: false, - }, - { - name: "Long month format", - input: "January 15, 2023", - want: "2023-01-15", - wantErr: false, - }, - { - name: "British format", - input: "15-Jan-2023", - want: "2023-01-15", - wantErr: false, - }, - { - name: "Invalid format", - input: "not a date", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var d DateValue - err := d.Parse(tt.input) - - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, d.String()) - } - }) - } -} - -func TestDateTimeValueParse(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - wantErr bool - }{ - { - name: "RFC3339", - input: "2023-05-15T14:30:45Z", - wantErr: false, - }, - { - name: "ISO with timezone", - input: "2023-05-15T14:30:45+02:00", - wantErr: false, - }, - { - name: "ISO without timezone", - input: "2023-05-15T14:30:45", - wantErr: false, - }, - { - name: "Space separated", - input: "2023-05-15 14:30:45", - wantErr: false, - }, - { - name: "RFC1123", - input: "Mon, 15 May 2023 14:30:45 GMT", - wantErr: false, - }, - { - name: "RFC822", - input: "15 May 23 14:30 GMT", - wantErr: false, - }, - { - name: "ANSIC", - input: "Mon Jan 2 15:04:05 2006", - wantErr: false, - }, - { - name: "Invalid format", - input: "not a datetime", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var d DateTimeValue - err := d.Parse(tt.input) - - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - - // Parse the string back to ensure it's valid RFC3339 - _, parseErr := time.Parse(time.RFC3339, d.String()) - assert.NoError(t, parseErr) - } - }) - } -} - -func TestTimeValueParse(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - want string - wantErr bool - }{ - { - name: "24-hour format", - input: "14:30:45", - want: "14:30:45", - wantErr: false, - }, - { - name: "12-hour format with seconds", - input: "2:30:45PM", - want: "14:30:45", - wantErr: false, - }, - { - name: "12-hour format without seconds", - input: "2:30 PM", - want: "14:30:00", - wantErr: false, - }, - { - name: "24-hour without seconds", - input: "14:30", - want: "14:30:00", - wantErr: false, - }, - { - name: "Kitchen format", - input: "2:30PM", - want: "14:30:00", - wantErr: false, - }, - { - name: "Invalid format", - input: "not a time", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var tv TimeValue - err := tv.Parse(tt.input) - - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.want, tv.String()) - } - }) - } -} - -func TestRequestParams(t *testing.T) { - t.Parallel() - - t.Run("map body type", func(t *testing.T) { - t.Parallel() - - // Create a mock command with flags - cmd := &cli.Command{ - Name: "test", - } - - // Create string flag with body path - stringFlag := &Flag[string]{ - Name: "string-flag", - Default: "default-string", - BodyPath: "string_field", - value: &cliValue[string]{value: "test-value"}, - hasBeenSet: true, - } - - // Create int flag with header path - intFlag := &Flag[int64]{ - Name: "int-flag", - Default: 42, - HeaderPath: "X-Int-Value", - value: &cliValue[int64]{value: 99}, - hasBeenSet: true, - } - - // Create bool flag with query path - boolFlag := &Flag[bool]{ - Name: "bool-flag", - Default: false, - QueryPath: "include_details", - value: &cliValue[bool]{value: true}, - hasBeenSet: true, - } - - // Create date flag with multiple paths - dateFlag := &Flag[DateValue]{ - Name: "date-flag", - Default: DateValue("2023-01-01"), - BodyPath: "effective_date", - HeaderPath: "X-Effective-Date", - QueryPath: "as_of_date", - value: &cliValue[DateValue]{value: DateValue("2023-05-15")}, - hasBeenSet: true, - } - - // Create flag with no path - noPathFlag := &Flag[string]{ - Name: "no-path-flag", - Default: "no-path", - value: &cliValue[string]{value: "no-path-value"}, - hasBeenSet: true, - } - - // Create unset flag - unsetFlag := &Flag[string]{ - Name: "unset-flag", - Default: "unset", - BodyPath: "should_not_appear", - value: &cliValue[string]{value: "unset-value"}, - hasBeenSet: false, - } - - cmd.Flags = []cli.Flag{stringFlag, intFlag, boolFlag, dateFlag, noPathFlag, unsetFlag} - - // Test the RequestParams function - contents := ExtractRequestContents(cmd) - - // Verify query parameters - assert.Equal(t, true, contents.Queries["include_details"]) - assert.Equal(t, DateValue("2023-05-15"), contents.Queries["as_of_date"]) - assert.Len(t, contents.Queries, 2) - - // Verify headers - assert.Equal(t, int64(99), contents.Headers["X-Int-Value"]) - assert.Equal(t, DateValue("2023-05-15"), contents.Headers["X-Effective-Date"]) - assert.Len(t, contents.Headers, 2) - - // Verify body - bodyMap, ok := contents.Body.(map[string]any) - assert.True(t, ok, "Expected body to be map[string]any, got %T", contents.Body) - assert.Equal(t, "test-value", bodyMap["string_field"]) - assert.Equal(t, DateValue("2023-05-15"), bodyMap["effective_date"]) - assert.Len(t, bodyMap, 2) - - // Verify the unset flag didn't make it into the maps - assert.NotContains(t, contents.Body, "should_not_appear") - }) - - t.Run("non-map body type", func(t *testing.T) { - t.Parallel() - - // Create a mock command with flags - cmd := &cli.Command{ - Name: "test", - Flags: []cli.Flag{ - &Flag[int64]{ - Name: "int-body-flag", - Default: 0, - BodyRoot: true, - }, - }, - } - cmd.Set("int-body-flag", "42") - - contents := ExtractRequestContents(cmd) - intBody, ok := contents.Body.(int64) - assert.True(t, ok, "Expected body to be int64, got %T", contents.Body) - assert.Equal(t, int64(42), intBody) - }) -} - -func TestFlagSet(t *testing.T) { - t.Parallel() - - strFlag := &Flag[string]{ - Name: "string-flag", - Default: "default-string", - } - - superstitiousIntFlag := &Flag[int64]{ - Name: "int-flag", - Default: 42, - Validator: func(val int64) error { - if val == 13 { - return fmt.Errorf("Unlucky number!") - } - return nil - }, - } - - boolFlag := &Flag[bool]{ - Name: "bool-flag", - Default: false, - } - - // 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{}, - } - - // Initialize the flag - assert.NoError(t, sliceFlag.PreParse()) - - // First set - assert.NoError(t, sliceFlag.Set("slice-flag", "10")) - - // Subsequent setting should append, not replace - assert.NoError(t, sliceFlag.Set("slice-flag", "20")) - assert.NoError(t, sliceFlag.Set("slice-flag", "30")) - - // Verify that we have both values in the slice - result := sliceFlag.Get() - assert.Equal(t, []int64{10, 20, 30}, result) - assert.True(t, sliceFlag.IsSet()) - }) - - t.Run("Set slice flag with a nonempty default", func(t *testing.T) { - t.Parallel() - - sliceFlag := &Flag[[]int64]{ - Name: "slice-flag", - Default: []int64{99, 100}, - } - - assert.NoError(t, sliceFlag.PreParse()) - assert.NoError(t, sliceFlag.Set("slice-flag", "10")) - assert.NoError(t, sliceFlag.Set("slice-flag", "20")) - assert.NoError(t, sliceFlag.Set("slice-flag", "30")) - - // Verify that we have clobbered the default value instead of appending - // to it. - result := sliceFlag.Get() - assert.Equal(t, []int64{10, 20, 30}, result) - assert.True(t, sliceFlag.IsSet()) - }) -} - -func TestParseTimeWithFormats(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - input string - formats []string - wantTime time.Time - wantErr bool - }{ - { - name: "RFC3339 format", - input: "2023-05-15T14:30:45Z", - formats: []string{time.RFC3339}, - wantTime: time.Date(2023, 5, 15, 14, 30, 45, 0, time.UTC), - wantErr: false, - }, - { - name: "Multiple formats - first matches", - input: "2023-05-15", - formats: []string{"2006-01-02", time.RFC3339}, - wantTime: time.Date(2023, 5, 15, 0, 0, 0, 0, time.UTC), - wantErr: false, - }, - { - name: "Multiple formats - second matches", - input: "15/05/2023", - formats: []string{"2006-01-02", "02/01/2006"}, - wantTime: time.Date(2023, 5, 15, 0, 0, 0, 0, time.UTC), - wantErr: false, - }, - { - name: "No matching format", - input: "not a date", - formats: []string{"2006-01-02", time.RFC3339}, - wantTime: time.Time{}, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - got, err := parseTimeWithFormats(tt.input, tt.formats) - - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.True(t, tt.wantTime.Equal(got), "Expected %v, got %v", tt.wantTime, got) - } - }) - } -} - -func TestYamlHandling(t *testing.T) { - t.Parallel() - - // Test with any value - t.Run("Parse YAML to any", func(t *testing.T) { - t.Parallel() - - cv := &cliValue[any]{} - err := cv.Set("name: test\nvalue: 42\n") - assert.NoError(t, err) - - // The value should be a map - val, ok := cv.Get().(map[string]any) - assert.True(t, ok, "Expected map[string]any, got %T", cv.Get()) - - if ok { - assert.Equal(t, "test", val["name"]) - assert.Equal(t, uint64(42), val["value"]) - } - - // The string representation should be valid YAML - strVal := cv.String() - var parsed map[string]any - err = yaml.Unmarshal([]byte(strVal), &parsed) - assert.NoError(t, err) - assert.Equal(t, "test", parsed["name"]) - assert.Equal(t, uint64(42), parsed["value"]) - }) - - // Test with array - t.Run("Parse YAML array", func(t *testing.T) { - t.Parallel() - - cv := &cliValue[any]{} - err := cv.Set("- item1\n- item2\n- item3\n") - assert.NoError(t, err) - - // The value should be a slice - val, ok := cv.Get().([]any) - assert.True(t, ok, "Expected []any, got %T", cv.Get()) - - if ok { - assert.Len(t, val, 3) - assert.Equal(t, "item1", val[0]) - assert.Equal(t, "item2", val[1]) - assert.Equal(t, "item3", val[2]) - } - }) - - t.Run("Parse @file.txt as YAML", func(t *testing.T) { - t.Parallel() - - flag := &Flag[any]{ - Name: "file-flag", - Default: nil, - } - assert.NoError(t, flag.PreParse()) - assert.NoError(t, flag.Set("file-flag", "@file.txt")) - - val := flag.Get() - assert.Equal(t, "@file.txt", val) - }) - - t.Run("Parse @file.txt list as YAML", func(t *testing.T) { - t.Parallel() - - flag := &Flag[[]any]{ - Name: "file-flag", - Default: nil, - } - assert.NoError(t, flag.PreParse()) - assert.NoError(t, flag.Set("file-flag", "@file1.txt")) - assert.NoError(t, flag.Set("file-flag", "@file2.txt")) - - val := flag.Get() - assert.Equal(t, []any{"@file1.txt", "@file2.txt"}, val) - }) - - t.Run("Parse identifiers as YAML", func(t *testing.T) { - t.Parallel() - - tests := []string{ - "hello", - "e4e355fa-b03b-4c57-a73d-25c9733eec79", - "foo_bar", - "Color.Red", - "aGVsbG8=", - } - for _, test := range tests { - flag := &Flag[any]{ - Name: "flag", - Default: nil, - } - assert.NoError(t, flag.PreParse()) - assert.NoError(t, flag.Set("flag", test)) - - val := flag.Get() - assert.Equal(t, test, val) - } - - for _, test := range tests { - flag := &Flag[[]any]{ - Name: "identifier", - Default: nil, - } - assert.NoError(t, flag.PreParse()) - assert.NoError(t, flag.Set("identifier", test)) - assert.NoError(t, flag.Set("identifier", test)) - - val := flag.Get() - assert.Equal(t, []any{test, test}, val) - } - }) - - // Test with invalid YAML - t.Run("Parse invalid YAML", func(t *testing.T) { - t.Parallel() - - invalidYaml := `[not closed` - cv := &cliValue[any]{} - err := cv.Set(invalidYaml) - assert.Error(t, err) - }) -} - -// 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() - - tests := []struct { - name string - flag cli.DocGenerationFlag - expected string - }{ - {"string", &Flag[string]{}, "string"}, - {"int64", &Flag[int64]{}, "int"}, - {"float64", &Flag[float64]{}, "float"}, - {"bool", &Flag[bool]{}, "boolean"}, - {"string slice", &Flag[[]string]{}, "string"}, - {"date", &Flag[DateValue]{}, "date"}, - {"datetime", &Flag[DateTimeValue]{}, "datetime"}, - {"time", &Flag[TimeValue]{}, "time"}, - {"date slice", &Flag[[]DateValue]{}, "date"}, - {"datetime slice", &Flag[[]DateTimeValue]{}, "datetime"}, - {"time slice", &Flag[[]TimeValue]{}, "time"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - typeName := tt.flag.TypeName() - assert.Equal(t, tt.expected, typeName, "Expected type name %q, got %q", tt.expected, typeName) - }) - } -} - -// 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/openapi.augmented.json b/openapi.augmented.json new file mode 100644 index 0000000..bf27b3f --- /dev/null +++ b/openapi.augmented.json @@ -0,0 +1,12227 @@ +{ + "components": { + "schemas": { + "ArtifactListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ArtifactResponse" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "ArtifactRef": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "artifact_id", + "name" + ], + "type": "object" + }, + "ArtifactResponse": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "download_url": { + "type": "string" + }, + "execution_id": { + "type": "string" + }, + "expires_at": { + "format": "date-time", + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "mime_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "sha256": { + "type": "string" + }, + "size_bytes": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "artifact_id", + "machine_id", + "name", + "size_bytes", + "created_at" + ], + "type": "object" + }, + "BustCreditCacheOutputBody": { + "additionalProperties": false, + "properties": { + "busted": { + "type": "boolean" + } + }, + "required": [ + "busted" + ], + "type": "object" + }, + "CreateExecutionRequest": { + "additionalProperties": false, + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "cwd": { + "type": "string" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "stdin": { + "type": "string" + }, + "timeout_ms": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "command" + ], + "type": "object" + }, + "CreateMachineRequest": { + "additionalProperties": false, + "properties": { + "autosleep": { + "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", + "type": "string" + }, + "memory_mib": { + "description": "Memory in MiB.", + "format": "int64", + "type": "integer" + }, + "storage_gib": { + "description": "Storage in GiB.", + "format": "int64", + "type": "integer" + }, + "vcpu": { + "description": "CPU in vCPUs.", + "format": "double", + "type": "number" + } + }, + "required": [ + "vcpu", + "memory_mib", + "storage_gib" + ], + "type": "object" + }, + "CreatePreviewRequest": { + "additionalProperties": false, + "properties": { + "port": { + "format": "int64", + "type": "integer" + }, + "protocol": { + "enum": [ + "http", + "https" + ], + "type": "string" + }, + "visibility": { + "enum": [ + "public", + "private", + "org" + ], + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "CreateSSHSessionRequest": { + "additionalProperties": false, + "properties": { + "public_key": { + "type": "string" + } + }, + "required": [ + "public_key" + ], + "type": "object" + }, + "CreateTerminalRequest": { + "additionalProperties": false, + "properties": { + "cwd": { + "type": "string" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "height": { + "format": "int64", + "type": "integer" + }, + "shell": { + "type": "string" + }, + "width": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "width", + "height" + ], + "type": "object" + }, + "ErrorDetail": { + "additionalProperties": false, + "properties": { + "location": { + "description": "Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'", + "type": "string" + }, + "message": { + "description": "Error message text", + "type": "string" + }, + "value": { + "description": "The value at the given location" + } + }, + "type": "object" + }, + "ErrorModel": { + "additionalProperties": false, + "properties": { + "detail": { + "description": "A human-readable explanation specific to this occurrence of the problem.", + "examples": [ + "Property foo is required but is missing." + ], + "type": "string" + }, + "errors": { + "description": "Optional list of individual error details", + "items": { + "$ref": "#/components/schemas/ErrorDetail" + }, + "type": [ + "array", + "null" + ] + }, + "instance": { + "description": "A URI reference that identifies the specific occurrence of the problem.", + "examples": [ + "https://example.com/error-log/abc123" + ], + "format": "uri", + "type": "string" + }, + "status": { + "description": "HTTP status code", + "examples": [ + 400 + ], + "format": "int64", + "type": "integer" + }, + "title": { + "description": "A short, human-readable summary of the problem type. This value should not change between occurrences of the error.", + "examples": [ + "Bad Request" + ], + "type": "string" + }, + "type": { + "default": "about:blank", + "description": "A URI reference to human-readable documentation for the error.", + "examples": [ + "https://example.com/errors/example" + ], + "format": "uri", + "type": "string" + } + }, + "type": "object" + }, + "ExecutionEvent": { + "additionalProperties": false, + "properties": { + "at": { + "format": "date-time", + "type": "string" + }, + "chunk": { + "type": "string" + }, + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "exit_code": { + "format": "int64", + "type": "integer" + }, + "sequence": { + "format": "int64", + "type": "integer" + }, + "signal": { + "format": "int64", + "type": "integer" + }, + "status": { + "enum": [ + "wake_in_progress", + "queued", + "running", + "succeeded", + "failed", + "cancelled", + "expired" + ], + "type": "string" + }, + "type": { + "enum": [ + "lifecycle", + "stdout", + "stderr" + ], + "type": "string" + } + }, + "required": [ + "sequence", + "type", + "at" + ], + "type": "object" + }, + "ExecutionEventsResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ExecutionEvent" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "ExecutionListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ExecutionResponse" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "ExecutionOutputResponse": { + "additionalProperties": false, + "properties": { + "execution_id": { + "type": "string" + }, + "stderr": { + "type": "string" + }, + "stderr_bytes": { + "format": "int64", + "type": "integer" + }, + "stderr_truncated": { + "type": "boolean" + }, + "stdout": { + "type": "string" + }, + "stdout_bytes": { + "format": "int64", + "type": "integer" + }, + "stdout_truncated": { + "type": "boolean" + } + }, + "required": [ + "execution_id" + ], + "type": "object" + }, + "ExecutionResponse": { + "additionalProperties": false, + "properties": { + "artifacts": { + "items": { + "$ref": "#/components/schemas/ArtifactRef" + }, + "type": [ + "array", + "null" + ] + }, + "command": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "completed_at": { + "format": "date-time", + "type": "string" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "cwd": { + "type": "string" + }, + "env_keys": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "execution_id": { + "type": "string" + }, + "exit_code": { + "format": "int64", + "type": "integer" + }, + "expires_at": { + "format": "date-time", + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "signal": { + "format": "int64", + "type": "integer" + }, + "started_at": { + "format": "date-time", + "type": "string" + }, + "status": { + "enum": [ + "wake_in_progress", + "queued", + "running", + "succeeded", + "failed", + "cancelled", + "expired" + ], + "type": "string" + }, + "stderr_bytes": { + "format": "int64", + "type": "integer" + }, + "stderr_truncated": { + "type": "boolean" + }, + "stdout_bytes": { + "format": "int64", + "type": "integer" + }, + "stdout_truncated": { + "type": "boolean" + } + }, + "required": [ + "execution_id", + "machine_id", + "status", + "command", + "created_at" + ], + "type": "object" + }, + "HostAgentRolloutOutputBody": { + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "source_nodes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "state": { + "type": "string" + }, + "warm_pool": { + "type": "string" + } + }, + "required": [ + "request_id", + "reason", + "source_nodes", + "state", + "warm_pool" + ], + "type": "object" + }, + "LifecycleResponse": { + "additionalProperties": false, + "properties": { + "autosleep_seconds": { + "description": "Seconds of inactivity before autosleep. 0 disables autosleep.", + "format": "int64", + "maximum": 9223372036, + "minimum": 0, + "type": "integer" + }, + "desired_state": { + "enum": [ + "running", + "sleeping", + "destroyed" + ], + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "memory_mib": { + "description": "Memory in MiB.", + "format": "int64", + "type": "integer" + }, + "status": { + "$ref": "#/components/schemas/LifecycleStatus" + }, + "storage_gib": { + "format": "int64", + "type": "integer" + }, + "vcpu": { + "description": "CPU in vCPUs.", + "format": "double", + "type": "number" + } + }, + "required": [ + "machine_id", + "vcpu", + "memory_mib", + "storage_gib", + "autosleep_seconds", + "desired_state", + "status" + ], + "type": "object" + }, + "LifecycleStatus": { + "additionalProperties": false, + "properties": { + "last_error": { + "type": "string" + }, + "last_progress_at": { + "format": "date-time", + "type": "string" + }, + "last_transition_at": { + "format": "date-time", + "type": "string" + }, + "phase": { + "enum": [ + "accepted", + "placement_pending", + "starting", + "running", + "stopping", + "sleeping", + "destroying", + "destroyed", + "failed" + ], + "type": "string" + }, + "reason": { + "type": "string" + }, + "retryable": { + "type": "boolean" + }, + "revision": { + "type": "string" + } + }, + "required": [ + "phase", + "reason", + "retryable", + "revision", + "last_transition_at", + "last_progress_at" + ], + "type": "object" + }, + "MachineComputeUsageBody": { + "additionalProperties": false, + "properties": { + "granularity": { + "description": "Usage breakdown granularity used for rows: hour or day.", + "type": "string" + }, + "period_end": { + "description": "Exclusive usage period end.", + "format": "date-time", + "type": "string" + }, + "period_start": { + "description": "Inclusive usage period start.", + "format": "date-time", + "type": "string" + }, + "rows": { + "description": "Machine-level compute usage breakdown rows.", + "items": { + "$ref": "#/components/schemas/MachineComputeUsageRowBody" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "granularity", + "period_start", + "period_end", + "rows" + ], + "type": "object" + }, + "MachineComputeUsageRowBody": { + "additionalProperties": false, + "properties": { + "awake_seconds": { + "description": "Machine-awake seconds in this bucket.", + "format": "int64", + "type": "integer" + }, + "bucket_end": { + "description": "Exclusive usage bucket end.", + "format": "date-time", + "type": "string" + }, + "bucket_start": { + "description": "Inclusive usage bucket start.", + "format": "date-time", + "type": "string" + }, + "cpu_millicore_seconds": { + "description": "Requested vCPU millicores multiplied by guest-owned active CPU seconds.", + "format": "int64", + "type": "integer" + }, + "last_window_end": { + "description": "Latest raw window_end represented by this row.", + "format": "date-time", + "type": "string" + }, + "latest_stripe_emitted_at": { + "description": "Latest Stripe emission timestamp for linked org buckets, when emitted.", + "format": "date-time", + "type": "string" + }, + "machine_id": { + "description": "Machine identifier.", + "type": "string" + }, + "memory_mib_seconds": { + "description": "Requested memory MiB multiplied by running allocation seconds.", + "format": "int64", + "type": "integer" + }, + "org_metering_bucket_ids": { + "description": "Org compute bucket IDs this row contributes to.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "requested_memory_mib": { + "description": "Requested memory for this shape, in MiB.", + "format": "int32", + "type": "integer" + }, + "requested_storage_gib": { + "description": "Requested storage for this shape, in GiB.", + "format": "int32", + "type": "integer" + }, + "requested_vcpu": { + "description": "Requested vCPU for this shape.", + "format": "double", + "type": "number" + }, + "spec_fingerprint": { + "description": "Stable fingerprint for the requested machine shape.", + "type": "string" + }, + "stripe_cpu_identifiers": { + "description": "Stripe CPU meter event identifiers linked to those org buckets.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "stripe_memory_identifiers": { + "description": "Stripe memory meter event identifiers linked to those org buckets.", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "window_count": { + "description": "Raw usage windows compacted into this row.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "machine_id", + "spec_fingerprint", + "requested_vcpu", + "requested_memory_mib", + "requested_storage_gib", + "bucket_start", + "bucket_end", + "awake_seconds", + "cpu_millicore_seconds", + "memory_mib_seconds", + "window_count", + "last_window_end", + "org_metering_bucket_ids", + "stripe_cpu_identifiers", + "stripe_memory_identifiers" + ], + "type": "object" + }, + "MachineIDPathSegment": { + "maxLength": 253, + "minLength": 4, + "pattern": "^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, + "MachineListItem": { + "additionalProperties": false, + "properties": { + "autosleep_seconds": { + "description": "Seconds of inactivity before autosleep. 0 disables autosleep.", + "format": "int64", + "maximum": 9223372036, + "minimum": 0, + "type": "integer" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "desired_state": { + "enum": [ + "running", + "sleeping", + "destroyed" + ], + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "memory_mib": { + "description": "Memory in MiB.", + "format": "int64", + "type": "integer" + }, + "status": { + "$ref": "#/components/schemas/LifecycleStatus" + }, + "storage_gib": { + "format": "int64", + "type": "integer" + }, + "vcpu": { + "description": "CPU in vCPUs.", + "format": "double", + "type": "number" + } + }, + "required": [ + "machine_id", + "vcpu", + "memory_mib", + "storage_gib", + "autosleep_seconds", + "desired_state", + "status", + "created_at" + ], + "type": "object" + }, + "MachineListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/MachineListItem" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "MachineStorageUsageBody": { + "additionalProperties": false, + "properties": { + "period_end": { + "description": "Exclusive usage period end.", + "format": "date-time", + "type": "string" + }, + "period_start": { + "description": "Inclusive usage period start.", + "format": "date-time", + "type": "string" + }, + "rows": { + "description": "Machine-level storage usage breakdown rows.", + "items": { + "$ref": "#/components/schemas/MachineStorageUsageRowBody" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "period_start", + "period_end", + "rows" + ], + "type": "object" + }, + "MachineStorageUsageRowBody": { + "additionalProperties": false, + "properties": { + "bucket_end": { + "description": "Exclusive usage bucket end.", + "format": "date-time", + "type": "string" + }, + "bucket_start": { + "description": "Inclusive usage bucket start.", + "format": "date-time", + "type": "string" + }, + "latest_stripe_emitted_at": { + "description": "Latest Stripe emission timestamp for the linked org bucket, when emitted.", + "format": "date-time", + "type": "string" + }, + "logical_storage_bytes": { + "description": "Machine logical bytes observed for storage allocation.", + "format": "int64", + "type": "integer" + }, + "machine_id": { + "description": "Machine identifier.", + "type": "string" + }, + "org_metering_bucket_id": { + "description": "Org storage bucket ID this row contributes to.", + "type": "string" + }, + "storage_mib_seconds": { + "description": "Allocated logical MiB-seconds for this machine.", + "format": "int64", + "type": "integer" + }, + "stripe_storage_identifier": { + "description": "Stripe storage meter event identifier linked to that org bucket.", + "type": "string" + } + }, + "required": [ + "machine_id", + "bucket_start", + "bucket_end", + "logical_storage_bytes", + "storage_mib_seconds", + "org_metering_bucket_id", + "stripe_storage_identifier" + ], + "type": "object" + }, + "MeteringClosedThroughBody": { + "additionalProperties": false, + "properties": { + "compute_cpu": { + "description": "Latest CPU org bucket end.", + "format": "date-time", + "type": "string" + }, + "compute_memory": { + "description": "Latest memory org bucket end.", + "format": "date-time", + "type": "string" + }, + "storage": { + "description": "Latest storage org bucket end.", + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "MeteringHealthBody": { + "additionalProperties": false, + "properties": { + "clickhouse_latest_raw_event_at": { + "description": "Latest raw ClickHouse usage event timestamp.", + "format": "date-time", + "type": "string" + }, + "clickhouse_oldest_raw_event_at": { + "description": "Oldest raw ClickHouse usage event timestamp still retained.", + "format": "date-time", + "type": "string" + }, + "clickhouse_raw_rows_near_ttl": { + "description": "Raw ClickHouse rows within 24 hours of the 30-day TTL.", + "format": "int64", + "type": "integer" + }, + "closed_through": { + "$ref": "#/components/schemas/MeteringClosedThroughBody", + "description": "Latest closed org bucket cursor per resource." + }, + "generated_at": { + "description": "Time this health snapshot was generated.", + "format": "date-time", + "type": "string" + }, + "oldest_logical_storage_observed_at": { + "description": "Oldest logical storage observation timestamp across current machine specs.", + "format": "date-time", + "type": "string" + }, + "stale_logical_storage_gauge_count": { + "description": "Machine storage gauges missing or older than the freshness window.", + "format": "int64", + "type": "integer" + }, + "stripe_outbox": { + "description": "Pending Stripe outbox buckets grouped by resource.", + "items": { + "$ref": "#/components/schemas/MeteringStripeOutboxBody" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "generated_at", + "clickhouse_raw_rows_near_ttl", + "closed_through", + "stripe_outbox", + "stale_logical_storage_gauge_count" + ], + "type": "object" + }, + "MeteringStripeOutboxBody": { + "additionalProperties": false, + "properties": { + "claimed_not_submitted_buckets": { + "description": "Pending org buckets claimed by a sweeper but not marked submitted to Stripe.", + "format": "int64", + "type": "integer" + }, + "oldest_bucket_end": { + "description": "Oldest pending bucket end timestamp.", + "format": "date-time", + "type": "string" + }, + "pending_buckets": { + "description": "Unemitted org bucket count.", + "format": "int64", + "type": "integer" + }, + "pending_unclaimed_buckets": { + "description": "Pending org buckets with no active emission claim.", + "format": "int64", + "type": "integer" + }, + "resource": { + "description": "Org metering bucket resource.", + "type": "string" + }, + "stripe_emission_lag_seconds": { + "description": "Seconds since the oldest pending bucket ended.", + "format": "int64", + "type": "integer" + }, + "submitted_not_emitted_buckets": { + "description": "Pending org buckets marked submitted to Stripe but not marked emitted.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "resource", + "pending_buckets", + "pending_unclaimed_buckets", + "claimed_not_submitted_buckets", + "submitted_not_emitted_buckets", + "stripe_emission_lag_seconds" + ], + "type": "object" + }, + "PreviewListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/PreviewResponse" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "PreviewResponse": { + "additionalProperties": false, + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "expires_at": { + "format": "date-time", + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "port": { + "format": "int64", + "type": "integer" + }, + "preview_id": { + "type": "string" + }, + "protocol": { + "enum": [ + "http", + "https" + ], + "type": "string" + }, + "ready_at": { + "format": "date-time", + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "status": { + "enum": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "type": "string" + }, + "url": { + "type": "string" + }, + "visibility": { + "enum": [ + "public", + "private", + "org" + ], + "type": "string" + } + }, + "required": [ + "preview_id", + "machine_id", + "status", + "port", + "visibility", + "created_at" + ], + "type": "object" + }, + "PublicPathSegment": { + "maxLength": 253, + "minLength": 1, + "pattern": "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$", + "type": "string" + }, + "PurgeMachineOutputBody": { + "additionalProperties": false, + "properties": { + "machine_id": { + "type": "string" + }, + "purge_state": { + "type": "string" + } + }, + "required": [ + "machine_id", + "purge_state" + ], + "type": "object" + }, + "RequestHostAgentRolloutInputBody": { + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + }, + "request_id": { + "type": "string" + }, + "source_nodes": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "required": [ + "reason", + "source_nodes" + ], + "type": "object" + }, + "SSHConnection": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "host_trust": { + "$ref": "#/components/schemas/SSHHostTrust" + }, + "port": { + "format": "int64", + "type": "integer" + }, + "ssh_username": { + "type": "string" + }, + "user_certificate": { + "type": "string" + } + }, + "required": [ + "endpoint", + "port", + "ssh_username" + ], + "type": "object" + }, + "SSHHostTrust": { + "additionalProperties": false, + "properties": { + "host_pattern": { + "type": "string" + }, + "kind": { + "enum": [ + "cert_authority" + ], + "type": "string" + }, + "public_key": { + "type": "string" + } + }, + "required": [ + "kind", + "host_pattern", + "public_key" + ], + "type": "object" + }, + "SSHSessionListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/SSHSessionResponse" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "SSHSessionResponse": { + "additionalProperties": false, + "properties": { + "connection": { + "$ref": "#/components/schemas/SSHConnection" + }, + "created_at": { + "format": "date-time", + "type": "string" + }, + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "expires_at": { + "format": "date-time", + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "ready_at": { + "format": "date-time", + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "session_id": { + "type": "string" + }, + "status": { + "enum": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "type": "string" + } + }, + "required": [ + "session_id", + "machine_id", + "status", + "created_at" + ], + "type": "object" + }, + "TerminalClientEvent": { + "discriminator": { + "mapping": { + "input": "#/components/schemas/TerminalInputEvent", + "resize": "#/components/schemas/TerminalResizeEvent" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TerminalInputEvent" + }, + { + "$ref": "#/components/schemas/TerminalResizeEvent" + } + ] + }, + "TerminalClosedEvent": { + "additionalProperties": false, + "properties": { + "type": { + "enum": [ + "closed" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "TerminalErrorEvent": { + "additionalProperties": false, + "properties": { + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "type": { + "enum": [ + "error" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "TerminalInputEvent": { + "additionalProperties": false, + "properties": { + "data": { + "description": "Base64-encoded terminal input.", + "format": "byte", + "type": "string" + }, + "type": { + "enum": [ + "input" + ], + "type": "string" + } + }, + "required": [ + "type", + "data" + ], + "type": "object" + }, + "TerminalListResponse": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/TerminalResponse" + }, + "type": [ + "array", + "null" + ] + }, + "next_cursor": { + "type": "string" + } + }, + "required": [ + "items" + ], + "type": "object" + }, + "TerminalOutputEvent": { + "additionalProperties": false, + "properties": { + "data": { + "description": "Base64-encoded terminal output.", + "format": "byte", + "type": "string" + }, + "type": { + "enum": [ + "output" + ], + "type": "string" + } + }, + "required": [ + "type", + "data" + ], + "type": "object" + }, + "TerminalResizeEvent": { + "additionalProperties": false, + "properties": { + "height": { + "format": "int64", + "type": "integer" + }, + "type": { + "enum": [ + "resize" + ], + "type": "string" + }, + "width": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "type", + "width", + "height" + ], + "type": "object" + }, + "TerminalResponse": { + "additionalProperties": false, + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "error_code": { + "type": "string" + }, + "error_message": { + "type": "string" + }, + "expires_at": { + "format": "date-time", + "type": "string" + }, + "height": { + "format": "int64", + "type": "integer" + }, + "machine_id": { + "type": "string" + }, + "protocol": { + "enum": [ + "websocket" + ], + "type": "string" + }, + "ready_at": { + "format": "date-time", + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "status": { + "enum": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "type": "string" + }, + "stream_url": { + "type": "string" + }, + "terminal_id": { + "type": "string" + }, + "width": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "terminal_id", + "machine_id", + "status", + "created_at", + "width", + "height" + ], + "type": "object" + }, + "TerminalServerEvent": { + "discriminator": { + "mapping": { + "closed": "#/components/schemas/TerminalClosedEvent", + "error": "#/components/schemas/TerminalErrorEvent", + "output": "#/components/schemas/TerminalOutputEvent" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/TerminalOutputEvent" + }, + { + "$ref": "#/components/schemas/TerminalErrorEvent" + }, + { + "$ref": "#/components/schemas/TerminalClosedEvent" + } + ] + }, + "UpdateMachineRequest": { + "additionalProperties": false, + "properties": { + "autosleep": { + "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", + "type": "string" + }, + "memory_mib": { + "description": "Memory in MiB.", + "format": "int64", + "type": "integer" + }, + "storage_gib": { + "description": "Storage in GiB.", + "format": "int64", + "type": "integer" + }, + "vcpu": { + "description": "CPU in vCPUs.", + "format": "double", + "type": "number" + } + }, + "type": "object" + }, + "UsageBody": { + "additionalProperties": false, + "properties": { + "billed_awake_seconds": { + "description": "Closed awake seconds in billed org buckets for the period.", + "format": "int64", + "type": "integer" + }, + "billed_cpu_millicore_seconds": { + "description": "Closed requested vCPU millicores multiplied by guest-owned active CPU seconds for the period.", + "format": "int64", + "type": "integer" + }, + "billed_logical_storage_mib_seconds": { + "description": "Closed billable logical MiB-seconds for the period, matching the Stripe storage meter.", + "format": "int64", + "type": "integer" + }, + "billed_memory_mib_seconds": { + "description": "Closed requested memory MiB multiplied by running allocation seconds for the period.", + "format": "int64", + "type": "integer" + }, + "included_storage_gib": { + "description": "Plan-included storage in GiB, used as a local guardrail only.", + "format": "int64", + "type": "integer" + }, + "plan_slug": { + "description": "Billing plan in effect for the organization.", + "type": "string" + }, + "provisioned_storage_gib": { + "description": "Current provisioned storage summed across machines in GiB.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "plan_slug", + "billed_awake_seconds", + "billed_cpu_millicore_seconds", + "billed_memory_mib_seconds", + "provisioned_storage_gib", + "included_storage_gib", + "billed_logical_storage_mib_seconds" + ], + "type": "object" + }, + "WakeAdmissionResponse": { + "additionalProperties": false, + "properties": { + "desired_state": { + "enum": [ + "running", + "sleeping", + "destroyed" + ], + "type": "string" + }, + "lifecycle_status": { + "$ref": "#/components/schemas/LifecycleStatus" + }, + "machine_id": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "status": { + "enum": [ + "proxy_ready", + "wake_in_progress" + ], + "type": "string" + }, + "wake_requested": { + "type": "boolean" + } + }, + "required": [ + "machine_id", + "desired_state", + "lifecycle_status", + "status", + "wake_requested" + ], + "type": "object" + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "API key authentication using X-API-Key header" + }, + "BearerAuth": { + "bearerFormat": "Dedalus API key", + "description": "Dedalus API key in Authorization: Bearer .", + "scheme": "bearer", + "type": "http" + }, + "Bearer": { + "type": "http", + "scheme": "bearer", + "description": "API key authentication using Bearer token" + } + } + }, + "info": { + "description": "Controlplane API for Dedalus Cloud Services (DCS).", + "title": "Dedalus Cloud Services API", + "version": "v1", + "x-scalar-sdk-installation": [ + { + "lang": "TypeScript", + "description": "```sh\nnpm install dedalus\n```" + }, + { + "lang": "Python", + "description": "```sh\npip install dedalus_sdk\n```" + }, + { + "lang": "Go", + "description": "```sh\ngo get dedalus-dedalus-cloud-services-api\n```" + } + ] + }, + "openapi": "3.1.0", + "paths": { + "/v1/machines": { + "get": { + "operationId": "listMachines", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MachineListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List machines", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list()\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst list = await client.machineLifecycle.list();\nconsole.log(list);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.List(context.Background(), sdk.MachineLifecycleListParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "post": { + "operationId": "createMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMachineRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Create converged inline", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Create accepted and pending convergence", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "idempotency_key_reused": { + "value": { + "error_code": "IDEMPOTENCY_KEY_REUSED", + "message": "idempotency key reused with different request parameters", + "retryable": false, + "details": { + "expected_request_hash": "9ad5f2ad", + "provided_request_hash": "a2b7e8d4" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Create conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Create machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create(\n memory_mib=0,\n storage_gib=0,\n vcpu=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst create = await client.machineLifecycle.create({\n memory_mib: 0,\n storage_gib: 0,\n vcpu: 0,\n});\nconsole.log(create);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.New(context.Background(), sdk.MachineLifecycleNewParams{\n\t\tCreateMachineRequest: sdk.CreateMachineRequest{\n\t\tMemoryMib: sdk.F[int64](0),\n\t\tStorageGib: sdk.F[int64](0),\n\t\tVcpu: sdk.F[float64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}": { + "delete": { + "operationId": "deleteMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "OK", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Mutation accepted or in-progress", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "conflict": { + "value": { + "error_code": "REVISION_MISMATCH", + "message": "revision mismatch", + "retryable": true, + "details": { + "expected_revision": "43", + "provided_revision": "42" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Revision conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Destroy machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst delete_ = await client.machineLifecycle.delete({\n machine_id: \"machineID\",\n});\nconsole.log(delete_);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Delete(context.Background(), sdk.MachineLifecycleDeleteParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "OK", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieve = await client.machineLifecycle.retrieve({\n machine_id: \"machineID\",\n});\nconsole.log(retrieve);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Get(context.Background(), sdk.MachineLifecycleGetParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "patch": { + "operationId": "patchMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMachineRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "OK", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Mutation accepted or in-progress", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "fence_conflict": { + "value": { + "error_code": "REVISION_MISMATCH", + "message": "revision mismatch", + "retryable": true, + "details": { + "expected_revision": "43", + "provided_revision": "42" + } + } + }, + "invalid_state": { + "value": { + "error_code": "INVALID_STATE", + "message": "requested lifecycle transition is invalid for current machine state", + "retryable": false, + "details": { + "current_desired_state": "destroyed", + "current_observed_state": "destroyed", + "expected_observed_state": "running", + "target_desired_state": "running" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "sleep_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "sleep_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + }, + "sleep_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "sleep_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Update machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.patch(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst patch = await client.machineLifecycle.patch({\n machine_id: \"machineID\",\n});\nconsole.log(patch);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Patch(context.Background(), sdk.MachineLifecyclePatchParams{\n\t\tMachineID: \"machineID\",\n\t\tUpdateMachineRequest: sdk.UpdateMachineRequest{},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/artifacts": { + "get": { + "operationId": "listMachineArtifacts", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List artifacts", + "tags": [ + "Machine Lifecycle", + "Machine Artifacts" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_artifacts(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listArtifacts = await client.machineLifecycle.listArtifacts({\n machine_id: \"machineID\",\n});\nconsole.log(listArtifacts);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListArtifacts(context.Background(), sdk.MachineLifecycleListArtifactsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/artifacts/{artifact_id}": { + "delete": { + "operationId": "deleteMachineArtifact", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "artifact_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Delete artifact", + "tags": [ + "Machine Lifecycle", + "Machine Artifacts" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_artifact(\n machine_id=\"machineID\",\n artifact_id=\"artifactID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteArtifact = await client.machineLifecycle.deleteArtifact({\n machine_id: \"machineID\",\n artifact_id: \"artifactID\",\n});\nconsole.log(deleteArtifact);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteArtifact(context.Background(), sdk.MachineLifecycleDeleteArtifactParams{\n\t\tArtifactID: \"artifactID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachineArtifact", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "artifact_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get artifact", + "tags": [ + "Machine Lifecycle", + "Machine Artifacts" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_artifact(\n machine_id=\"machineID\",\n artifact_id=\"artifactID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveArtifact = await client.machineLifecycle.retrieveArtifact({\n machine_id: \"machineID\",\n artifact_id: \"artifactID\",\n});\nconsole.log(retrieveArtifact);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetArtifact(context.Background(), sdk.MachineLifecycleGetArtifactParams{\n\t\tArtifactID: \"artifactID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/executions": { + "get": { + "operationId": "listMachineExecutions", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List executions", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_executions(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutions = await client.machineLifecycle.listExecutions({\n machine_id: \"machineID\",\n});\nconsole.log(listExecutions);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutions(context.Background(), sdk.MachineLifecycleListExecutionsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "post": { + "operationId": "createMachineExecution", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateExecutionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "idempotency_key_reused": { + "value": { + "error_code": "IDEMPOTENCY_KEY_REUSED", + "message": "idempotency key reused with different request parameters", + "retryable": false, + "details": { + "expected_request_hash": "9ad5f2ad", + "provided_request_hash": "a2b7e8d4" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Create conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Create execution", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_execution(\n machine_id=\"machineID\",\n command=[],\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createExecution = await client.machineLifecycle.createExecution({\n machine_id: \"machineID\",\n command: [],\n});\nconsole.log(createExecution);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewExecution(context.Background(), sdk.MachineLifecycleNewExecutionParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateExecutionRequest: sdk.CreateExecutionRequest{\n\t\tCommand: sdk.F[[]string]([]string{\"\"}),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/executions/{execution_id}": { + "delete": { + "operationId": "deleteMachineExecution", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "execution_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Delete execution", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_execution(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteExecution = await client.machineLifecycle.deleteExecution({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(deleteExecution);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteExecution(context.Background(), sdk.MachineLifecycleDeleteExecutionParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachineExecution", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "execution_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get execution", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_execution(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveExecution = await client.machineLifecycle.retrieveExecution({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(retrieveExecution);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetExecution(context.Background(), sdk.MachineLifecycleGetExecutionParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/executions/{execution_id}/events": { + "get": { + "operationId": "listMachineExecutionEvents", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "execution_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionEventsResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List execution events", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_execution_events(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutionEvents = await client.machineLifecycle.listExecutionEvents({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(listExecutionEvents);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutionEvents(context.Background(), sdk.MachineLifecycleListExecutionEventsParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/executions/{execution_id}/output": { + "get": { + "operationId": "getMachineExecutionOutput", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "execution_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecutionOutputResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get execution output", + "tags": [ + "Machine Lifecycle", + "Machine Executions" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_execution_output(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutionOutput = await client.machineLifecycle.listExecutionOutput({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(listExecutionOutput);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutionOutput(context.Background(), sdk.MachineLifecycleListExecutionOutputParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/previews": { + "get": { + "operationId": "listMachinePreviews", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List previews", + "tags": [ + "Machine Lifecycle", + "Machine Previews" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_previews(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listPreviews = await client.machineLifecycle.listPreviews({\n machine_id: \"machineID\",\n});\nconsole.log(listPreviews);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListPreviews(context.Background(), sdk.MachineLifecycleListPreviewsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "post": { + "operationId": "createMachinePreview", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePreviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "idempotency_key_reused": { + "value": { + "error_code": "IDEMPOTENCY_KEY_REUSED", + "message": "idempotency key reused with different request parameters", + "retryable": false, + "details": { + "expected_request_hash": "9ad5f2ad", + "provided_request_hash": "a2b7e8d4" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Create conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Create preview", + "tags": [ + "Machine Lifecycle", + "Machine Previews" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_preview(\n machine_id=\"machineID\",\n port=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createPreview = await client.machineLifecycle.createPreview({\n machine_id: \"machineID\",\n port: 0,\n});\nconsole.log(createPreview);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewPreview(context.Background(), sdk.MachineLifecycleNewPreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tCreatePreviewRequest: sdk.CreatePreviewRequest{\n\t\tPort: sdk.F[int64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/previews/{preview_id}": { + "delete": { + "operationId": "deleteMachinePreview", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "preview_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Delete preview", + "tags": [ + "Machine Lifecycle", + "Machine Previews" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_preview(\n machine_id=\"machineID\",\n preview_id=\"previewID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deletePreview = await client.machineLifecycle.deletePreview({\n machine_id: \"machineID\",\n preview_id: \"previewID\",\n});\nconsole.log(deletePreview);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeletePreview(context.Background(), sdk.MachineLifecycleDeletePreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tPreviewID: \"previewID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachinePreview", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "preview_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PreviewResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get preview", + "tags": [ + "Machine Lifecycle", + "Machine Previews" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_preview(\n machine_id=\"machineID\",\n preview_id=\"previewID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrievePreview = await client.machineLifecycle.retrievePreview({\n machine_id: \"machineID\",\n preview_id: \"previewID\",\n});\nconsole.log(retrievePreview);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetPreview(context.Background(), sdk.MachineLifecycleGetPreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tPreviewID: \"previewID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/sleep": { + "post": { + "operationId": "sleepMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "OK", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Mutation accepted or in-progress", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "fence_conflict": { + "value": { + "error_code": "REVISION_MISMATCH", + "message": "revision mismatch", + "retryable": true, + "details": { + "expected_revision": "43", + "provided_revision": "42" + } + } + }, + "invalid_state": { + "value": { + "error_code": "INVALID_STATE", + "message": "requested lifecycle transition is invalid for current machine state", + "retryable": false, + "details": { + "current_desired_state": "running", + "current_observed_state": "error", + "expected_observed_state": "running", + "target_desired_state": "sleeping" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "sleep_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "sleep_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + }, + "sleep_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "sleep_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Sleep a running machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.sleep(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst sleep = await client.machineLifecycle.sleep({\n machine_id: \"machineID\",\n});\nconsole.log(sleep);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Sleep(context.Background(), sdk.MachineLifecycleSleepParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/ssh": { + "get": { + "operationId": "listMachineSSHSessions", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SSHSessionListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List SSH sessions", + "tags": [ + "Machine Lifecycle", + "Machine SSH" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_ssh_sessions(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listSSHSessions = await client.machineLifecycle.listSSHSessions({\n machine_id: \"machineID\",\n});\nconsole.log(listSSHSessions);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListSSHSessions(context.Background(), sdk.MachineLifecycleListSSHSessionsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "post": { + "operationId": "createMachineSSHSession", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateSSHSessionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SSHSessionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "idempotency_key_reused": { + "value": { + "error_code": "IDEMPOTENCY_KEY_REUSED", + "message": "idempotency key reused with different request parameters", + "retryable": false, + "details": { + "expected_request_hash": "9ad5f2ad", + "provided_request_hash": "a2b7e8d4" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Create conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Create SSH session", + "tags": [ + "Machine Lifecycle", + "Machine SSH" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_ssh_session(\n machine_id=\"machineID\",\n public_key=\"\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createSSHSession = await client.machineLifecycle.createSSHSession({\n machine_id: \"machineID\",\n public_key: \"\",\n});\nconsole.log(createSSHSession);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewSSHSession(context.Background(), sdk.MachineLifecycleNewSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateSSHSessionRequest: sdk.CreateSSHSessionRequest{\n\t\tPublicKey: sdk.F[string](\"\"),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/ssh/{session_id}": { + "delete": { + "operationId": "deleteMachineSSHSession", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "session_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SSHSessionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Delete SSH session", + "tags": [ + "Machine Lifecycle", + "Machine SSH" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_ssh_session(\n machine_id=\"machineID\",\n session_id=\"sessionID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteSSHSession = await client.machineLifecycle.deleteSSHSession({\n machine_id: \"machineID\",\n session_id: \"sessionID\",\n});\nconsole.log(deleteSSHSession);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteSSHSession(context.Background(), sdk.MachineLifecycleDeleteSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tSessionID: \"sessionID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachineSSHSession", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "session_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SSHSessionResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get SSH session", + "tags": [ + "Machine Lifecycle", + "Machine SSH" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_ssh_session(\n machine_id=\"machineID\",\n session_id=\"sessionID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveSSHSession = await client.machineLifecycle.retrieveSSHSession({\n machine_id: \"machineID\",\n session_id: \"sessionID\",\n});\nconsole.log(retrieveSSHSession);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetSSHSession(context.Background(), sdk.MachineLifecycleGetSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tSessionID: \"sessionID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/status/stream": { + "get": { + "description": "Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state.", + "operationId": "watchMachineStatus", + "parameters": [ + { + "description": "Organization ID header applied to all DCS requests.", + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Machine identifier.", + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "description": "Optional resourceVersion bookmark used to resume a previous stream.", + "in": "header", + "name": "Last-Event-ID", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "text/event-stream": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Server-Sent Event stream (`text/event-stream`) of machine lifecycle updates." + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + } + }, + "summary": "Watch machine lifecycle status", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nstream = client.machine_lifecycle.watch_status(\n machine_id=\"machineID\",\n)\nfor event in stream:\n print(event)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst stream = await client.machineLifecycle.watchStatus({\n machine_id: \"machineID\",\n});\nfor await (const event of stream) {\n console.log(event);\n}" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tstream := client.MachineLifecycle.WatchStatusStreaming(context.Background(), sdk.MachineLifecycleWatchStatusParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tdefer stream.Close()\n\tfor stream.Next() {\n\t\tevent := stream.Current()\n\t\tfmt.Println(event)\n\t}\n\tif err := stream.Err(); err != nil {\n\t\tpanic(err)\n\t}\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/terminals": { + "get": { + "operationId": "listMachineTerminals", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "explode": false, + "in": "query", + "name": "limit", + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "explode": false, + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TerminalListResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List terminals", + "tags": [ + "Machine Lifecycle", + "Machine Terminals" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_terminals(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listTerminals = await client.machineLifecycle.listTerminals({\n machine_id: \"machineID\",\n});\nconsole.log(listTerminals);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListTerminals(context.Background(), sdk.MachineLifecycleListTerminalsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "post": { + "operationId": "createMachineTerminal", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTerminalRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TerminalResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "idempotency_key_reused": { + "value": { + "error_code": "IDEMPOTENCY_KEY_REUSED", + "message": "idempotency key reused with different request parameters", + "retryable": false, + "details": { + "expected_request_hash": "9ad5f2ad", + "provided_request_hash": "a2b7e8d4" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Create conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Create terminal", + "tags": [ + "Machine Lifecycle", + "Machine Terminals" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_terminal(\n machine_id=\"machineID\",\n height=0,\n width=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createTerminal = await client.machineLifecycle.createTerminal({\n machine_id: \"machineID\",\n height: 0,\n width: 0,\n});\nconsole.log(createTerminal);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewTerminal(context.Background(), sdk.MachineLifecycleNewTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateTerminalRequest: sdk.CreateTerminalRequest{\n\t\tHeight: sdk.F[int64](0),\n\t\tWidth: sdk.F[int64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/terminals/{terminal_id}": { + "delete": { + "operationId": "deleteMachineTerminal", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "terminal_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TerminalResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Delete terminal", + "tags": [ + "Machine Lifecycle", + "Machine Terminals" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_terminal(\n machine_id=\"machineID\",\n terminal_id=\"terminalID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteTerminal = await client.machineLifecycle.deleteTerminal({\n machine_id: \"machineID\",\n terminal_id: \"terminalID\",\n});\nconsole.log(deleteTerminal);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteTerminal(context.Background(), sdk.MachineLifecycleDeleteTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tTerminalID: \"terminalID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + }, + "get": { + "operationId": "getMachineTerminal", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "path", + "name": "terminal_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TerminalResponse" + } + } + }, + "description": "OK" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get terminal", + "tags": [ + "Machine Lifecycle", + "Machine Terminals" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_terminal(\n machine_id=\"machineID\",\n terminal_id=\"terminalID\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveTerminal = await client.machineLifecycle.retrieveTerminal({\n machine_id: \"machineID\",\n terminal_id: \"terminalID\",\n});\nconsole.log(retrieveTerminal);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetTerminal(context.Background(), sdk.MachineLifecycleGetTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tTerminalID: \"terminalID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/machines/{machine_id}/terminals/{terminal_id}/stream": { + "get": { + "description": "Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields.", + "operationId": "connectMachineTerminal", + "parameters": [ + { + "description": "Organization ID header applied to all DCS requests.", + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "description": "Machine identifier.", + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "description": "Terminal identifier.", + "in": "path", + "name": "terminal_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "101": { + "description": "Switching Protocols to WebSocket" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "terminal_closed": { + "value": { + "error_code": "INVALID_STATE", + "message": "terminal is not ready for streaming", + "retryable": false, + "details": { + "current_status": "closed", + "expected_status": "ready" + } + } + }, + "terminal_not_ready": { + "value": { + "error_code": "INVALID_STATE", + "message": "terminal is not ready for streaming", + "retryable": true, + "retry_after_ms": 1000, + "details": { + "current_status": "wake_in_progress", + "expected_status": "ready" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Terminal is not ready for streaming" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "read_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "read_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + } + }, + "summary": "Connect to terminal WebSocket stream", + "tags": [ + "Machine Lifecycle", + "Machine Terminals" + ] + } + }, + "/v1/machines/{machine_id}/wake": { + "post": { + "operationId": "wakeMachine", + "parameters": [ + { + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + }, + { + "in": "header", + "name": "Idempotency-Key", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "OK", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "202": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LifecycleResponse" + } + } + }, + "description": "Mutation accepted or in-progress", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + }, + "X-Dedalus-Storage-Operation-Id": { + "schema": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "examples": { + "fence_conflict": { + "value": { + "error_code": "REVISION_MISMATCH", + "message": "revision mismatch", + "retryable": true, + "details": { + "expected_revision": "43", + "provided_revision": "42" + } + } + }, + "invalid_state": { + "value": { + "error_code": "INVALID_STATE", + "message": "requested lifecycle transition is invalid for current machine state", + "retryable": false, + "details": { + "current_desired_state": "sleeping", + "current_observed_state": "error", + "expected_observed_state": "sleeping", + "target_desired_state": "running" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Conflict" + }, + "429": { + "content": { + "application/json": { + "examples": { + "idempotency_in_flight": { + "value": { + "error_code": "RATE_LIMITED", + "message": "idempotency key is already in-flight", + "retryable": true, + "retry_after_ms": 250 + } + }, + "mutation_rate_limited": { + "value": { + "error_code": "RATE_LIMITED", + "message": "mutation rate limit exceeded", + "retryable": true, + "retry_after_ms": 84, + "details": { + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Rate limited" + }, + "503": { + "content": { + "application/json": { + "examples": { + "mutation_limiter_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "mutation rate limiter unavailable", + "retryable": true, + "details": { + "rate_limit_backend": "redis", + "rate_limit_scope": "mutating_lifecycle_routes" + } + } + }, + "runtime_unavailable": { + "value": { + "error_code": "DEPENDENCY_UNAVAILABLE", + "message": "runtime unavailable", + "retryable": true + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Dependency unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Wake a sleeping machine", + "tags": [ + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.wake(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst wake = await client.machineLifecycle.wake({\n machine_id: \"machineID\",\n});\nconsole.log(wake);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Wake(context.Background(), sdk.MachineLifecycleWakeParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + } + ] + } + }, + "/v1/usage": { + "get": { + "operationId": "getUsage", + "parameters": [ + { + "description": "Billing period start (YYYY-MM-DD). Defaults to first of current month.", + "explode": false, + "in": "query", + "name": "period_start", + "schema": { + "description": "Billing period start (YYYY-MM-DD). Defaults to first of current month.", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageBody" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Invalid request" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "500": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Internal server error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Billing ledger unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "Get usage summary", + "tags": [ + "Usage", + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nusage = client.usage.list()\nprint(usage)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst list = await client.usage.list();\nconsole.log(list);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tusage, err := client.Usage.List(context.Background(), sdk.UsageListParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(usage)\n}" + } + ] + } + }, + "/v1/usage/machines/compute": { + "get": { + "operationId": "listMachineComputeUsage", + "parameters": [ + { + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "explode": false, + "in": "query", + "name": "period_start", + "schema": { + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "type": "string" + } + }, + { + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "explode": false, + "in": "query", + "name": "period_end", + "schema": { + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "type": "string" + } + }, + { + "description": "Optional machine ID filter.", + "explode": false, + "in": "query", + "name": "machine_id", + "schema": { + "description": "Optional machine ID filter.", + "type": "string" + } + }, + { + "description": "Usage breakdown granularity: hour or day. Defaults to hour.", + "explode": false, + "in": "query", + "name": "granularity", + "schema": { + "description": "Usage breakdown granularity: hour or day. Defaults to hour.", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MachineComputeUsageBody" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Invalid request" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "500": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Internal server error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Billing ledger unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List machine compute usage breakdown", + "tags": [ + "Usage", + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine = client.usage.machines.list_compute_usage()\nprint(machine)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listComputeUsage = await client.usage.machines.listComputeUsage();\nconsole.log(listComputeUsage);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachine, err := client.Usage.Machines.ListComputeUsage(context.Background(), sdk.UsageMachineListComputeUsageParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machine)\n}" + } + ] + } + }, + "/v1/usage/machines/storage": { + "get": { + "operationId": "listMachineStorageUsage", + "parameters": [ + { + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "explode": false, + "in": "query", + "name": "period_start", + "schema": { + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "type": "string" + } + }, + { + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "explode": false, + "in": "query", + "name": "period_end", + "schema": { + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "type": "string" + } + }, + { + "description": "Optional machine ID filter.", + "explode": false, + "in": "query", + "name": "machine_id", + "schema": { + "description": "Optional machine ID filter.", + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MachineStorageUsageBody" + } + } + }, + "description": "OK" + }, + "400": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Invalid request" + }, + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "internal service authorization is required", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "500": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Internal server error" + }, + "502": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Billing ledger unavailable" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" + } + }, + "summary": "List machine storage usage breakdown", + "tags": [ + "Usage", + "Machine Lifecycle" + ], + "x-scalar-examples": [ + { + "lang": "Python", + "label": "Python", + "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine = client.usage.machines.list_storage_usage()\nprint(machine)" + }, + { + "lang": "TypeScript", + "label": "TypeScript", + "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listStorageUsage = await client.usage.machines.listStorageUsage();\nconsole.log(listStorageUsage);" + }, + { + "lang": "Go", + "label": "Go", + "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachine, err := client.Usage.Machines.ListStorageUsage(context.Background(), sdk.UsageMachineListStorageUsageParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machine)\n}" + } + ] + } + } + }, + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "servers": [ + { + "description": "Official DCS API", + "url": "https://dcs.dedaluslabs.ai" + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fff5a56 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "dedalus-cli", + "version": "0.1.3", + "description": "Controlplane API for Dedalus Cloud Services (DCS).", + "type": "module", + "bin": { + "dedalus": "./dist/esm/bin.js" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/esm/index.d.ts", + "exports": { + ".": { + "types": "./dist/esm/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/esm/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "api.md" + ], + "scripts": { + "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/finalize-build.mjs", + "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.cjs.json --noEmit" + }, + "dependencies": { + "ansis": "^4.3.0", + "commander": "^14.0.3", + "yaml": "^2.9.0", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/node": "^20.17.0", + "@types/ws": "^8.5.13", + "typescript": "^6.0.0" + }, + "peerDependencies": { + "ws": "^8.18.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + } + }, + "license": "Apache-2.0" +} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go deleted file mode 100644 index d358db6..0000000 --- a/pkg/cmd/cmd.go +++ /dev/null @@ -1,259 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "bytes" - "compress/gzip" - "context" - "fmt" - "os" - "path/filepath" - "slices" - "strings" - - "github.com/dedalus-labs/dedalus-cli/internal/autocomplete" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - docs "github.com/urfave/cli-docs/v3" - "github.com/urfave/cli/v3" -) - -var ( - Command *cli.Command - CommandErrorBuffer bytes.Buffer -) - -func init() { - Command = &cli.Command{ - Name: "dedalus", - Usage: "CLI for the Dedalus API", - Suggest: true, - Version: Version, - ErrWriter: &CommandErrorBuffer, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "debug", - Usage: "Enable debug logging", - }, - &cli.StringFlag{ - Name: "base-url", - DefaultText: "url", - Usage: "Override the base URL for API requests", - Validator: func(baseURL string) error { - return ValidateBaseURL(baseURL, "--base-url") - }, - }, - &cli.StringFlag{ - Name: "format", - Usage: "The format for displaying response data (one of: " + strings.Join(OutputFormats, ", ") + ")", - Value: "json", - Validator: func(format string) error { - if !slices.Contains(OutputFormats, strings.ToLower(format)) { - return fmt.Errorf("format must be one of: %s", strings.Join(OutputFormats, ", ")) - } - return nil - }, - }, - &cli.StringFlag{ - Name: "format-error", - Usage: "The format for displaying error data (one of: " + strings.Join(OutputFormats, ", ") + ")", - Value: "json", - Validator: func(format string) error { - if !slices.Contains(OutputFormats, strings.ToLower(format)) { - return fmt.Errorf("format must be one of: %s", strings.Join(OutputFormats, ", ")) - } - return nil - }, - }, - &cli.StringFlag{ - Name: "transform", - Usage: "The GJSON transformation for data output.", - }, - &cli.StringFlag{ - Name: "transform-error", - Usage: "The GJSON transformation for errors.", - }, - &cli.BoolFlag{ - Name: "raw-output", - Aliases: []string{"r"}, - Usage: "If the result is a string, print it without JSON quotes. This can be useful for making output transforms talk to non-JSON-based systems.", - }, - &requestflag.Flag[string]{ - Name: "api-key", - Usage: "Dedalus API key sent as Authorization Bearer.", - Sources: cli.EnvVars("DEDALUS_API_KEY"), - }, - &requestflag.Flag[string]{ - Name: "x-api-key", - Usage: "Dedalus API key sent as x-api-key header.", - Sources: cli.EnvVars("DEDALUS_X_API_KEY"), - }, - &requestflag.Flag[string]{ - Name: "dedalus-org-id", - Usage: "Organization ID header for all DCS requests.", - Sources: cli.EnvVars("DEDALUS_ORG_ID"), - }, - }, - Commands: []*cli.Command{ - { - Name: "usage", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &usageRetrieve, - &usageMachineCompute, - &usageMachineStorage, - }, - }, - { - Name: "machines", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesCreate, - &machinesRetrieve, - &machinesUpdate, - &machinesList, - &machinesDelete, - &machinesSleep, - &machinesWake, - &machinesWatch, - }, - }, - { - Name: "machines:artifacts", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesArtifactsRetrieve, - &machinesArtifactsList, - &machinesArtifactsDelete, - }, - }, - { - Name: "machines:previews", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesPreviewsCreate, - &machinesPreviewsRetrieve, - &machinesPreviewsList, - &machinesPreviewsDelete, - }, - }, - { - Name: "machines:ssh", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesSSHCreate, - &machinesSSHRetrieve, - &machinesSSHList, - &machinesSSHDelete, - }, - }, - { - Name: "machines:executions", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesExecutionsCreate, - &machinesExecutionsRetrieve, - &machinesExecutionsList, - &machinesExecutionsDelete, - &machinesExecutionsEvents, - &machinesExecutionsOutput, - }, - }, - { - Name: "machines:terminals", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &machinesTerminalsCreate, - &machinesTerminalsRetrieve, - &machinesTerminalsList, - &machinesTerminalsDelete, - }, - }, - { - Name: "@manpages", - Usage: "Generate documentation for 'man'", - UsageText: "dedalus @manpages [-o dedalus.1] [--gzip]", - Hidden: true, - Action: generateManpages, - HideHelpCommand: true, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Usage: "write manpages to the given folder", - Value: "man", - }, - &cli.BoolFlag{ - Name: "gzip", - Aliases: []string{"z"}, - Usage: "output gzipped manpage files to .gz", - Value: true, - }, - &cli.BoolFlag{ - Name: "text", - Aliases: []string{"z"}, - Usage: "output uncompressed text files", - Value: false, - }, - }, - }, - { - Name: "__complete", - Hidden: true, - HideHelpCommand: true, - Action: autocomplete.ExecuteShellCompletion, - }, - { - Name: "@completion", - Hidden: true, - HideHelpCommand: true, - Action: autocomplete.OutputCompletionScript, - }, - }, - HideHelpCommand: true, - } -} - -func generateManpages(ctx context.Context, c *cli.Command) error { - manpage, err := docs.ToManWithSection(Command, 1) - if err != nil { - return err - } - dir := c.String("output") - err = os.MkdirAll(filepath.Join(dir, "man1"), 0755) - if err != nil { - // handle error - } - if c.Bool("text") { - file, err := os.Create(filepath.Join(dir, "man1", "dedalus.1")) - if err != nil { - return err - } - defer file.Close() - if _, err := file.WriteString(manpage); err != nil { - return err - } - } - if c.Bool("gzip") { - file, err := os.Create(filepath.Join(dir, "man1", "dedalus.1.gz")) - if err != nil { - return err - } - defer file.Close() - gzWriter := gzip.NewWriter(file) - defer gzWriter.Close() - _, err = gzWriter.Write([]byte(manpage)) - if err != nil { - return err - } - } - fmt.Printf("Wrote manpages to %s\n", dir) - return nil -} diff --git a/pkg/cmd/cmdutil.go b/pkg/cmd/cmdutil.go deleted file mode 100644 index d1bf75c..0000000 --- a/pkg/cmd/cmdutil.go +++ /dev/null @@ -1,531 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log" - "mime" - "net/http" - "net/http/httputil" - "os" - "os/exec" - "os/signal" - "path/filepath" - "strings" - "syscall" - - "github.com/dedalus-labs/dedalus-cli/internal/jsonview" - "github.com/dedalus-labs/dedalus-go/option" - - "github.com/charmbracelet/x/term" - "github.com/itchyny/json2yaml" - "github.com/muesli/reflow/wrap" - "github.com/tidwall/gjson" - "github.com/tidwall/pretty" - "github.com/urfave/cli/v3" -) - -var OutputFormats = []string{"auto", "explore", "json", "jsonl", "pretty", "raw", "yaml"} - -// ValidateBaseURL checks that a base URL is correctly prefixed with a protocol scheme and produces a better -// error message than the person would see otherwise if it doesn't. -func ValidateBaseURL(value, source string) error { - if value != "" && !strings.HasPrefix(value, "http://") && !strings.HasPrefix(value, "https://") { - return fmt.Errorf("%s %q is missing a scheme (expected http:// or https://)", source, value) - } - return nil -} - -func getDefaultRequestOptions(cmd *cli.Command) []option.RequestOption { - opts := []option.RequestOption{ - option.WithHeader("User-Agent", fmt.Sprintf("Dedalus/CLI %s", Version)), - option.WithHeader("X-Stainless-Lang", "cli"), - option.WithHeader("X-Stainless-Package-Version", Version), - option.WithHeader("X-Stainless-Runtime", "cli"), - option.WithHeader("X-Stainless-CLI-Command", cmd.FullName()), - } - if cmd.IsSet("api-key") { - opts = append(opts, option.WithAPIKey(cmd.String("api-key"))) - } - if cmd.IsSet("x-api-key") { - opts = append(opts, option.WithXAPIKey(cmd.String("x-api-key"))) - } - if cmd.IsSet("dedalus-org-id") { - opts = append(opts, option.WithDedalusOrgID(cmd.String("dedalus-org-id"))) - } - - // Override base URL if the --base-url flag is provided - if baseURL := cmd.String("base-url"); baseURL != "" { - opts = append(opts, option.WithBaseURL(baseURL)) - } - - return opts -} - -var debugMiddlewareOption = option.WithMiddleware( - func(r *http.Request, mn option.MiddlewareNext) (*http.Response, error) { - logger := log.Default() - - if reqBytes, err := httputil.DumpRequest(r, true); err == nil { - logger.Printf("Request Content:\n%s\n", reqBytes) - } - - resp, err := mn(r) - if err != nil { - return resp, err - } - - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { - logger.Printf("Response Content:\n%s\n", respBytes) - } - - return resp, err - }, -) - -// 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, 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 { - switch v := w.(type) { - case *os.File: - return term.IsTerminal(v.Fd()) - default: - return false - } -} - -func streamOutput(label string, generateOutput func(w *os.File) error) error { - // For non-tty output (probably a pipe), write directly to stdout - if !isTerminal(os.Stdout) { - return streamToStdout(generateOutput) - } - - // When streaming output on Unix-like systems, there's a special trick involving creating two socket pairs - // that we prefer because it supports small buffer sizes which results in less pagination per buffer. The - // constructs needed to run it don't exist on Windows builds, so we have this function broken up into - // OS-specific files with conditional build comments. Under Windows (and in case our fancy constructs fail - // on Unix), we fall back to using pipes (`streamToPagerWithPipe`), which are OS agnostic. - // - // Defined in either cmdutil_unix.go or cmdutil_windows.go. - return streamOutputOSSpecific(label, generateOutput) -} - -func streamToPagerWithPipe(label string, generateOutput func(w *os.File) error) error { - r, w, err := os.Pipe() - if err != nil { - return err - } - defer r.Close() - defer w.Close() - - pagerProgram := os.Getenv("PAGER") - if pagerProgram == "" { - pagerProgram = "less" - } - - if _, err := exec.LookPath(pagerProgram); err != nil { - return err - } - - cmd := exec.Command(pagerProgram) - cmd.Stdin = r - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = append(os.Environ(), - "LESS=-X -r -P "+label, - "MORE=-r -P "+label, - ) - - if err := cmd.Start(); err != nil { - return err - } - - if err := r.Close(); err != nil { - return err - } - - // If we would be streaming to a terminal and aren't forcing color one way - // or the other, we should configure things to use color so the pager gets - // colorized input. - if isTerminal(os.Stdout) && os.Getenv("FORCE_COLOR") == "" { - os.Setenv("FORCE_COLOR", "1") - } - - if err := generateOutput(w); err != nil && !strings.Contains(err.Error(), "broken pipe") { - return err - } - - w.Close() - return cmd.Wait() -} - -func streamToStdout(generateOutput func(w *os.File) error) error { - signal.Ignore(syscall.SIGPIPE) - err := generateOutput(os.Stdout) - if err != nil && strings.Contains(err.Error(), "broken pipe") { - return nil - } - return err -} - -// writeBinaryResponse writes a binary response to stdout or a file. -// -// Takes in a stdout reference so we can test this function without overriding os.Stdout in tests. -func writeBinaryResponse(response *http.Response, stdout io.Writer, outfile string) (string, error) { - defer response.Body.Close() - body, err := io.ReadAll(response.Body) - if err != nil { - return "", err - } - switch outfile { - case "-", "/dev/stdout": - _, err := stdout.Write(body) - return "", err - case "": - // If output file is unspecified, then print to stdout for plain text or - // if stdout is not a terminal: - if !isTerminal(os.Stdout) || isUTF8TextFile(body) { - _, err := stdout.Write(body) - return "", err - } - - // If response has a suggested filename in the content-disposition - // header, then use that (with an optional suffix to ensure uniqueness): - file, err := createDownloadFile(response, body) - if err != nil { - return "", err - } - defer file.Close() - if _, err := file.Write(body); err != nil { - return "", err - } - return fmt.Sprintf("Wrote output to: %s", file.Name()), nil - default: - if err := os.WriteFile(outfile, body, 0644); err != nil { - return "", err - } - return fmt.Sprintf("Wrote output to: %s", outfile), nil - } -} - -// Return a writable file handle to a new file, which attempts to choose a good filename -// based on the Content-Disposition header or sniffing the MIME filetype of the response. -func createDownloadFile(response *http.Response, data []byte) (*os.File, error) { - filename := "file" - // If the header provided an output filename, use that - disp := response.Header.Get("Content-Disposition") - _, params, err := mime.ParseMediaType(disp) - if err == nil { - if dispFilename, ok := params["filename"]; ok { - // Only use the last path component to prevent directory traversal - filename = filepath.Base(dispFilename) - // Try to create the file with exclusive flag to avoid race conditions - file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) - if err == nil { - return file, nil - } - } - } - - // If file already exists, create a unique filename using CreateTemp - ext := filepath.Ext(filename) - if ext == "" { - ext = guessExtension(data) - } - base := strings.TrimSuffix(filename, ext) - return os.CreateTemp(".", base+"-*"+ext) -} - -func guessExtension(data []byte) string { - ct := http.DetectContentType(data) - - // Prefer common extensions over obscure ones - switch ct { - case "application/gzip": - return ".gz" - case "application/pdf": - return ".pdf" - case "application/zip": - return ".zip" - case "audio/mpeg": - return ".mp3" - case "image/bmp": - return ".bmp" - case "image/gif": - return ".gif" - case "image/jpeg": - return ".jpg" - case "image/png": - return ".png" - case "image/webp": - return ".webp" - case "video/mp4": - return ".mp4" - } - - exts, err := mime.ExtensionsByType(ct) - if err == nil && len(exts) > 0 { - return exts[0] - } else if isUTF8TextFile(data) { - return ".txt" - } else { - return ".bin" - } -} - -func shouldUseColors(w io.Writer) bool { - force, ok := os.LookupEnv("FORCE_COLOR") - if ok { - if force == "1" { - return true - } - if force == "0" { - return false - } - } - return isTerminal(w) -} - -func formatJSON(res gjson.Result, opts ShowJSONOpts) ([]byte, error) { - if opts.Transform != "" { - transformed := res.Get(opts.Transform) - if transformed.Exists() { - res = transformed - } - } - // Modeled after `jq -r` (`--raw-output`): if the result is a string, print it without JSON quotes so that - // it's easier to pipe into other programs. - if opts.RawOutput && res.Type == gjson.String { - return []byte(res.Str + "\n"), nil - } - switch strings.ToLower(opts.Format) { - case "auto": - autoOpts := opts - autoOpts.Format = "json" - autoOpts.Transform = "" - return formatJSON(res, autoOpts) - case "pretty": - return []byte(jsonview.RenderJSON(opts.Title, res) + "\n"), nil - case "json": - prettyJSON := pretty.Pretty([]byte(res.Raw)) - if shouldUseColors(opts.Stdout) { - return pretty.Color(prettyJSON, pretty.TerminalStyle), nil - } else { - return prettyJSON, nil - } - case "jsonl": - // @ugly is gjson syntax for "no whitespace", so it fits on one line - oneLineJSON := res.Get("@ugly").Raw - if shouldUseColors(opts.Stdout) { - bytes := append(pretty.Color([]byte(oneLineJSON), pretty.TerminalStyle), '\n') - return bytes, nil - } else { - return []byte(oneLineJSON + "\n"), nil - } - case "raw": - return []byte(res.Raw + "\n"), nil - case "yaml": - input := strings.NewReader(res.Raw) - var yaml strings.Builder - if err := json2yaml.Convert(&yaml, input); err != nil { - return nil, err - } - _, err := opts.Stdout.Write([]byte(yaml.String())) - return nil, err - default: - return nil, fmt.Errorf("Invalid format: %s, valid formats are: %s", opts.Format, strings.Join(OutputFormats, ", ")) - } -} - -const warningExploreNotSupported = "Warning: Output format 'explore' not supported for non-terminal output; falling back to 'json'\n" - -// ShowJSONOpts configures how JSON output is displayed. -type ShowJSONOpts struct { - ExplicitFormat bool // true if the user explicitly passed --format - Format string // output format (auto, explore, json, jsonl, pretty, raw, yaml) - RawOutput bool // like jq -r: print strings without JSON quotes - Stderr io.Writer // stderr for warnings; injectable for testing; defaults to os.Stderr - Stdout *os.File // stdout (or pager); injectable for testing; defaults to os.Stdout - Title string // display title - Transform string // GJSON path to extract before displaying -} - -func (o *ShowJSONOpts) setDefaults() { - if o.Stderr == nil { - o.Stderr = os.Stderr - } - if o.Stdout == nil { - o.Stdout = os.Stdout - } -} - -// ShowJSON displays a single JSON result to the user. -func ShowJSON(res gjson.Result, opts ShowJSONOpts) error { - opts.setDefaults() - - switch strings.ToLower(opts.Format) { - case "auto": - autoOpts := opts - autoOpts.Format = "json" - return ShowJSON(res, autoOpts) - case "explore": - if !isTerminal(opts.Stdout) { - if opts.ExplicitFormat { - fmt.Fprint(opts.Stderr, warningExploreNotSupported) - } - jsonOpts := opts - jsonOpts.Format = "json" - return ShowJSON(res, jsonOpts) - } - if opts.Transform != "" { - transformed := res.Get(opts.Transform) - if transformed.Exists() { - res = transformed - } - } - return jsonview.ExploreJSON(opts.Title, res) - default: - bytes, err := formatJSON(res, opts) - if err != nil { - return err - } - - _, err = opts.Stdout.Write(bytes) - return err - } -} - -// Get the number of lines that would be output by writing the data to the terminal -func countTerminalLines(data []byte, terminalWidth int) int { - return bytes.Count([]byte(wrap.String(string(data), terminalWidth)), []byte("\n")) -} - -type hasRawJSON interface { - RawJSON() string -} - -// ShowJSONIterator displays an iterator of values to the user. Use itemsToDisplay = -1 for no limit. -func ShowJSONIterator[T any](iter jsonview.Iterator[T], itemsToDisplay int64, opts ShowJSONOpts) error { - opts.setDefaults() - - if opts.Format == "explore" { - if isTerminal(opts.Stdout) { - return jsonview.ExploreJSONStream(opts.Title, iter) - } - if opts.ExplicitFormat { - fmt.Fprint(opts.Stderr, warningExploreNotSupported) - } - opts.Format = "json" - } - - terminalWidth, terminalHeight, err := term.GetSize(os.Stdout.Fd()) - if err != nil { - terminalWidth = 100 - terminalHeight = 40 - } - - // Decide whether or not to use a pager based on whether it's a short output or a long output - usePager := false - output := []byte{} - numberOfNewlines := 0 - // -1 is used to signal no limit of items to display - for itemsToDisplay != 0 && iter.Next() { - item := iter.Current() - var obj gjson.Result - if hasRaw, ok := any(item).(hasRawJSON); ok { - obj = gjson.Parse(hasRaw.RawJSON()) - } else { - jsonData, err := json.Marshal(item) - if err != nil { - return err - } - obj = gjson.ParseBytes(jsonData) - } - json, err := formatJSON(obj, opts) - if err != nil { - return err - } - - output = append(output, json...) - itemsToDisplay -= 1 - numberOfNewlines += countTerminalLines(json, terminalWidth) - - // If the output won't fit in the terminal window, stream it to a pager - if numberOfNewlines >= terminalHeight-3 { - usePager = true - break - } - } - - if !usePager { - _, err := opts.Stdout.Write(output) - if err != nil { - return err - } - - return iter.Err() - } - - return streamOutput(opts.Title, func(pager *os.File) error { - _, err := pager.Write(output) - if err != nil { - return err - } - - pagerOpts := opts - pagerOpts.Stdout = pager - - for iter.Next() { - if itemsToDisplay == 0 { - break - } - item := iter.Current() - var obj gjson.Result - if hasRaw, ok := any(item).(hasRawJSON); ok { - obj = gjson.Parse(hasRaw.RawJSON()) - } else { - jsonData, err := json.Marshal(item) - if err != nil { - return err - } - obj = gjson.ParseBytes(jsonData) - } - if err := ShowJSON(obj, pagerOpts); err != nil { - return err - } - itemsToDisplay -= 1 - } - return iter.Err() - }) -} diff --git a/pkg/cmd/cmdutil_test.go b/pkg/cmd/cmdutil_test.go deleted file mode 100644 index e91cb10..0000000 --- a/pkg/cmd/cmdutil_test.go +++ /dev/null @@ -1,388 +0,0 @@ -package cmd - -import ( - "bytes" - "io" - "net/http" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tidwall/gjson" - - "github.com/dedalus-labs/dedalus-cli/internal/jsonview" -) - -func TestStreamOutput(t *testing.T) { - t.Setenv("PAGER", "cat") - err := streamOutput("stream test", func(w *os.File) error { - _, writeErr := w.WriteString("Hello world\n") - return writeErr - }) - if err != nil { - t.Errorf("streamOutput failed: %v", err) - } -} - -func TestWriteBinaryResponse(t *testing.T) { - t.Run("write to explicit file", func(t *testing.T) { - tmpDir := t.TempDir() - outfile := tmpDir + "/output.txt" - body := []byte("test content") - resp := &http.Response{ - Body: io.NopCloser(bytes.NewReader(body)), - } - - msg, err := writeBinaryResponse(resp, os.Stdout, outfile) - - require.NoError(t, err) - assert.Contains(t, msg, outfile) - - content, err := os.ReadFile(outfile) - require.NoError(t, err) - assert.Equal(t, body, content) - }) - - t.Run("write to stdout", func(t *testing.T) { - t.Parallel() - - var buf bytes.Buffer - body := []byte("stdout content") - resp := &http.Response{ - Body: io.NopCloser(bytes.NewReader(body)), - } - msg, err := writeBinaryResponse(resp, &buf, "-") - - require.NoError(t, err) - assert.Empty(t, msg) - assert.Equal(t, body, buf.Bytes()) - }) -} - -func TestCreateDownloadFile(t *testing.T) { - t.Run("creates file with filename from header", func(t *testing.T) { - t.Chdir(t.TempDir()) - - resp := &http.Response{ - Header: http.Header{ - "Content-Disposition": []string{`attachment; filename="test.txt"`}, - }, - } - file, err := createDownloadFile(resp, []byte("test content")) - require.NoError(t, err) - defer file.Close() - assert.Equal(t, "test.txt", filepath.Base(file.Name())) - - // Create a second file with the same name to ensure it doesn't clobber the first - resp2 := &http.Response{ - Header: http.Header{ - "Content-Disposition": []string{`attachment; filename="test.txt"`}, - }, - } - file2, err := createDownloadFile(resp2, []byte("second content")) - require.NoError(t, err) - defer file2.Close() - assert.NotEqual(t, file.Name(), file2.Name(), "second file should have a different name") - assert.Contains(t, filepath.Base(file2.Name()), "test") - }) - - t.Run("creates temp file when no header", func(t *testing.T) { - t.Chdir(t.TempDir()) - - resp := &http.Response{Header: http.Header{}} - file, err := createDownloadFile(resp, []byte("test content")) - require.NoError(t, err) - defer file.Close() - assert.Contains(t, filepath.Base(file.Name()), "file-") - }) - - t.Run("prevents directory traversal", func(t *testing.T) { - t.Chdir(t.TempDir()) - - resp := &http.Response{ - Header: http.Header{ - "Content-Disposition": []string{`attachment; filename="../../../etc/passwd"`}, - }, - } - file, err := createDownloadFile(resp, []byte("test content")) - require.NoError(t, err) - defer file.Close() - assert.Equal(t, "passwd", filepath.Base(file.Name())) - }) -} - -func TestValidateBaseURL(t *testing.T) { - t.Parallel() - - t.Run("ValidHTTPS", func(t *testing.T) { - t.Parallel() - - require.NoError(t, ValidateBaseURL("https://api.example.com", "--base-url")) - }) - - t.Run("ValidHTTP", func(t *testing.T) { - t.Parallel() - - require.NoError(t, ValidateBaseURL("http://localhost:8080", "--base-url")) - }) - - t.Run("Empty", func(t *testing.T) { - t.Parallel() - - require.NoError(t, ValidateBaseURL("", "MY_BASE_URL")) - }) - - t.Run("MissingScheme", func(t *testing.T) { - t.Parallel() - - err := ValidateBaseURL("localhost:8080", "MY_BASE_URL") - require.Error(t, err) - assert.Contains(t, err.Error(), "MY_BASE_URL") - assert.Contains(t, err.Error(), "missing a scheme") - }) - - t.Run("HostOnly", func(t *testing.T) { - t.Parallel() - - err := ValidateBaseURL("api.example.com", "--base-url") - require.Error(t, err) - assert.Contains(t, err.Error(), "--base-url") - }) -} - -func TestFormatJSON(t *testing.T) { - t.Parallel() - - t.Run("RawWithTransform", func(t *testing.T) { - t.Parallel() - - res := gjson.Parse(`{"id":"abc123","name":"test"}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "id"}) - require.NoError(t, err) - require.Equal(t, `"abc123"`+"\n", string(formatted)) - }) - - t.Run("RawWithoutTransform", func(t *testing.T) { - t.Parallel() - - res := gjson.Parse(`{"id":"abc123","name":"test"}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout}) - require.NoError(t, err) - require.Equal(t, `{"id":"abc123","name":"test"}`+"\n", string(formatted)) - }) - - t.Run("RawWithNestedTransform", func(t *testing.T) { - t.Parallel() - - res := gjson.Parse(`{"data":{"items":[1,2,3]}}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "data.items"}) - require.NoError(t, err) - require.Equal(t, "[1,2,3]\n", string(formatted)) - }) - - t.Run("RawWithNonexistentTransform", func(t *testing.T) { - t.Parallel() - - res := gjson.Parse(`{"id":"abc123"}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "missing"}) - require.NoError(t, err) - // Transform path doesn't exist, so original result is returned - require.Equal(t, `{"id":"abc123"}`+"\n", string(formatted)) - }) - - t.Run("RawOutputString", func(t *testing.T) { - t.Parallel() - - res := gjson.Parse(`{"id":"abc123","name":"test"}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "json", Stdout: os.Stdout, Transform: "id", RawOutput: true}) - require.NoError(t, err) - require.Equal(t, "abc123\n", string(formatted)) - }) - - t.Run("RawOutputNonString", func(t *testing.T) { - t.Parallel() - - // --raw-output has no effect on non-string values - res := gjson.Parse(`{"count":42}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "count", RawOutput: true}) - require.NoError(t, err) - require.Equal(t, "42\n", string(formatted)) - }) - - t.Run("RawOutputObject", func(t *testing.T) { - t.Parallel() - - // --raw-output has no effect on objects - res := gjson.Parse(`{"nested":{"a":1}}`) - formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "nested", RawOutput: true}) - require.NoError(t, err) - require.Equal(t, `{"a":1}`+"\n", string(formatted)) - }) -} - -func TestShowJSONIterator(t *testing.T) { - t.Parallel() - - t.Run("RawMultipleItems", func(t *testing.T) { - t.Parallel() - - iter := &sliceIterator[map[string]any]{items: []map[string]any{ - {"id": "abc", "name": "first"}, - {"id": "def", "name": "second"}, - }} - captured := captureShowJSONIterator(t, iter, "raw", "", -1) - assert.Equal(t, `{"id":"abc","name":"first"}`+"\n"+`{"id":"def","name":"second"}`+"\n", captured) - }) - - t.Run("RawWithTransform", func(t *testing.T) { - t.Parallel() - - iter := &sliceIterator[map[string]any]{items: []map[string]any{ - {"id": "abc", "name": "first"}, - {"id": "def", "name": "second"}, - }} - captured := captureShowJSONIterator(t, iter, "raw", "id", -1) - assert.Equal(t, `"abc"`+"\n"+`"def"`+"\n", captured) - }) - - t.Run("LimitItems", func(t *testing.T) { - t.Parallel() - - iter := &sliceIterator[map[string]any]{items: []map[string]any{ - {"id": "abc"}, - {"id": "def"}, - {"id": "ghi"}, - }} - captured := captureShowJSONIterator(t, iter, "raw", "", 2) - assert.Equal(t, `{"id":"abc"}`+"\n"+`{"id":"def"}`+"\n", captured) - }) -} - -func TestExploreFallback(t *testing.T) { - t.Parallel() - - t.Run("ShowJSONFallsBackToJsonOnNonTTY", func(t *testing.T) { - t.Parallel() - - // os.Pipe() produces a *os.File that isn't a terminal, so explore should fall back. - r, w, err := os.Pipe() - require.NoError(t, err) - defer r.Close() - - var stderr bytes.Buffer - res := gjson.Parse(`{"id":"abc"}`) - err = ShowJSON(res, ShowJSONOpts{ - Format: "explore", - Stderr: &stderr, - Stdout: w, - Title: "test", - }) - w.Close() - require.NoError(t, err) - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - assert.Contains(t, buf.String(), `"id"`) - assert.Contains(t, buf.String(), `"abc"`) - }) - - t.Run("ShowJSONIteratorFallsBackToJsonOnNonTTY", func(t *testing.T) { - t.Parallel() - - iter := &sliceIterator[map[string]any]{items: []map[string]any{ - {"id": "abc"}, - }} - captured := captureShowJSONIterator(t, iter, "explore", "", -1) - assert.Contains(t, captured, `"id"`) - assert.Contains(t, captured, `"abc"`) - }) - - t.Run("ShowJSONWarnsWhenExplicitFormatOnNonTTY", func(t *testing.T) { - t.Parallel() - - r, w, err := os.Pipe() - require.NoError(t, err) - defer r.Close() - - var stderr bytes.Buffer - res := gjson.Parse(`{"id":"abc"}`) - err = ShowJSON(res, ShowJSONOpts{ - ExplicitFormat: true, - Format: "explore", - Stderr: &stderr, - Stdout: w, - Title: "test", - }) - w.Close() - require.NoError(t, err) - - assert.Equal(t, warningExploreNotSupported, stderr.String()) - }) - - t.Run("ShowJSONSilentWhenDefaultFormatOnNonTTY", func(t *testing.T) { - t.Parallel() - - r, w, err := os.Pipe() - require.NoError(t, err) - defer r.Close() - - var stderr bytes.Buffer - res := gjson.Parse(`{"id":"abc"}`) - err = ShowJSON(res, ShowJSONOpts{ - Format: "explore", - Stderr: &stderr, - Stdout: w, - Title: "test", - }) - w.Close() - require.NoError(t, err) - - assert.Empty(t, stderr.String(), "no warning expected when format was not explicit") - }) -} - -// sliceIterator is a simple iterator over a slice for testing. -type sliceIterator[T any] struct { - index int - items []T -} - -func (it *sliceIterator[T]) Next() bool { - it.index++ - return it.index <= len(it.items) -} - -func (it *sliceIterator[T]) Current() T { - return it.items[it.index-1] -} - -func (it *sliceIterator[T]) Err() error { - return nil -} - -var _ jsonview.Iterator[any] = (*sliceIterator[any])(nil) - -// captureShowJSONIterator runs ShowJSONIterator and captures the output written to a file. -func captureShowJSONIterator[T any](t *testing.T, iter jsonview.Iterator[T], format, transform string, itemsToDisplay int64) string { - t.Helper() - - r, w, err := os.Pipe() - require.NoError(t, err) - defer r.Close() - - err = ShowJSONIterator(iter, itemsToDisplay, ShowJSONOpts{ - Format: format, - Stderr: io.Discard, - Stdout: w, - Title: "test", - Transform: transform, - }) - w.Close() - require.NoError(t, err) - - var buf bytes.Buffer - _, _ = buf.ReadFrom(r) - return buf.String() -} diff --git a/pkg/cmd/cmdutil_unix.go b/pkg/cmd/cmdutil_unix.go deleted file mode 100644 index edefcd7..0000000 --- a/pkg/cmd/cmdutil_unix.go +++ /dev/null @@ -1,127 +0,0 @@ -//go:build !windows - -package cmd - -import ( - "fmt" - "os" - "os/exec" - "strings" - "syscall" - - "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) - if err != nil || pagerInput == nil { - // Fall back to pipe if socket setup fails - return streamToPagerWithPipe(label, generateOutput) - } - defer pagerInput.Close() - - // If we would be streaming to a terminal and aren't forcing color one way - // or the other, we should configure things to use color so the pager gets - // colorized input. - if isTerminal(os.Stdout) && os.Getenv("FORCE_COLOR") == "" { - os.Setenv("FORCE_COLOR", "1") - } - - // If the pager exits before reading all input, then generateOutput() will - // produce a broken pipe error, which is fine and we don't want to propagate it. - if err := generateOutput(pagerInput); err != nil && - !strings.Contains(err.Error(), "broken pipe") { - return err - } - - // Close the file NOW before we wait for the child process to terminate. - // This way, the child will receive the end-of-file signal and know that - // there is no more input. Otherwise the child process may block - // indefinitely waiting for another line (this can happen when streaming - // less than a screenful of data to a pager). - pagerInput.Close() - - // Wait for child process to exit - var wstatus syscall.WaitStatus - _, err = syscall.Wait4(pid, &wstatus, 0, nil) - if wstatus.ExitStatus() != 0 { - return fmt.Errorf("Pager exited with non-zero exit status: %d", wstatus.ExitStatus()) - } - return err -} - -func openSocketPairPager(label string) (*os.File, int, error) { - fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - return nil, 0, err - } - - // The child file descriptor will be sent to the child process through - // ProcAttr and ForkExec(), while the parent process will always close the - // child file descriptor. - // The parent file descriptor will be wrapped in an os.File wrapper and - // returned from this function, or closed if something goes wrong. - parentFd, childFd := fds[0], fds[1] - defer unix.Close(childFd) - - // Use small buffer sizes so we don't ask the server for more paginated - // values than we actually need. - if err := unix.SetsockoptInt(parentFd, unix.SOL_SOCKET, unix.SO_SNDBUF, 128); err != nil { - unix.Close(parentFd) - return nil, 0, err - } - if err := unix.SetsockoptInt(childFd, unix.SOL_SOCKET, unix.SO_RCVBUF, 128); err != nil { - unix.Close(parentFd) - return nil, 0, err - } - - // Set CLOEXEC on the parent file descriptor so it doesn't leak to child - syscall.CloseOnExec(parentFd) - - parentConn := os.NewFile(uintptr(parentFd), "parent-socket") - - pagerProgram := os.Getenv("PAGER") - if pagerProgram == "" { - pagerProgram = "less" - } - - pagerPath, err := exec.LookPath(pagerProgram) - if err != nil { - unix.Close(parentFd) - return nil, 0, err - } - - env := os.Environ() - env = append(env, "LESS=-r -P "+label) - env = append(env, "MORE=-r -P "+label) - - procAttr := &syscall.ProcAttr{ - Dir: "", - Env: env, - Files: []uintptr{ - uintptr(childFd), // stdin (fd 0) - uintptr(syscall.Stdout), // stdout (fd 1) - uintptr(syscall.Stderr), // stderr (fd 2) - }, - } - - pid, err := syscall.ForkExec(pagerPath, []string{pagerProgram}, procAttr) - if err != nil { - unix.Close(parentFd) - return nil, 0, err - } - - return parentConn, pid, nil -} diff --git a/pkg/cmd/cmdutil_windows.go b/pkg/cmd/cmdutil_windows.go deleted file mode 100644 index 49b025e..0000000 --- a/pkg/cmd/cmdutil_windows.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build windows - -package cmd - -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 - // available on Windows, so we fall back to using pipes. - return streamToPagerWithPipe(label, generateOutput) -} diff --git a/pkg/cmd/flagoptions.go b/pkg/cmd/flagoptions.go deleted file mode 100644 index 73fd179..0000000 --- a/pkg/cmd/flagoptions.go +++ /dev/null @@ -1,692 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "maps" - "mime" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "reflect" - "strings" - "unicode/utf8" - - "github.com/dedalus-labs/dedalus-cli/internal/apiform" - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/debugmiddleware" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go/option" - - "github.com/goccy/go-yaml" - "github.com/urfave/cli/v3" -) - -type BodyContentType int - -const ( - EmptyBody BodyContentType = iota - MultipartFormEncoded - ApplicationJSON - ApplicationOctetStream -) - -type FileEmbedStyle int - -const ( - // EmbedText reads referenced files fully into memory and substitutes the file's contents back into the - // value as a string. Binary files are base64-encoded. Used for JSON request bodies and for headers and - // query parameters, where the file contents need to be serialized inline. - EmbedText FileEmbedStyle = iota - - // EmbedIOReader replaces file references with an io.Reader that streams the file's contents. Used for - // `multipart/form-data` and `application/octet-stream` request bodies, where files are uploaded as binary - // parts rather than embedded into a text value. - EmbedIOReader -) - -// onceStdinReader wraps an io.Reader that can only be consumed once, used to ensure stdin is read by at most -// one parameter (or only for a body root parameter or only for YAML parameter input). If reason is set, stdin -// is unavailable and read() returns an error explaining why. -type onceStdinReader struct { - stdinReader io.Reader - failureReason string -} - -func (o *onceStdinReader) read() (io.Reader, error) { - if o.failureReason != "" { - return nil, fmt.Errorf("cannot read from stdin: %s", o.failureReason) - } - if o.stdinReader == nil { - return nil, fmt.Errorf("stdin has already been read by another parameter; it can only be read once") - } - r := o.stdinReader - o.stdinReader = nil - return r, nil -} - -func (o *onceStdinReader) readAll() ([]byte, error) { - r, err := o.read() - if err != nil { - return nil, err - } - return io.ReadAll(r) -} - -func isStdinPath(s string) bool { - switch s { - case "-", "/dev/fd/0", "/dev/stdin": - return true - } - return false -} - -func embedFiles(obj any, embedStyle FileEmbedStyle, stdin *onceStdinReader) (any, error) { - if obj == nil { - return obj, nil - } - v := reflect.ValueOf(obj) - result, err := embedFilesValue(v, embedStyle, stdin) - if err != nil { - return nil, err - } - return result.Interface(), nil -} - -// Replace "@file.txt" with the file's contents inside a value -func embedFilesValue(v reflect.Value, embedStyle FileEmbedStyle, stdin *onceStdinReader) (reflect.Value, error) { - // Unwrap interface values to get the concrete type - if v.Kind() == reflect.Interface { - if v.IsNil() { - return v, nil - } - v = v.Elem() - } - - switch v.Kind() { - case reflect.Map: - if v.Len() == 0 { - return v, nil - } - // Always create map[string]any to handle potential type changes when embedding files - result := reflect.MakeMap(reflect.TypeOf(map[string]any{})) - - iter := v.MapRange() - for iter.Next() { - key := iter.Key() - val := iter.Value() - newVal, err := embedFilesValue(val, embedStyle, stdin) - if err != nil { - return reflect.Value{}, err - } - result.SetMapIndex(key, newVal) - } - return result, nil - - case reflect.Slice, reflect.Array: - if v.Len() == 0 { - return v, nil - } - // Use `[]any` to allow for types to change when embedding files - result := reflect.MakeSlice(reflect.TypeOf([]any{}), v.Len(), v.Len()) - for i := 0; i < v.Len(); i++ { - newVal, err := embedFilesValue(v.Index(i), embedStyle, stdin) - if err != nil { - return reflect.Value{}, err - } - result.Index(i).Set(newVal) - } - return result, nil - - case reflect.String: - // FilePathValue is always treated as a file path without needing the "@" prefix. - // These only appear on binary upload parameters (multipart/octet-stream), which - // always use EmbedIOReader. - if v.Type() == reflect.TypeOf(FilePathValue("")) { - s := v.String() - if s == "" { - return v, nil - } - if embedStyle == EmbedIOReader { - if isStdinPath(s) { - r, err := stdin.read() - if err != nil { - return v, err - } - return reflect.ValueOf(io.NopCloser(r)), nil - } - upload, err := openFileUpload(s) - if err != nil { - return v, err - } - return reflect.ValueOf(upload), nil - } - if isStdinPath(s) { - content, err := stdin.readAll() - if err != nil { - return v, err - } - return reflect.ValueOf(string(content)), nil - } - content, err := os.ReadFile(s) - if err != nil { - return v, err - } - return reflect.ValueOf(string(content)), nil - } - - s := v.String() - if literal, ok := strings.CutPrefix(s, "\\@"); ok { - // Allow for escaped @ signs if you don't want them to be treated as files - return reflect.ValueOf("@" + literal), nil - } - - if embedStyle == EmbedText { - if filename, ok := strings.CutPrefix(s, "@data://"); ok { - // The "@data://" prefix is for files you explicitly want to upload - // as base64-encoded (even if the file itself is plain text) - if isStdinPath(filename) { - content, err := stdin.readAll() - if err != nil { - return v, err - } - return reflect.ValueOf(base64.StdEncoding.EncodeToString(content)), nil - } - content, err := os.ReadFile(filename) - if err != nil { - return v, err - } - return reflect.ValueOf(base64.StdEncoding.EncodeToString(content)), nil - } else if filename, ok := strings.CutPrefix(s, "@file://"); ok { - // The "@file://" prefix is for files that you explicitly want to - // upload as a string literal with backslash escapes (not base64 - // encoded) - if isStdinPath(filename) { - content, err := stdin.readAll() - if err != nil { - return v, err - } - return reflect.ValueOf(string(content)), nil - } - content, err := os.ReadFile(filename) - if err != nil { - return v, err - } - return reflect.ValueOf(string(content)), nil - } else if filename, ok := strings.CutPrefix(s, "@"); ok { - if isStdinPath(filename) { - content, err := stdin.readAll() - if err != nil { - return v, err - } - if isUTF8TextFile(content) { - return reflect.ValueOf(string(content)), nil - } - return reflect.ValueOf(base64.StdEncoding.EncodeToString(content)), nil - } - content, err := os.ReadFile(filename) - if err != nil { - // If the string is "@username", it's probably supposed to be a - // string literal and not a file reference. However, if the - // string looks like "@file.txt" or "@/tmp/file", then it's - // probably supposed to be a file. - probablyFile := strings.Contains(filename, ".") || strings.Contains(filename, "/") - if probablyFile { - // Give a useful error message if the user tried to upload a - // file, but the file couldn't be read (e.g. mistyped - // filename or permission error) - return v, err - } - // Fall back to the raw value if the user provided something - // like "@username" that's not intended to be a file. - return v, nil - } - // If the file looks like a plain text UTF8 file format, then use the contents directly. - if isUTF8TextFile(content) { - return reflect.ValueOf(string(content)), nil - } - // Otherwise it's a binary file, so encode it with base64 - return reflect.ValueOf(base64.StdEncoding.EncodeToString(content)), nil - } - } else { - if filename, ok := strings.CutPrefix(s, "@"); ok { - // Behavior is the same for @file, @data://file, and @file://file, except that - // @username will be treated as a literal string if no "username" file exists - expectsFile := true - if withoutPrefix, ok := strings.CutPrefix(filename, "data://"); ok { - filename = withoutPrefix - } else if withoutPrefix, ok := strings.CutPrefix(filename, "file://"); ok { - filename = withoutPrefix - } else { - expectsFile = strings.Contains(filename, ".") || strings.Contains(filename, "/") - } - - if isStdinPath(filename) { - r, err := stdin.read() - if err != nil { - return v, err - } - return reflect.ValueOf(io.NopCloser(r)), nil - } - - upload, err := openFileUpload(filename) - if err != nil { - if !expectsFile { - // For strings that start with "@" and don't look like a filename, return the string - return v, nil - } - return v, err - } - return reflect.ValueOf(upload), nil - } - } - return v, nil - - default: - return v, nil - } -} - -// Guess whether a file's contents are binary (e.g. a .jpg or .mp3), as opposed -// to plain text (e.g. .txt or .md). -func isUTF8TextFile(content []byte) bool { - // Go's DetectContentType follows https://mimesniff.spec.whatwg.org/ and - // these are the sniffable content types that are plain text: - textTypes := []string{ - "text/", - "application/json", - "application/xml", - "application/javascript", - "application/x-javascript", - "application/ecmascript", - "application/x-ecmascript", - } - - contentType := http.DetectContentType(content) - for _, prefix := range textTypes { - if strings.HasPrefix(contentType, prefix) { - return utf8.Valid(content) - } - } - return false -} - -func flagOptions( - cmd *cli.Command, - nestedFormat apiquery.NestedQueryFormat, - arrayFormat apiquery.ArrayQueryFormat, - bodyType BodyContentType, - - // This parameter is true if stdin is already in use to pass a binary parameter by using the special value - // "-". In this case, we won't attempt to read it as a JSON/YAML blob for options setting. - ignoreStdin bool, -) ([]option.RequestOption, error) { - var options []option.RequestOption - if cmd.Bool("debug") { - options = append(options, option.WithMiddleware(debugmiddleware.NewRequestLogger().Middleware())) - } - - requestContents := requestflag.ExtractRequestContents(cmd) - - // Translate inner-field aliases in YAML values that came from flags (e.g. - // `--parent '{"alias": val}'` resolving to the canonical inner field). - if bodyMap, ok := requestContents.Body.(map[string]any); ok { - applyDataAliases(cmd, bodyMap) - } - - stdinConsumedByPipe := false - if bodyType != ApplicationOctetStream && !ignoreStdin && isInputPiped() { - pipeData, err := io.ReadAll(os.Stdin) - if err != nil { - return nil, err - } - - if len(pipeData) > 0 { - stdinConsumedByPipe = true - var bodyData any - if err := yaml.Unmarshal(pipeData, &bodyData); err != nil { - return nil, fmt.Errorf("Failed to parse piped data as YAML/JSON:\n%w", err) - } - if bodyMap, ok := bodyData.(map[string]any); ok { - applyDataAliases(cmd, 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 { - requestContents.Body = bodyData - } - } - } - } - - 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 { - names = append(names, flag.Names()[0]) - } - return nil, fmt.Errorf("Required flags %q not set\nRun '%s --help' for usage information", strings.Join(names, ", "), cmd.FullName()) - } - } - - // For flags marked as FileInput (type: string, format: binary), the value is always - // a file path. Wrap with FilePathValue so embedFiles reads the file automatically - // without requiring the user to type the "@" prefix. This handles both values set - // via explicit CLI flags and values that arrived via piped YAML/JSON data. - wrapFileInputValues(cmd, &requestContents) - - // Determine stdin availability for FileInput params that use "-". - var stdinReader onceStdinReader - if ignoreStdin { - stdinReader = onceStdinReader{failureReason: "stdin is already being used for the request body"} - } else if stdinConsumedByPipe { - stdinReader = onceStdinReader{failureReason: "stdin was already consumed by piped YAML/JSON input"} - } else { - stdinReader = onceStdinReader{stdinReader: os.Stdin} - } - - // Embed files passed as "@file.jpg" in the request body, headers, and query: - embedStyle := EmbedText - if bodyType == ApplicationOctetStream || bodyType == MultipartFormEncoded { - embedStyle = EmbedIOReader - } - - if embedded, err := embedFiles(requestContents.Body, embedStyle, &stdinReader); err != nil { - return nil, err - } else { - requestContents.Body = embedded - } - - if headersWithFiles, err := embedFiles(requestContents.Headers, EmbedText, &stdinReader); err != nil { - return nil, err - } else { - requestContents.Headers = headersWithFiles.(map[string]any) - } - if queriesWithFiles, err := embedFiles(requestContents.Queries, EmbedText, &stdinReader); err != nil { - return nil, err - } else { - requestContents.Queries = queriesWithFiles.(map[string]any) - } - - querySettings := apiquery.QuerySettings{ - NestedFormat: nestedFormat, - ArrayFormat: arrayFormat, - } - - // Add query parameters: - if values, err := apiquery.MarshalWithSettings(requestContents.Queries, querySettings); err != nil { - return nil, err - } else { - for k, vs := range values { - if len(vs) == 0 { - options = append(options, option.WithQueryDel(k)) - } else { - options = append(options, option.WithQuery(k, vs[0])) - for _, v := range vs[1:] { - options = append(options, option.WithQueryAdd(k, v)) - } - } - } - } - - // Add header parameters - headerSettings := apiquery.QuerySettings{ - NestedFormat: apiquery.NestedQueryFormatDots, - ArrayFormat: apiquery.ArrayQueryFormatRepeat, - } - if values, err := apiquery.MarshalWithSettings(requestContents.Headers, headerSettings); err != nil { - return nil, err - } else { - for k, vs := range values { - if len(vs) == 0 { - options = append(options, option.WithHeaderDel(k)) - } else { - options = append(options, option.WithHeader(k, vs[0])) - for _, v := range vs[1:] { - options = append(options, option.WithHeaderAdd(k, v)) - } - } - } - } - - switch bodyType { - case EmptyBody: - break - case MultipartFormEncoded: - buf := new(bytes.Buffer) - writer := multipart.NewWriter(buf) - - // For multipart/form-encoded, we need a map structure - bodyMap, ok := requestContents.Body.(map[string]any) - if !ok { - return nil, fmt.Errorf("Cannot send a non-map value to a form-encoded endpoint: %v\n", requestContents.Body) - } - encodingFormat := apiform.FormatRepeat - if err := apiform.MarshalWithSettings(bodyMap, writer, encodingFormat); err != nil { - return nil, err - } - if err := writer.Close(); err != nil { - return nil, err - } - options = append(options, option.WithRequestBody(writer.FormDataContentType(), buf)) - - case ApplicationJSON: - bodyBytes, err := json.Marshal(requestContents.Body) - if err != nil { - return nil, err - } - options = append(options, option.WithRequestBody("application/json", bodyBytes)) - - case ApplicationOctetStream: - // If there is a body root parameter, that will handle setting the request body, we don't need to do it here. - for _, flag := range cmd.Flags { - if toSend, ok := flag.(requestflag.InRequest); ok && toSend.IsBodyRoot() { - return options, nil - } - } - if bodyBytes, ok := requestContents.Body.([]byte); ok { - options = append(options, option.WithRequestBody("application/octet-stream", bodyBytes)) - } else if bodyStr, ok := requestContents.Body.(string); ok { - options = append(options, option.WithRequestBody("application/octet-stream", []byte(bodyStr))) - } else { - return nil, fmt.Errorf("Unsupported body for application/octet-stream: %v", requestContents.Body) - } - - default: - panic("Invalid body content type!") - } - - return options, nil -} - -// FilePathValue is a string wrapper that marks a value as a file path whose contents should be read -// and embedded in the request. Unlike a regular string, embedFilesValue always treats a FilePathValue -// as a file path without needing the "@" prefix. -type FilePathValue string - -// fileUpload wraps an io.Reader with filename and content-type metadata for -// use as a multipart form part. The apiform encoder detects the Filename and -// ContentType methods and uses them to populate the Content-Disposition -// filename and the Content-Type header on the part. -type fileUpload struct { - io.Reader // apiform checks for reader and reads its contents during encode - filename string - contentType string -} - -func (f fileUpload) Filename() string { return f.filename } -func (f fileUpload) ContentType() string { return f.contentType } -func (f fileUpload) Close() error { - if c, ok := f.Reader.(io.Closer); ok { - return c.Close() - } - return nil -} - -// openFileUpload opens the file at path and returns a fileUpload whose filename -// is the path's basename and whose content type is derived from the file -// extension (falling back to application/octet-stream when unknown). -func openFileUpload(path string) (fileUpload, error) { - file, err := os.Open(path) - if err != nil { - return fileUpload{}, err - } - contentType := mime.TypeByExtension(filepath.Ext(path)) - if contentType == "" { - contentType = "application/octet-stream" - } - return fileUpload{ - Reader: file, - filename: filepath.Base(path), - contentType: contentType, - }, nil -} - -// applyDataAliases rewrites keys in a body map based on flag `DataAliases` metadata. For top-level flags, -// `{alias: value}` becomes `{canonical: value}`. For inner flags (those registered under an outer flag -// via WithInnerFlags), the alias translation is also applied to the nested map under the outer flag's -// body path, so values like `--parent '{"alias": val}'` resolve to the canonical inner field name. -func applyDataAliases(cmd *cli.Command, bodyMap map[string]any) { - for _, flag := range cmd.Flags { - // Inner flags: rewrite aliases inside the nested map under the outer flag's body path. - if inner, ok := flag.(requestflag.HasOuterFlag); ok { - outer, outerOk := inner.GetOuterFlag().(requestflag.InRequest) - if !outerOk { - continue - } - if nested, ok := bodyMap[outer.GetBodyPath()].(map[string]any); ok && inner.GetInnerField() != "" { - rewriteAliases(nested, inner.GetInnerField(), inner.GetDataAliases()) - } - continue - } - // Top-level flags: rewrite aliases in the body map. - if inReq, ok := flag.(requestflag.InRequest); ok && inReq.GetBodyPath() != "" { - rewriteAliases(bodyMap, inReq.GetBodyPath(), inReq.GetDataAliases()) - } - } -} - -// rewriteAliases replaces each alias key in m with the canonical key, preserving the value. The -// "canonical" key is the name the API itself expects (the OpenAPI property/field name) — e.g. for -// a top-level flag, the parameter's BodyPath; for an inner flag, the inner field name. Aliases are -// the user-facing alternate names declared via x-stainless-cli-data-alias. -func rewriteAliases(m map[string]any, canonical string, aliases []string) { - for _, alias := range aliases { - if alias == "" || alias == canonical { - continue - } - if val, exists := m[alias]; exists { - m[canonical] = val - delete(m, alias) - } - } -} - -// wrapFileInputValues replaces string values for FileInput flags (type: string, format: binary) with -// FilePathValue sentinel values. embedFilesValue recognizes FilePathValue and reads the file contents -// directly, so the user doesn't need to type the "@" prefix. This handles both values set via explicit -// CLI flags and values that arrived via piped YAML/JSON data. -func wrapFileInputValues(cmd *cli.Command, contents *requestflag.RequestContents) { - bodyMap, _ := contents.Body.(map[string]any) - - for _, flag := range cmd.Flags { - inReq, ok := flag.(requestflag.InRequest) - if !ok || !inReq.IsFileInput() || inReq.IsBodyRoot() { - continue - } - - // Wrap values set via explicit CLI flags. - if flag.IsSet() { - if wrapped, changed := wrapFileInputValue(flag.Get()); changed { - if bodyPath := inReq.GetBodyPath(); bodyPath != "" { - if bodyMap != nil { - bodyMap[bodyPath] = wrapped - } - } else if queryPath := inReq.GetQueryPath(); queryPath != "" { - contents.Queries[queryPath] = wrapped - } else if headerPath := inReq.GetHeaderPath(); headerPath != "" { - contents.Headers[headerPath] = wrapped - } - } - } - - // Wrap values that arrived via piped YAML/JSON data in the body map. - if bodyPath := inReq.GetBodyPath(); bodyPath != "" && bodyMap != nil { - if value, exists := bodyMap[bodyPath]; exists { - if wrapped, changed := wrapFileInputValue(value); changed { - bodyMap[bodyPath] = wrapped - } - } - } - } -} - -func wrapFileInputValue(value any) (any, bool) { - switch v := value.(type) { - case string: - if v == "" { - return value, false - } - return FilePathValue(v), true - - case []string: - result := make([]any, len(v)) - for i, s := range v { - result[i] = FilePathValue(s) - } - return result, true - - case []any: - result := make([]any, len(v)) - for i, elem := range v { - if s, ok := elem.(string); ok { - result[i] = FilePathValue(s) - } else { - result[i] = elem - } - } - return result, true - - default: - return value, false - } -} diff --git a/pkg/cmd/flagoptions_test.go b/pkg/cmd/flagoptions_test.go deleted file mode 100644 index 00734ca..0000000 --- a/pkg/cmd/flagoptions_test.go +++ /dev/null @@ -1,392 +0,0 @@ -package cmd - -import ( - "encoding/base64" - "io" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsUTF8TextFile(t *testing.T) { - t.Parallel() - - tests := []struct { - content []byte - expected bool - }{ - {[]byte("Hello, world!"), true}, - {[]byte(`{"key": "value"}`), true}, - {[]byte(``), true}, - {[]byte(`function test() {}`), true}, - {[]byte{0xFF, 0xD8, 0xFF, 0xE0}, false}, // JPEG header - {[]byte{0x00, 0x01, 0xFF, 0xFE}, false}, // binary - {[]byte("Hello \xFF\xFE"), false}, // invalid UTF-8 - {[]byte("Hello ☺️"), true}, // emoji - {[]byte{}, true}, // empty - } - - for _, tt := range tests { - require.Equal(t, tt.expected, isUTF8TextFile(tt.content)) - } -} - -func TestEmbedFiles(t *testing.T) { - t.Parallel() - - // Create temporary directory for test files - tmpDir := t.TempDir() - - // Create test files - configContent := "host=localhost\nport=8080" - templateContent := "Hello" - dataContent := `{"key": "value"}` - - writeTestFile(t, tmpDir, "config.txt", configContent) - writeTestFile(t, tmpDir, "template.html", templateContent) - writeTestFile(t, tmpDir, "data.json", dataContent) - jpegHeader := []byte{0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46} - writeTestFile(t, tmpDir, "image.jpg", string(jpegHeader)) - - tests := []struct { - name string - input any - want any - wantErr bool - }{ - { - name: "map[string]any with file references", - input: map[string]any{ - "config": "@" + filepath.Join(tmpDir, "config.txt"), - "template": "@file://" + filepath.Join(tmpDir, "template.html"), - "count": 42, - }, - want: map[string]any{ - "config": configContent, - "template": templateContent, - "count": 42, - }, - wantErr: false, - }, - { - name: "map[string]string with file references", - input: map[string]any{ - "config": "@" + filepath.Join(tmpDir, "config.txt"), - "name": "test", - }, - want: map[string]any{ - "config": configContent, - "name": "test", - }, - wantErr: false, - }, - { - name: "[]any with file references", - input: []any{ - "@" + filepath.Join(tmpDir, "config.txt"), - 42, - true, - "@file://" + filepath.Join(tmpDir, "data.json"), - }, - want: []any{ - configContent, - 42, - true, - dataContent, - }, - wantErr: false, - }, - { - name: "[]string with file references", - input: []any{ - "@" + filepath.Join(tmpDir, "config.txt"), - "normal string", - }, - want: []any{ - configContent, - "normal string", - }, - wantErr: false, - }, - { - name: "nested structures", - input: map[string]any{ - "outer": map[string]any{ - "inner": []any{ - "@" + filepath.Join(tmpDir, "config.txt"), - map[string]any{ - "data": "@" + filepath.Join(tmpDir, "data.json"), - }, - }, - }, - }, - want: map[string]any{ - "outer": map[string]any{ - "inner": []any{ - configContent, - map[string]any{ - "data": dataContent, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "base64 encoding", - input: map[string]any{ - "encoded": "@data://" + filepath.Join(tmpDir, "config.txt"), - "image": "@" + filepath.Join(tmpDir, "image.jpg"), - }, - want: map[string]any{ - "encoded": base64.StdEncoding.EncodeToString([]byte(configContent)), - "image": base64.StdEncoding.EncodeToString(jpegHeader), - }, - wantErr: false, - }, - { - name: "non-existent file with @ prefix", - input: map[string]any{ - "missing": "@file.txt", - }, - want: nil, - wantErr: true, - }, - { - name: "non-file-like thing with @ prefix", - input: map[string]any{ - "username": "@user", - "favorite_symbol": "@", - }, - want: map[string]any{ - "username": "@user", - "favorite_symbol": "@", - }, - wantErr: false, - }, - { - name: "non-existent file with @file:// prefix (error)", - input: map[string]any{ - "missing": "@file:///nonexistent/file.txt", - }, - want: nil, - wantErr: true, - }, - { - name: "escaping", - input: map[string]any{ - "simple": "\\@file.txt", - "file": "\\@file://file.txt", - "data": "\\@data://file.txt", - "keep_escape": "user\\@example.com", - }, - want: map[string]any{ - "simple": "@file.txt", - "file": "@file://file.txt", - "data": "@data://file.txt", - "keep_escape": "user\\@example.com", - }, - wantErr: false, - }, - { - name: "primitive types", - input: map[string]any{ - "int": 123, - "float": 45.67, - "bool": true, - "null": nil, - "string": "no prefix", - "email": "user@example.com", - }, - want: map[string]any{ - "int": 123, - "float": 45.67, - "bool": true, - "null": nil, - "string": "no prefix", - "email": "user@example.com", - }, - wantErr: false, - }, - { - name: "[]int values unchanged", - input: []int{1, 2, 3, 4, 5}, - want: []any{1, 2, 3, 4, 5}, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name+" text", func(t *testing.T) { - t.Parallel() - - got, err := embedFiles(tt.input, EmbedText, nil) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, tt.want, got) - } - }) - - t.Run(tt.name+" io.Reader", func(t *testing.T) { - t.Parallel() - - _, err := embedFiles(tt.input, EmbedIOReader, nil) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestEmbedFilesStdin(t *testing.T) { - t.Parallel() - - t.Run("FilePathValueDash", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{stdinReader: strings.NewReader("stdin content")} - - withEmbedded, err := embedFiles(map[string]any{"file": FilePathValue("-")}, EmbedText, stdin) - require.NoError(t, err) - require.Equal(t, map[string]any{"file": "stdin content"}, withEmbedded) - }) - - t.Run("FilePathValueDevStdin", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{stdinReader: strings.NewReader("stdin content")} - - withEmbedded, err := embedFiles(map[string]any{"file": FilePathValue("/dev/stdin")}, EmbedText, stdin) - require.NoError(t, err) - require.Equal(t, map[string]any{"file": "stdin content"}, withEmbedded) - }) - - t.Run("MultipleFilePathValueDashesError", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{stdinReader: strings.NewReader("stdin content")} - - _, err := embedFiles(map[string]any{ - "file1": FilePathValue("-"), - "file2": FilePathValue("-"), - }, EmbedText, stdin) - require.Error(t, err) - require.Contains(t, err.Error(), "already been read") - }) - - t.Run("FilePathValueDashUnavailableStdin", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{failureReason: "stdin is already being used for the request body"} - - _, err := embedFiles(map[string]any{"file": FilePathValue("-")}, EmbedText, stdin) - require.Error(t, err) - require.Contains(t, err.Error(), "cannot read from stdin") - require.Contains(t, err.Error(), "request body") - }) - - t.Run("AtDashEmbedText", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{stdinReader: strings.NewReader("piped content")} - - withEmbedded, err := embedFiles(map[string]any{"data": "@-"}, EmbedText, stdin) - require.NoError(t, err) - require.Equal(t, map[string]any{"data": "piped content"}, withEmbedded) - }) - - t.Run("AtDashEmbedIOReader", func(t *testing.T) { - t.Parallel() - - stdin := &onceStdinReader{stdinReader: strings.NewReader("piped content")} - - withEmbedded, err := embedFiles(map[string]any{"data": "@-"}, EmbedIOReader, stdin) - require.NoError(t, err) - - withEmbeddedMap := withEmbedded.(map[string]any) - r := withEmbeddedMap["data"].(io.ReadCloser) - - content, err := io.ReadAll(r) - require.NoError(t, err) - require.Equal(t, "piped content", string(content)) - }) - - t.Run("FilePathValueRealFile", func(t *testing.T) { - t.Parallel() - - tmpDir := t.TempDir() - writeTestFile(t, tmpDir, "test.txt", "file content") - - stdin := &onceStdinReader{stdinReader: strings.NewReader("unused stdin")} - - withEmbedded, err := embedFiles(map[string]any{"file": FilePathValue(filepath.Join(tmpDir, "test.txt"))}, EmbedText, stdin) - require.NoError(t, err) - require.Equal(t, map[string]any{"file": "file content"}, withEmbedded) - }) -} - -// TestEmbedFilesUploadMetadata verifies that EmbedIOReader mode wraps file readers with filename and -// content-type metadata so the multipart encoder populates `Content-Disposition` and `Content-Type` headers. -func TestEmbedFilesUploadMetadata(t *testing.T) { - t.Parallel() - - tmpDir := t.TempDir() - writeTestFile(t, tmpDir, "hello.txt", "hi") - writeTestFile(t, tmpDir, "page.html", "") - writeTestFile(t, tmpDir, "blob.bin", "\x00\x01") - - cases := []struct { - basename string - wantContentType string - }{ - {"hello.txt", "text/plain; charset=utf-8"}, - {"page.html", "text/html; charset=utf-8"}, - {"blob.bin", "application/octet-stream"}, - } - - for _, tc := range cases { - t.Run("AtPrefix_"+tc.basename, func(t *testing.T) { - t.Parallel() - - path := filepath.Join(tmpDir, tc.basename) - withEmbedded, err := embedFiles(map[string]any{"file": "@" + path}, EmbedIOReader, nil) - require.NoError(t, err) - - upload, ok := withEmbedded.(map[string]any)["file"].(fileUpload) - require.True(t, ok, "expected fileUpload, got %T", withEmbedded.(map[string]any)["file"]) - require.Equal(t, tc.basename, upload.Filename()) - require.Equal(t, upload.ContentType(), tc.wantContentType) - require.NoError(t, upload.Close()) - }) - - t.Run("FilePathValue_"+tc.basename, func(t *testing.T) { - t.Parallel() - - path := filepath.Join(tmpDir, tc.basename) - withEmbedded, err := embedFiles(map[string]any{"file": FilePathValue(path)}, EmbedIOReader, nil) - require.NoError(t, err) - - upload, ok := withEmbedded.(map[string]any)["file"].(fileUpload) - require.True(t, ok, "expected fileUpload, got %T", withEmbedded.(map[string]any)["file"]) - require.Equal(t, tc.basename, upload.Filename()) - require.Equal(t, upload.ContentType(), tc.wantContentType) - require.NoError(t, upload.Close()) - }) - } -} - -func writeTestFile(t *testing.T, dir, filename, content string) { - t.Helper() - - path := filepath.Join(dir, filename) - - err := os.WriteFile(path, []byte(content), 0644) - require.NoError(t, err, "failed to write test file %s", path) -} diff --git a/pkg/cmd/machine.go b/pkg/cmd/machine.go deleted file mode 100644 index c8636a8..0000000 --- a/pkg/cmd/machine.go +++ /dev/null @@ -1,539 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesCreate = cli.Command{ - Name: "create", - Usage: "Create machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[int64]{ - Name: "memory-mib", - Usage: "Memory in MiB.", - Required: true, - BodyPath: "memory_mib", - }, - &requestflag.Flag[int64]{ - Name: "storage-gib", - Usage: "Storage in GiB.", - Required: true, - BodyPath: "storage_gib", - }, - &requestflag.Flag[float64]{ - Name: "vcpu", - Usage: "CPU in vCPUs.", - Required: true, - BodyPath: "vcpu", - }, - &requestflag.Flag[string]{ - Name: "autosleep", - Usage: `Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable.`, - BodyPath: "autosleep", - }, - }, - Action: handleMachinesCreate, - HideHelpCommand: true, -} - -var machinesRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - }, - Action: handleMachinesRetrieve, - HideHelpCommand: true, -} - -var machinesUpdate = cli.Command{ - Name: "update", - Usage: "Update machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "autosleep", - Usage: `Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable.`, - BodyPath: "autosleep", - }, - &requestflag.Flag[int64]{ - Name: "memory-mib", - Usage: "Memory in MiB.", - BodyPath: "memory_mib", - }, - &requestflag.Flag[int64]{ - Name: "storage-gib", - Usage: "Storage in GiB.", - BodyPath: "storage_gib", - }, - &requestflag.Flag[float64]{ - Name: "vcpu", - Usage: "CPU in vCPUs.", - BodyPath: "vcpu", - }, - }, - Action: handleMachinesUpdate, - HideHelpCommand: true, -} - -var machinesList = cli.Command{ - Name: "list", - Usage: "List machines", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesList, - HideHelpCommand: true, -} - -var machinesDelete = cli.Command{ - Name: "delete", - Usage: "Destroy machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - }, - Action: handleMachinesDelete, - HideHelpCommand: true, -} - -var machinesSleep = cli.Command{ - Name: "sleep", - Usage: "Sleep a running machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - }, - Action: handleMachinesSleep, - HideHelpCommand: true, -} - -var machinesWake = cli.Command{ - Name: "wake", - Usage: "Wake a sleeping machine", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - }, - Action: handleMachinesWake, - HideHelpCommand: true, -} - -var machinesWatch = cli.Command{ - Name: "watch", - Usage: "Streams machine lifecycle updates over Server-Sent Events. Each `status` event\ncontains a full `LifecycleResponse` payload. The stream closes after the machine\nreaches its current desired state.", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &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: handleMachinesWatch, - HideHelpCommand: true, -} - -func handleMachinesCreate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineNewParams{} - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.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: "machines create", - Transform: transform, - }) -} - -func handleMachinesRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineGetParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Get(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: "machines retrieve", - Transform: transform, - }) -} - -func handleMachinesUpdate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineUpdateParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Update(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: "machines update", - Transform: transform, - }) -} - -func handleMachinesList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineListParams{} - - 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.Machines.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: "machines list", - Transform: transform, - }) - } else { - iter := client.Machines.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: "machines list", - Transform: transform, - }) - } -} - -func handleMachinesDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Delete(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: "machines delete", - Transform: transform, - }) -} - -func handleMachinesSleep(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineSleepParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Sleep(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: "machines sleep", - Transform: transform, - }) -} - -func handleMachinesWake(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineWakeParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Wake(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: "machines wake", - Transform: transform, - }) -} - -func handleMachinesWatch(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineWatchParams{ - MachineID: cmd.Value("machine-id").(string), - } - - format := cmd.Root().String("format") - explicitFormat := cmd.Root().IsSet("format") - transform := cmd.Root().String("transform") - stream := client.Machines.WatchStreaming(ctx, 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: "machines watch", - Transform: transform, - }) -} diff --git a/pkg/cmd/machine_test.go b/pkg/cmd/machine_test.go deleted file mode 100644 index ae554fc..0000000 --- a/pkg/cmd/machine_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "create", - "--memory-mib", "0", - "--storage-gib", "0", - "--vcpu", "0", - "--autosleep", "autosleep", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "memory_mib: 0\n" + - "storage_gib: 0\n" + - "vcpu: 0\n" + - "autosleep: autosleep\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "create", - ) - }) -} - -func TestMachinesRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "retrieve", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesUpdate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "update", - "--machine-id", "dm-3", - "--autosleep", "autosleep", - "--memory-mib", "0", - "--storage-gib", "0", - "--vcpu", "0", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "autosleep: autosleep\n" + - "memory_mib: 0\n" + - "storage_gib: 0\n" + - "vcpu: 0\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "update", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "list", - "--max-items", "10", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "delete", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesSleep(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "sleep", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesWake(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "wake", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesWatch(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "watch", - "--max-items", "10", - "--machine-id", "dm-3", - "--last-event-id", "Last-Event-ID", - ) - }) -} diff --git a/pkg/cmd/machineartifact.go b/pkg/cmd/machineartifact.go deleted file mode 100644 index 53575c6..0000000 --- a/pkg/cmd/machineartifact.go +++ /dev/null @@ -1,227 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesArtifactsRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get artifact", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "artifact-id", - Required: true, - PathParam: "artifact_id", - }, - }, - Action: handleMachinesArtifactsRetrieve, - HideHelpCommand: true, -} - -var machinesArtifactsList = cli.Command{ - Name: "list", - Usage: "List artifacts", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesArtifactsList, - HideHelpCommand: true, -} - -var machinesArtifactsDelete = cli.Command{ - Name: "delete", - Usage: "Delete artifact", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "artifact-id", - Required: true, - PathParam: "artifact_id", - }, - }, - Action: handleMachinesArtifactsDelete, - HideHelpCommand: true, -} - -func handleMachinesArtifactsRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineArtifactGetParams{ - MachineID: cmd.Value("machine-id").(string), - ArtifactID: cmd.Value("artifact-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Artifacts.Get(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: "machines artifacts retrieve", - Transform: transform, - }) -} - -func handleMachinesArtifactsList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineArtifactListParams{ - MachineID: cmd.Value("machine-id").(string), - } - - 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.Machines.Artifacts.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: "machines artifacts list", - Transform: transform, - }) - } else { - iter := client.Machines.Artifacts.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: "machines artifacts list", - Transform: transform, - }) - } -} - -func handleMachinesArtifactsDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineArtifactDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - ArtifactID: cmd.Value("artifact-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Artifacts.Delete(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: "machines artifacts delete", - Transform: transform, - }) -} diff --git a/pkg/cmd/machineartifact_test.go b/pkg/cmd/machineartifact_test.go deleted file mode 100644 index 0ed1bd8..0000000 --- a/pkg/cmd/machineartifact_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesArtifactsRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "artifacts", "retrieve", - "--machine-id", "dm-3", - "--artifact-id", "artifact_id", - ) - }) -} - -func TestMachinesArtifactsList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "artifacts", "list", - "--max-items", "10", - "--machine-id", "dm-3", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesArtifactsDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "artifacts", "delete", - "--machine-id", "dm-3", - "--artifact-id", "artifact_id", - ) - }) -} diff --git a/pkg/cmd/machineexecution.go b/pkg/cmd/machineexecution.go deleted file mode 100644 index b6310e8..0000000 --- a/pkg/cmd/machineexecution.go +++ /dev/null @@ -1,460 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesExecutionsCreate = cli.Command{ - Name: "create", - Usage: "Create execution", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[any]{ - Name: "command", - Required: true, - BodyPath: "command", - }, - &requestflag.Flag[string]{ - Name: "cwd", - BodyPath: "cwd", - }, - &requestflag.Flag[map[string]any]{ - Name: "env", - BodyPath: "env", - }, - &requestflag.Flag[string]{ - Name: "stdin", - BodyPath: "stdin", - }, - &requestflag.Flag[int64]{ - Name: "timeout-ms", - BodyPath: "timeout_ms", - }, - }, - Action: handleMachinesExecutionsCreate, - HideHelpCommand: true, -} - -var machinesExecutionsRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get execution", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "execution-id", - Required: true, - PathParam: "execution_id", - }, - }, - Action: handleMachinesExecutionsRetrieve, - HideHelpCommand: true, -} - -var machinesExecutionsList = cli.Command{ - Name: "list", - Usage: "List executions", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesExecutionsList, - HideHelpCommand: true, -} - -var machinesExecutionsDelete = cli.Command{ - Name: "delete", - Usage: "Delete execution", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "execution-id", - Required: true, - PathParam: "execution_id", - }, - }, - Action: handleMachinesExecutionsDelete, - HideHelpCommand: true, -} - -var machinesExecutionsEvents = cli.Command{ - Name: "events", - Usage: "List execution events", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "execution-id", - Required: true, - PathParam: "execution_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesExecutionsEvents, - HideHelpCommand: true, -} - -var machinesExecutionsOutput = cli.Command{ - Name: "output", - Usage: "Get execution output", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "execution-id", - Required: true, - PathParam: "execution_id", - }, - }, - Action: handleMachinesExecutionsOutput, - HideHelpCommand: true, -} - -func handleMachinesExecutionsCreate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionNewParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Executions.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: "machines executions create", - Transform: transform, - }) -} - -func handleMachinesExecutionsRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionGetParams{ - MachineID: cmd.Value("machine-id").(string), - ExecutionID: cmd.Value("execution-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Executions.Get(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: "machines executions retrieve", - Transform: transform, - }) -} - -func handleMachinesExecutionsList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionListParams{ - MachineID: cmd.Value("machine-id").(string), - } - - 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.Machines.Executions.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: "machines executions list", - Transform: transform, - }) - } else { - iter := client.Machines.Executions.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: "machines executions list", - Transform: transform, - }) - } -} - -func handleMachinesExecutionsDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - ExecutionID: cmd.Value("execution-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Executions.Delete(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: "machines executions delete", - Transform: transform, - }) -} - -func handleMachinesExecutionsEvents(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionEventsParams{ - MachineID: cmd.Value("machine-id").(string), - ExecutionID: cmd.Value("execution-id").(string), - } - - 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.Machines.Executions.Events(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: "machines executions events", - Transform: transform, - }) - } else { - iter := client.Machines.Executions.EventsAutoPaging(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: "machines executions events", - Transform: transform, - }) - } -} - -func handleMachinesExecutionsOutput(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineExecutionOutputParams{ - MachineID: cmd.Value("machine-id").(string), - ExecutionID: cmd.Value("execution-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Executions.Output(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: "machines executions output", - Transform: transform, - }) -} diff --git a/pkg/cmd/machineexecution_test.go b/pkg/cmd/machineexecution_test.go deleted file mode 100644 index e345c07..0000000 --- a/pkg/cmd/machineexecution_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesExecutionsCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "create", - "--machine-id", "dm-3", - "--command", "[string]", - "--cwd", "cwd", - "--env", "{foo: string}", - "--stdin", "stdin", - "--timeout-ms", "0", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "command:\n" + - " - string\n" + - "cwd: cwd\n" + - "env:\n" + - " foo: string\n" + - "stdin: stdin\n" + - "timeout_ms: 0\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "executions", "create", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesExecutionsRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "retrieve", - "--machine-id", "dm-3", - "--execution-id", "execution_id", - ) - }) -} - -func TestMachinesExecutionsList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "list", - "--max-items", "10", - "--machine-id", "dm-3", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesExecutionsDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "delete", - "--machine-id", "dm-3", - "--execution-id", "execution_id", - ) - }) -} - -func TestMachinesExecutionsEvents(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "events", - "--max-items", "10", - "--machine-id", "dm-3", - "--execution-id", "execution_id", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesExecutionsOutput(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "executions", "output", - "--machine-id", "dm-3", - "--execution-id", "execution_id", - ) - }) -} diff --git a/pkg/cmd/machinepreview.go b/pkg/cmd/machinepreview.go deleted file mode 100644 index ce2865b..0000000 --- a/pkg/cmd/machinepreview.go +++ /dev/null @@ -1,300 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesPreviewsCreate = cli.Command{ - Name: "create", - Usage: "Create preview", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[int64]{ - Name: "port", - Required: true, - BodyPath: "port", - }, - &requestflag.Flag[string]{ - Name: "protocol", - Usage: `Allowed values: "http", "https".`, - BodyPath: "protocol", - }, - &requestflag.Flag[string]{ - Name: "visibility", - Usage: `Allowed values: "public", "private", "org".`, - BodyPath: "visibility", - }, - }, - Action: handleMachinesPreviewsCreate, - HideHelpCommand: true, -} - -var machinesPreviewsRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get preview", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "preview-id", - Required: true, - PathParam: "preview_id", - }, - }, - Action: handleMachinesPreviewsRetrieve, - HideHelpCommand: true, -} - -var machinesPreviewsList = cli.Command{ - Name: "list", - Usage: "List previews", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesPreviewsList, - HideHelpCommand: true, -} - -var machinesPreviewsDelete = cli.Command{ - Name: "delete", - Usage: "Delete preview", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "preview-id", - Required: true, - PathParam: "preview_id", - }, - }, - Action: handleMachinesPreviewsDelete, - HideHelpCommand: true, -} - -func handleMachinesPreviewsCreate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachinePreviewNewParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Previews.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: "machines previews create", - Transform: transform, - }) -} - -func handleMachinesPreviewsRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachinePreviewGetParams{ - MachineID: cmd.Value("machine-id").(string), - PreviewID: cmd.Value("preview-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Previews.Get(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: "machines previews retrieve", - Transform: transform, - }) -} - -func handleMachinesPreviewsList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachinePreviewListParams{ - MachineID: cmd.Value("machine-id").(string), - } - - 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.Machines.Previews.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: "machines previews list", - Transform: transform, - }) - } else { - iter := client.Machines.Previews.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: "machines previews list", - Transform: transform, - }) - } -} - -func handleMachinesPreviewsDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachinePreviewDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - PreviewID: cmd.Value("preview-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Previews.Delete(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: "machines previews delete", - Transform: transform, - }) -} diff --git a/pkg/cmd/machinepreview_test.go b/pkg/cmd/machinepreview_test.go deleted file mode 100644 index 2ee25b2..0000000 --- a/pkg/cmd/machinepreview_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesPreviewsCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "previews", "create", - "--machine-id", "dm-3", - "--port", "0", - "--protocol", "http", - "--visibility", "public", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "port: 0\n" + - "protocol: http\n" + - "visibility: public\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "previews", "create", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesPreviewsRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "previews", "retrieve", - "--machine-id", "dm-3", - "--preview-id", "preview_id", - ) - }) -} - -func TestMachinesPreviewsList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "previews", "list", - "--max-items", "10", - "--machine-id", "dm-3", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesPreviewsDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "previews", "delete", - "--machine-id", "dm-3", - "--preview-id", "preview_id", - ) - }) -} diff --git a/pkg/cmd/machinessh.go b/pkg/cmd/machinessh.go deleted file mode 100644 index 5fa8004..0000000 --- a/pkg/cmd/machinessh.go +++ /dev/null @@ -1,290 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesSSHCreate = cli.Command{ - Name: "create", - Usage: "Create SSH session", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "public-key", - Required: true, - BodyPath: "public_key", - }, - }, - Action: handleMachinesSSHCreate, - HideHelpCommand: true, -} - -var machinesSSHRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get SSH session", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "session-id", - Required: true, - PathParam: "session_id", - }, - }, - Action: handleMachinesSSHRetrieve, - HideHelpCommand: true, -} - -var machinesSSHList = cli.Command{ - Name: "list", - Usage: "List SSH sessions", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesSSHList, - HideHelpCommand: true, -} - -var machinesSSHDelete = cli.Command{ - Name: "delete", - Usage: "Delete SSH session", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "session-id", - Required: true, - PathParam: "session_id", - }, - }, - Action: handleMachinesSSHDelete, - HideHelpCommand: true, -} - -func handleMachinesSSHCreate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineSSHNewParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.SSH.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: "machines ssh create", - Transform: transform, - }) -} - -func handleMachinesSSHRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineSSHGetParams{ - MachineID: cmd.Value("machine-id").(string), - SessionID: cmd.Value("session-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.SSH.Get(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: "machines ssh retrieve", - Transform: transform, - }) -} - -func handleMachinesSSHList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineSSHListParams{ - MachineID: cmd.Value("machine-id").(string), - } - - 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.Machines.SSH.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: "machines ssh list", - Transform: transform, - }) - } else { - iter := client.Machines.SSH.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: "machines ssh list", - Transform: transform, - }) - } -} - -func handleMachinesSSHDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineSSHDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - SessionID: cmd.Value("session-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.SSH.Delete(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: "machines ssh delete", - Transform: transform, - }) -} diff --git a/pkg/cmd/machinessh_test.go b/pkg/cmd/machinessh_test.go deleted file mode 100644 index 850ea5b..0000000 --- a/pkg/cmd/machinessh_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesSSHCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "ssh", "create", - "--machine-id", "dm-3", - "--public-key", "public_key", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("public_key: public_key") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "ssh", "create", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesSSHRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "ssh", "retrieve", - "--machine-id", "dm-3", - "--session-id", "session_id", - ) - }) -} - -func TestMachinesSSHList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "ssh", "list", - "--max-items", "10", - "--machine-id", "dm-3", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesSSHDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "ssh", "delete", - "--machine-id", "dm-3", - "--session-id", "session_id", - ) - }) -} diff --git a/pkg/cmd/machineterminal.go b/pkg/cmd/machineterminal.go deleted file mode 100644 index b6ebddc..0000000 --- a/pkg/cmd/machineterminal.go +++ /dev/null @@ -1,307 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var machinesTerminalsCreate = cli.Command{ - Name: "create", - Usage: "Create terminal", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[int64]{ - Name: "height", - Required: true, - BodyPath: "height", - }, - &requestflag.Flag[int64]{ - Name: "width", - Required: true, - BodyPath: "width", - }, - &requestflag.Flag[string]{ - Name: "cwd", - BodyPath: "cwd", - }, - &requestflag.Flag[map[string]any]{ - Name: "env", - BodyPath: "env", - }, - &requestflag.Flag[string]{ - Name: "shell", - BodyPath: "shell", - }, - }, - Action: handleMachinesTerminalsCreate, - HideHelpCommand: true, -} - -var machinesTerminalsRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get terminal", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "terminal-id", - Required: true, - PathParam: "terminal_id", - }, - }, - Action: handleMachinesTerminalsRetrieve, - HideHelpCommand: true, -} - -var machinesTerminalsList = cli.Command{ - Name: "list", - Usage: "List terminals", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "cursor", - QueryPath: "cursor", - }, - &requestflag.Flag[int64]{ - Name: "limit", - QueryPath: "limit", - }, - &requestflag.Flag[int64]{ - Name: "max-items", - Usage: "The maximum number of items to return (use -1 for unlimited).", - }, - }, - Action: handleMachinesTerminalsList, - HideHelpCommand: true, -} - -var machinesTerminalsDelete = cli.Command{ - Name: "delete", - Usage: "Delete terminal", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Required: true, - PathParam: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "terminal-id", - Required: true, - PathParam: "terminal_id", - }, - }, - Action: handleMachinesTerminalsDelete, - HideHelpCommand: true, -} - -func handleMachinesTerminalsCreate(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineTerminalNewParams{ - MachineID: cmd.Value("machine-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Terminals.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: "machines terminals create", - Transform: transform, - }) -} - -func handleMachinesTerminalsRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineTerminalGetParams{ - MachineID: cmd.Value("machine-id").(string), - TerminalID: cmd.Value("terminal-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Terminals.Get(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: "machines terminals retrieve", - Transform: transform, - }) -} - -func handleMachinesTerminalsList(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineTerminalListParams{ - MachineID: cmd.Value("machine-id").(string), - } - - 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.Machines.Terminals.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: "machines terminals list", - Transform: transform, - }) - } else { - iter := client.Machines.Terminals.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: "machines terminals list", - Transform: transform, - }) - } -} - -func handleMachinesTerminalsDelete(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.MachineTerminalDeleteParams{ - MachineID: cmd.Value("machine-id").(string), - TerminalID: cmd.Value("terminal-id").(string), - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Machines.Terminals.Delete(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: "machines terminals delete", - Transform: transform, - }) -} diff --git a/pkg/cmd/machineterminal_test.go b/pkg/cmd/machineterminal_test.go deleted file mode 100644 index a745ada..0000000 --- a/pkg/cmd/machineterminal_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestMachinesTerminalsCreate(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "terminals", "create", - "--machine-id", "dm-3", - "--height", "0", - "--width", "0", - "--cwd", "cwd", - "--env", "{foo: string}", - "--shell", "shell", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "height: 0\n" + - "width: 0\n" + - "cwd: cwd\n" + - "env:\n" + - " foo: string\n" + - "shell: shell\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "machines", "terminals", "create", - "--machine-id", "dm-3", - ) - }) -} - -func TestMachinesTerminalsRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "terminals", "retrieve", - "--machine-id", "dm-3", - "--terminal-id", "terminal_id", - ) - }) -} - -func TestMachinesTerminalsList(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "terminals", "list", - "--max-items", "10", - "--machine-id", "dm-3", - "--cursor", "cursor", - "--limit", "0", - ) - }) -} - -func TestMachinesTerminalsDelete(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "machines", "terminals", "delete", - "--machine-id", "dm-3", - "--terminal-id", "terminal_id", - ) - }) -} diff --git a/pkg/cmd/nesting.go b/pkg/cmd/nesting.go deleted file mode 100644 index 2af92d8..0000000 --- a/pkg/cmd/nesting.go +++ /dev/null @@ -1,45 +0,0 @@ -// Re-parents Stainless's colon-flattened subresource commands into -// space-separated subcommands. `dedalus machines:executions create` becomes -// `dedalus machines executions create`. The colon form is removed. -// -// Stainless's Go CLI generator emits nested subresources as colon-delimited -// top-level commands; native nesting is a tracked feature request (Slack -// thread with Nick, 2026-04). Once that ships, delete this file. - -package cmd - -import ( - "strings" - - "github.com/urfave/cli/v3" -) - -func init() { - Command.Commands = renestColonCommands(Command.Commands) -} - -// renestColonCommands moves any command whose Name contains ':' under the -// matching prefix command at the same level. Applied recursively so future -// `a:b:c` shapes work without changes. -func renestColonCommands(cmds []*cli.Command) []*cli.Command { - byName := map[string]*cli.Command{} - for _, c := range cmds { - byName[c.Name] = c - } - kept := cmds[:0] - for _, c := range cmds { - if i := strings.IndexByte(c.Name, ':'); i > 0 { - if parent, ok := byName[c.Name[:i]]; ok { - child := *c - child.Name = c.Name[i+1:] - parent.Commands = append(parent.Commands, &child) - continue - } - } - kept = append(kept, c) - } - for _, c := range kept { - c.Commands = renestColonCommands(c.Commands) - } - return kept -} diff --git a/pkg/cmd/nesting_test.go b/pkg/cmd/nesting_test.go deleted file mode 100644 index 4ddff0e..0000000 --- a/pkg/cmd/nesting_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/urfave/cli/v3" -) - -// findSub returns the immediate subcommand of c with the given name, or nil. -func findSub(c *cli.Command, name string) *cli.Command { - for _, sc := range c.Commands { - if sc.Name == name { - return sc - } - } - return nil -} - -func TestRenestColonCommands_one_level(t *testing.T) { - t.Parallel() - - cmds := []*cli.Command{ - {Name: "machines", Commands: []*cli.Command{{Name: "create"}, {Name: "list"}}}, - {Name: "machines:executions", Commands: []*cli.Command{{Name: "create"}, {Name: "list"}}}, - {Name: "machines:ssh", Commands: []*cli.Command{{Name: "create"}}}, - } - - out := renestColonCommands(cmds) - - require.Len(t, out, 1, "only `machines` should remain at the top level") - assert.Equal(t, "machines", out[0].Name) - - // Parent's own commands are preserved. - assert.NotNil(t, findSub(out[0], "create")) - assert.NotNil(t, findSub(out[0], "list")) - - // Subresources are re-parented and renamed. - execs := findSub(out[0], "executions") - require.NotNil(t, execs, "machines:executions should become machines > executions") - assert.NotNil(t, findSub(execs, "create")) - assert.NotNil(t, findSub(execs, "list")) - - ssh := findSub(out[0], "ssh") - require.NotNil(t, ssh) - assert.NotNil(t, findSub(ssh, "create")) -} - -func TestRenestColonCommands_recurses_for_nested_subresources(t *testing.T) { - t.Parallel() - - // Hypothetical future shape: `a:b:c`. Today Stainless only emits one - // colon, but if that ever changes we want the wrapper to keep working. - cmds := []*cli.Command{ - {Name: "a", Commands: []*cli.Command{ - {Name: "list"}, - {Name: "b", Commands: []*cli.Command{ - {Name: "list"}, - }}, - {Name: "b:c", Commands: []*cli.Command{ - {Name: "create"}, - }}, - }}, - } - - out := renestColonCommands(cmds) - - require.Len(t, out, 1) - a := out[0] - assert.NotNil(t, findSub(a, "list")) - - b := findSub(a, "b") - require.NotNil(t, b) - assert.NotNil(t, findSub(b, "list")) - - c := findSub(b, "c") - require.NotNil(t, c, "b:c should be re-parented under b as `c`") - assert.NotNil(t, findSub(c, "create")) - - // Original colon entry is gone from `a`'s direct children. - assert.Nil(t, findSub(a, "b:c")) -} - -func TestRenestColonCommands_leaves_orphan_colon_commands_alone(t *testing.T) { - t.Parallel() - - // No `foo` parent exists, so `foo:bar` has nothing to attach to and - // must be left at the top level rather than silently dropped. - cmds := []*cli.Command{ - {Name: "foo:bar"}, - } - - out := renestColonCommands(cmds) - require.Len(t, out, 1) - assert.Equal(t, "foo:bar", out[0].Name) -} - -// TestRenestColonCommands_matches_generated_machines_shape pins the wrapper -// against the exact command tree the Stainless generator currently emits -// for the dedalus-cli (see pkg/cmd/cmd.go). If the generator changes shape, -// this test surfaces it. -func TestRenestColonCommands_matches_generated_machines_shape(t *testing.T) { - t.Parallel() - - cmds := []*cli.Command{ - {Name: "machines"}, - {Name: "machines:artifacts"}, - {Name: "machines:previews"}, - {Name: "machines:ssh"}, - {Name: "machines:executions"}, - {Name: "machines:terminals"}, - } - - out := renestColonCommands(cmds) - - require.Len(t, out, 1) - m := out[0] - for _, sub := range []string{"artifacts", "previews", "ssh", "executions", "terminals"} { - assert.NotNil(t, findSub(m, sub), "expected `machines %s` subcommand", sub) - } -} diff --git a/pkg/cmd/ssh.go b/pkg/cmd/ssh.go deleted file mode 100644 index 970c431..0000000 --- a/pkg/cmd/ssh.go +++ /dev/null @@ -1,228 +0,0 @@ -// Implements `dedalus ssh `: -// -// 1. Generate ephemeral ed25519 keypair in a temp directory. -// 2. POST /v1/machines/{machine_id}/ssh with the public key. -// 3. Poll GET until status=ready. -// 4. Write user certificate and host CA to temp files. -// 5. Exec the local ssh binary with explicit trust settings. -// 6. Clean up temp directory on exit (including signal-driven exit). - -package cmd - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "encoding/pem" - "fmt" - "os" - "os/exec" - "os/signal" - "path/filepath" - "strconv" - "syscall" - "time" - - "github.com/dedalus-labs/dedalus-go" - "github.com/urfave/cli/v3" - "golang.org/x/crypto/ssh" -) - -const ( - sshPollInterval = 500 * time.Millisecond - sshPollMax = 120 -) - -func init() { - Command.Commands = append(Command.Commands, &cli.Command{ - Name: "ssh", - Usage: "SSH into a running machine", - UsageText: "dedalus ssh ", - Category: "MACHINE", - Suggest: true, - Flags: []cli.Flag{}, - Action: handleSSH, - HideHelpCommand: true, - }) -} - -func handleSSH(ctx context.Context, cmd *cli.Command) error { - machineID := cmd.Args().First() - if machineID == "" { - return fmt.Errorf("machine_id is required; usage: dedalus ssh ") - } - - client := dedalus.NewClient(getDefaultRequestOptions(cmd)...) - - dir, err := os.MkdirTemp("", "dedalus-ssh-*") - if err != nil { - return fmt.Errorf("create temp directory for ephemeral SSH keys: %w", err) - } - removed := false - remove := func() { - if !removed { - os.RemoveAll(dir) - removed = true - } - } - defer remove() - trapSignals(remove) - - keyPath := filepath.Join(dir, "key") - pubKey, err := genKeypair(keyPath) - if err != nil { - return err - } - - sess, err := awaitSSHSession(ctx, &client, machineID, pubKey) - if err != nil { - return err - } - - conn := sess.Connection - if conn.UserCertificate == "" { - return fmt.Errorf("SSH session %s is ready but the server returned no user certificate; this is a server-side bug", sess.SessionID) - } - if conn.Endpoint == "" { - return fmt.Errorf("SSH session %s is ready but the server returned no endpoint; this is a server-side bug", sess.SessionID) - } - if conn.HostTrust.PublicKey == "" { - return fmt.Errorf("SSH session %s is ready but the server returned no host CA public key; this is a server-side bug", sess.SessionID) - } - - certPath := filepath.Join(dir, "key-cert.pub") - if err := os.WriteFile(certPath, []byte(conn.UserCertificate), 0600); err != nil { - return fmt.Errorf("write certificate: %w", err) - } - - khPath := filepath.Join(dir, "known_hosts") - line := fmt.Sprintf("@cert-authority %s %s\n", conn.HostTrust.HostPattern, conn.HostTrust.PublicKey) - if err := os.WriteFile(khPath, []byte(line), 0600); err != nil { - return fmt.Errorf("write known_hosts: %w", err) - } - - return runSSH(ctx, keyPath, certPath, khPath, conn) -} - -func awaitSSHSession( - ctx context.Context, - client *dedalus.Client, - machineID, pubKey string, -) (*dedalus.SSHSession, error) { - resp, err := client.Machines.SSH.New(ctx, dedalus.MachineSSHNewParams{ - MachineID: machineID, - SSHSessionCreateParams: dedalus.SSHSessionCreateParams{ - PublicKey: pubKey, - }, - }) - if err != nil { - return nil, fmt.Errorf("create ssh session: %w", err) - } - fmt.Fprintf(os.Stderr, "ssh %s: %s\n", resp.SessionID, resp.Status) - - for i := 0; i < sshPollMax; i++ { - switch resp.Status { - case dedalus.SSHSessionStatusReady: - return resp, nil - case dedalus.SSHSessionStatusFailed: - msg := resp.ErrorMessage - if msg == "" { - msg = "no detail from server" - } - return nil, fmt.Errorf("SSH session failed: %s (error_code=%s)", msg, resp.ErrorCode) - case dedalus.SSHSessionStatusExpired: - return nil, fmt.Errorf("SSH session expired before it became ready; try again with a fresh session") - case dedalus.SSHSessionStatusClosed: - return nil, fmt.Errorf("SSH session was closed before it became ready; another client may have deleted it") - } - - delay := sshPollInterval - if resp.RetryAfterMs > 0 { - delay = time.Duration(resp.RetryAfterMs) * time.Millisecond - } - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-time.After(delay): - } - - resp, err = client.Machines.SSH.Get(ctx, dedalus.MachineSSHGetParams{ - MachineID: machineID, - SessionID: resp.SessionID, - }) - if err != nil { - return nil, fmt.Errorf("poll ssh session: %w", err) - } - fmt.Fprintf(os.Stderr, "ssh %s: %s\n", resp.SessionID, resp.Status) - } - return nil, fmt.Errorf("SSH session did not become ready after %d polls (%v); the Dedalus Machine may be unresponsive or the SSH gateway may be down", sshPollMax, time.Duration(sshPollMax)*sshPollInterval) -} - -func runSSH(ctx context.Context, keyPath, certPath, khPath string, conn dedalus.SSHConnection) error { - bin, err := exec.LookPath("ssh") - if err != nil { - return fmt.Errorf("ssh not found in PATH; install OpenSSH and ensure 'ssh' is available in your shell: %w", err) - } - - fmt.Fprintf(os.Stderr, "connecting to %s@%s:%d\n", conn.SSHUsername, conn.Endpoint, conn.Port) - - c := exec.CommandContext(ctx, bin, - "-i", keyPath, - "-o", "CertificateFile="+certPath, - "-o", "UserKnownHostsFile="+khPath, - "-o", "GlobalKnownHostsFile=/dev/null", - "-o", "StrictHostKeyChecking=yes", - "-o", "IdentitiesOnly=yes", - "-p", strconv.FormatInt(conn.Port, 10), - conn.SSHUsername+"@"+conn.Endpoint, - ) - c.Stdin = os.Stdin - c.Stdout = os.Stdout - c.Stderr = os.Stderr - - if err := c.Run(); err != nil { - if exit, ok := err.(*exec.ExitError); ok { - os.Exit(exit.ExitCode()) - } - return err - } - return nil -} - -func genKeypair(keyPath string) (string, error) { - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - return "", fmt.Errorf("generate ed25519 key: %w", err) - } - - privPEM, err := ssh.MarshalPrivateKey(priv, "") - if err != nil { - return "", fmt.Errorf("marshal private key: %w", err) - } - if err := os.WriteFile(keyPath, pem.EncodeToMemory(privPEM), 0600); err != nil { - return "", fmt.Errorf("write private key: %w", err) - } - - sshPub, err := ssh.NewPublicKey(pub) - if err != nil { - return "", fmt.Errorf("convert to ssh public key: %w", err) - } - authorized := string(ssh.MarshalAuthorizedKey(sshPub)) - - if err := os.WriteFile(keyPath+".pub", []byte(authorized), 0644); err != nil { - return "", fmt.Errorf("write public key: %w", err) - } - return authorized, nil -} - -func trapSignals(cleanup func()) { - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-ch - cleanup() - signal.Reset(syscall.SIGINT, syscall.SIGTERM) - p, _ := os.FindProcess(os.Getpid()) - _ = p.Signal(syscall.SIGINT) - }() -} diff --git a/pkg/cmd/ssh_test.go b/pkg/cmd/ssh_test.go deleted file mode 100644 index 734a534..0000000 --- a/pkg/cmd/ssh_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package cmd - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - gossh "golang.org/x/crypto/ssh" -) - -func TestGenKeypair_produces_valid_ed25519(t *testing.T) { - tmpDir := t.TempDir() - keyPath := filepath.Join(tmpDir, "key") - - pubKeyStr, err := genKeypair(keyPath) - require.NoError(t, err) - - // Public key is valid authorized_key format. - pub, _, _, _, err := gossh.ParseAuthorizedKey([]byte(pubKeyStr)) - require.NoError(t, err) - assert.Equal(t, "ssh-ed25519", pub.Type()) - - // Private key file exists with restricted permissions. - info, err := os.Stat(keyPath) - require.NoError(t, err) - assert.Equal(t, os.FileMode(0600), info.Mode().Perm()) - - // Private key is parseable OpenSSH format. - privBytes, err := os.ReadFile(keyPath) - require.NoError(t, err) - assert.True(t, strings.HasPrefix(string(privBytes), "-----BEGIN OPENSSH PRIVATE KEY-----"), - "private key must be OpenSSH format, got: %s", string(privBytes[:40])) - - signer, err := gossh.ParsePrivateKey(privBytes) - require.NoError(t, err) - assert.Equal(t, "ssh-ed25519", signer.PublicKey().Type()) - - // Public key file also written. - pubFileBytes, err := os.ReadFile(keyPath + ".pub") - require.NoError(t, err) - assert.True(t, strings.HasPrefix(string(pubFileBytes), "ssh-ed25519 ")) -} - -func TestGenKeypair_keys_correspond(t *testing.T) { - tmpDir := t.TempDir() - keyPath := filepath.Join(tmpDir, "key") - - pubKeyStr, err := genKeypair(keyPath) - require.NoError(t, err) - - privBytes, err := os.ReadFile(keyPath) - require.NoError(t, err) - signer, err := gossh.ParsePrivateKey(privBytes) - require.NoError(t, err) - - pub, _, _, _, err := gossh.ParseAuthorizedKey([]byte(pubKeyStr)) - require.NoError(t, err) - - // The public key derived from the private key must match. - assert.Equal(t, - gossh.MarshalAuthorizedKey(signer.PublicKey()), - gossh.MarshalAuthorizedKey(pub), - "public key from private key must match the returned authorized key") -} - -func TestGenKeypair_unique_per_call(t *testing.T) { - tmpDir := t.TempDir() - - pub1, err := genKeypair(filepath.Join(tmpDir, "key1")) - require.NoError(t, err) - - pub2, err := genKeypair(filepath.Join(tmpDir, "key2")) - require.NoError(t, err) - - assert.NotEqual(t, pub1, pub2, "each invocation must produce a fresh keypair") -} - -func TestKnownHostsLine_format(t *testing.T) { - hostPattern := "*.ssh.dedaluslabs.ai" - caPublicKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyData" - - line := "@cert-authority " + hostPattern + " " + caPublicKey + "\n" - - assert.True(t, strings.HasPrefix(line, "@cert-authority ")) - assert.Contains(t, line, hostPattern) - assert.Contains(t, line, caPublicKey) - assert.True(t, strings.HasSuffix(line, "\n")) - - // The line is parseable as a known_hosts entry by splitting. - fields := strings.Fields(strings.TrimSpace(line)) - assert.Equal(t, 4, len(fields), "known_hosts cert-authority line should have 4 fields") - assert.Equal(t, "@cert-authority", fields[0]) - assert.Equal(t, hostPattern, fields[1]) - assert.Equal(t, "ssh-ed25519", fields[2]) -} - -func TestSSHArgs_structure(t *testing.T) { - args := []string{ - "-i", "/tmp/key", - "-o", "CertificateFile=/tmp/key-cert.pub", - "-o", "UserKnownHostsFile=/tmp/known_hosts", - "-o", "GlobalKnownHostsFile=/dev/null", - "-o", "StrictHostKeyChecking=yes", - "-o", "IdentitiesOnly=yes", - "-p", "2222", - "workspace@ssh.dedaluslabs.ai", - } - - // Identity file is first. - assert.Equal(t, "-i", args[0]) - - // CertificateFile is set. - certIdx := -1 - for i, a := range args { - if strings.HasPrefix(a, "CertificateFile=") { - certIdx = i - break - } - } - assert.Greater(t, certIdx, 0, "CertificateFile option must be present") - - // StrictHostKeyChecking=yes is enforced (no TOFU). - found := false - for _, a := range args { - if a == "StrictHostKeyChecking=yes" { - found = true - } - } - assert.True(t, found, "StrictHostKeyChecking=yes must be set") - - // IdentitiesOnly=yes prevents ssh-agent from offering other keys. - idOnly := false - for _, a := range args { - if a == "IdentitiesOnly=yes" { - idOnly = true - } - } - assert.True(t, idOnly, "IdentitiesOnly=yes must be set") - - // GlobalKnownHostsFile=/dev/null prevents system-wide trust. - globalNull := false - for _, a := range args { - if a == "GlobalKnownHostsFile=/dev/null" { - globalNull = true - } - } - assert.True(t, globalNull, "GlobalKnownHostsFile must be /dev/null") - - // Last arg is user@host. - last := args[len(args)-1] - assert.Contains(t, last, "@", "last arg must be user@host") -} - -func TestCleanup_removes_temp_directory(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "dedalus-ssh-test-*") - require.NoError(t, err) - - keyPath := filepath.Join(tmpDir, "key") - require.NoError(t, os.WriteFile(keyPath, []byte("secret"), 0600)) - require.NoError(t, os.WriteFile(keyPath+".pub", []byte("public"), 0644)) - - os.RemoveAll(tmpDir) - - _, err = os.Stat(tmpDir) - assert.True(t, os.IsNotExist(err), "temp directory must be removed after cleanup") - _, err = os.Stat(keyPath) - assert.True(t, os.IsNotExist(err), "private key must be removed after cleanup") -} diff --git a/pkg/cmd/startup_update.go b/pkg/cmd/startup_update.go deleted file mode 100644 index 13a49dc..0000000 --- a/pkg/cmd/startup_update.go +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright (c) 2026 Dedalus Labs, Inc. All rights reserved. - -package cmd - -import ( - "bufio" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/charmbracelet/x/term" -) - -/* -The startup update prompt runs before urfave parses or executes the requested -command. Its control flow is: - - 1. Skip non-interactive, CI, help, version, completion, and update commands. - 2. Read the cached release and use it immediately while it is fresh. - 3. Refresh stale or missing cache entries with a bounded network request. - 4. If that refresh fails, compare a previously valid cache entry. - 5. Prompt only when the selected release is newer than the running build. - -Accepting the prompt delegates to the same updater used by `dedalus update`. -The true return value tells the caller not to execute the original command. -*/ - -const ( - dedalusHomeEnv = "DEDALUS_HOME" - disableUpdateCheckEnv = "DEDALUS_NO_UPDATE_CHECK" - startupUpdateCacheFile = "version.json" - // startupUpdateCheckInterval keeps routine invocations off the release API - // while allowing a fresh check during each day of CLI use. - startupUpdateCheckInterval = 20 * time.Hour - // startupUpdateCheckTimeout keeps a slow network from delaying command dispatch. - startupUpdateCheckTimeout = 2 * time.Second -) - -// startupVersionInfo is the complete on-disk cache entry. LastCheckedAt is the -// time of a successful release query, not the time the cache was read. -type startupVersionInfo struct { - LatestVersion string `json:"latest_version"` - LastCheckedAt time.Time `json:"last_checked_at"` -} - -// startupUpdatePrompt owns startup eligibility, release caching, and prompting. -// Function fields isolate terminal, clock, network, and update effects in tests. -type startupUpdatePrompt struct { - stdin io.Reader - stderr io.Writer - getenv func(string) string - homeDir func() (string, error) - now func() time.Time - isInteractive func() bool - latestVersion func(context.Context) (string, error) - runUpdate func(context.Context) error - cachePath string -} - -// MaybeRunStartupUpdate prompts interactive users before command execution. It -// returns true only when the user selected the updater, so callers can stop the -// original command even when the update returns an error. -func MaybeRunStartupUpdate(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error) { - p := newStartupUpdatePrompt(stdin, stdout, stderr) - return p.run(ctx, args) -} - -func newStartupUpdatePrompt(stdin io.Reader, stdout, stderr io.Writer) *startupUpdatePrompt { - p := &startupUpdatePrompt{ - stdin: stdin, - stderr: stderr, - getenv: os.Getenv, - homeDir: os.UserHomeDir, - now: time.Now, - } - p.isInteractive = func() bool { - return terminalReader(stdin) && terminalWriter(stdout) && terminalWriter(stderr) - } - p.latestVersion = func(ctx context.Context) (string, error) { - updater := newUpdater(io.Discard, io.Discard) - return updater.latestVersion(ctx) - } - p.runUpdate = func(ctx context.Context) error { - return newUpdater(stdout, stderr).update(ctx, updateOptions{}) - } - return p -} - -// run returns whether startup processing consumed the original command. -func (p *startupUpdatePrompt) run(ctx context.Context, args []string) (bool, error) { - if p.shouldSkip(args) { - return false, nil - } - - latest, ok := p.upgradeVersion(ctx) - if !ok { - return false, nil - } - - return p.prompt(ctx, latest) -} - -// shouldSkip protects flows that must remain non-interactive. It examines raw -// arguments because startup prompting runs before the CLI parser. -func (p *startupUpdatePrompt) shouldSkip(args []string) bool { - if !p.isInteractive() { - return true - } - if envTruthy(p.getenv("CI")) || envTruthy(p.getenv(disableUpdateCheckEnv)) { - return true - } - if hasHelpOrVersionArg(args) { - return true - } - - switch rootCommandArg(args) { - case "update", "__complete", "@completion", "@manpages", "help": - return true - default: - return false - } -} - -// upgradeVersion selects one release for comparison. A fresh cache avoids the -// network. A stale or missing cache gets one bounded refresh; a successful -// response remains usable when the cache write fails, while a failed refresh -// may still use a previously valid cache entry. -func (p *startupUpdatePrompt) upgradeVersion(ctx context.Context) (string, bool) { - cachePath, err := p.versionCachePath() - if err != nil { - return "", false - } - - info, cacheOK := readStartupVersionInfo(cachePath) - if cacheOK && p.now().Sub(info.LastCheckedAt) < startupUpdateCheckInterval { - return newerStartupVersion(info.LatestVersion) - } - - checkCtx, cancel := context.WithTimeout(ctx, startupUpdateCheckTimeout) - defer cancel() - - latest, err := p.latestVersion(checkCtx) - if err == nil { - if err := writeStartupVersionInfo(cachePath, startupVersionInfo{ - LatestVersion: latest, - LastCheckedAt: p.now(), - }); err != nil { - return newerStartupVersion(latest) - } - return newerStartupVersion(latest) - } - - if cacheOK { - return newerStartupVersion(info.LatestVersion) - } - return "", false -} - -// prompt returns true only after the user accepts the update. Input failures and -// negative answers leave the original command eligible to run. -func (p *startupUpdatePrompt) prompt(ctx context.Context, latest string) (bool, error) { - fmt.Fprintf(p.stderr, "A new Dedalus CLI is available.\n\n") - fmt.Fprintf(p.stderr, "Current: %s\n", versionTag(Version)) - fmt.Fprintf(p.stderr, "Latest: %s\n\n", versionTag(latest)) - fmt.Fprint(p.stderr, "Update now?\n") - fmt.Fprint(p.stderr, "> Yes\n") - fmt.Fprint(p.stderr, " Not now\n\n") - fmt.Fprint(p.stderr, "Press Enter to update, or type n then Enter to skip: ") - - answer, err := bufio.NewReader(p.stdin).ReadString('\n') - if err != nil && !errors.Is(err, io.EOF) { - fmt.Fprintln(p.stderr) - return false, nil - } - if errors.Is(err, io.EOF) && strings.TrimSpace(answer) == "" { - fmt.Fprintln(p.stderr) - return false, nil - } - if shouldRunStartupUpdate(answer) { - fmt.Fprintln(p.stderr) - return true, p.runUpdate(ctx) - } - - fmt.Fprintln(p.stderr) - return false, nil -} - -func (p *startupUpdatePrompt) versionCachePath() (string, error) { - if p.cachePath != "" { - return p.cachePath, nil - } - home := strings.TrimSpace(p.getenv(dedalusHomeEnv)) - if home == "" { - userHome, err := p.homeDir() - if err != nil { - return "", err - } - home = filepath.Join(userHome, ".dedalus") - } - return filepath.Join(home, startupUpdateCacheFile), nil -} - -// readStartupVersionInfo treats unreadable, malformed, and incomplete data as a -// cache miss. The caller then decides whether a network refresh is possible. -func readStartupVersionInfo(path string) (startupVersionInfo, bool) { - data, err := os.ReadFile(path) - if err != nil { - return startupVersionInfo{}, false - } - var info startupVersionInfo - if err := json.Unmarshal(data, &info); err != nil { - return startupVersionInfo{}, false - } - if strings.TrimSpace(info.LatestVersion) == "" || info.LastCheckedAt.IsZero() { - return startupVersionInfo{}, false - } - return info, true -} - -func writeStartupVersionInfo(path string, info startupVersionInfo) error { - data, err := json.Marshal(info) - if err != nil { - return err - } - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return err - } - return os.WriteFile(path, append(data, '\n'), 0644) -} - -func newerStartupVersion(latest string) (string, bool) { - if isNewerVersion(latest, Version) { - return versionTag(latest), true - } - return "", false -} - -func shouldRunStartupUpdate(answer string) bool { - answer = strings.ToLower(strings.TrimSpace(answer)) - return answer == "" || answer == "y" || answer == "yes" -} - -// rootCommandArg finds the first positional root command without invoking the -// CLI parser. Root flags that consume a following value must be skipped here. -func rootCommandArg(args []string) string { - for i := 1; i < len(args); i++ { - arg := args[i] - if arg == "--" { - return "" - } - if strings.HasPrefix(arg, "-") { - if rootFlagTakesValue(arg) && !strings.Contains(arg, "=") { - i++ - } - continue - } - return arg - } - return "" -} - -// rootFlagTakesValue mirrors value-bearing flags on the Stainless-generated root -// command. New root flags must be added here or their value may be mistaken for -// a command. -func rootFlagTakesValue(arg string) bool { - name := strings.TrimLeft(strings.SplitN(arg, "=", 2)[0], "-") - switch name { - case "api-key", "base-url", "dedalus-org-id", "format", "format-error", "transform", "transform-error", "x-api-key": - return true - default: - return false - } -} - -func hasHelpOrVersionArg(args []string) bool { - for _, arg := range args[1:] { - switch arg { - case "-h", "--help", "-v", "--version": - return true - } - } - return false -} - -func envTruthy(value string) bool { - switch strings.ToLower(strings.TrimSpace(value)) { - case "1", "true", "yes", "on": - return true - default: - return false - } -} - -func terminalReader(r io.Reader) bool { - f, ok := r.(*os.File) - return ok && term.IsTerminal(f.Fd()) -} - -func terminalWriter(w io.Writer) bool { - f, ok := w.(*os.File) - return ok && term.IsTerminal(f.Fd()) -} - -type parsedVersion struct { - major int - minor int - patch int - prerelease string -} - -// isNewerVersion compares the numeric release tuple, then the prerelease suffix. -// Tags outside that grammar compare as normalized strings; any difference is -// treated as an available update. -func isNewerVersion(candidate, current string) bool { - candidateVersion, okCandidate := parseVersion(candidate) - currentVersion, okCurrent := parseVersion(current) - if !okCandidate || !okCurrent { - return !sameVersion(candidate, current) - } - - if candidateVersion.major != currentVersion.major { - return candidateVersion.major > currentVersion.major - } - if candidateVersion.minor != currentVersion.minor { - return candidateVersion.minor > currentVersion.minor - } - if candidateVersion.patch != currentVersion.patch { - return candidateVersion.patch > currentVersion.patch - } - if candidateVersion.prerelease == currentVersion.prerelease { - return false - } - if candidateVersion.prerelease == "" { - return true - } - if currentVersion.prerelease == "" { - return false - } - return candidateVersion.prerelease > currentVersion.prerelease -} - -// parseVersion accepts three numeric components and an optional prerelease -// suffix. Prerelease suffixes are compared lexicographically by isNewerVersion. -func parseVersion(version string) (parsedVersion, bool) { - version = strings.TrimPrefix(strings.TrimSpace(version), "v") - if version == "" { - return parsedVersion{}, false - } - - mainVersion, prerelease, _ := strings.Cut(version, "-") - parts := strings.Split(mainVersion, ".") - if len(parts) != 3 { - return parsedVersion{}, false - } - - major, err := strconv.Atoi(parts[0]) - if err != nil { - return parsedVersion{}, false - } - minor, err := strconv.Atoi(parts[1]) - if err != nil { - return parsedVersion{}, false - } - patch, err := strconv.Atoi(parts[2]) - if err != nil { - return parsedVersion{}, false - } - - return parsedVersion{ - major: major, - minor: minor, - patch: patch, - prerelease: prerelease, - }, true -} diff --git a/pkg/cmd/startup_update_test.go b/pkg/cmd/startup_update_test.go deleted file mode 100644 index bece302..0000000 --- a/pkg/cmd/startup_update_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) 2026 Dedalus Labs, Inc. All rights reserved. - -package cmd - -import ( - "bytes" - "context" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestStartupUpdatePromptUsesFreshCacheAndRunsUpdate(t *testing.T) { - now := time.Date(2026, 6, 24, 12, 0, 0, 0, time.UTC) - cachePath := filepath.Join(t.TempDir(), startupUpdateCacheFile) - if err := writeStartupVersionInfo(cachePath, startupVersionInfo{ - LatestVersion: "v9.9.9", - LastCheckedAt: now, - }); err != nil { - t.Fatalf("writeStartupVersionInfo() returned unexpected error: %v", err) - } - - var stderr bytes.Buffer - ranUpdate := false - prompt := newTestStartupUpdatePrompt(t, cachePath, "\n", &stderr) - prompt.now = func() time.Time { return now } - prompt.latestVersion = func(context.Context) (string, error) { - t.Fatal("fresh cache should not fetch latest version") - return "", nil - } - prompt.runUpdate = func(context.Context) error { - ranUpdate = true - return nil - } - - updated, err := prompt.run(context.Background(), []string{"dedalus", "machines", "list"}) - if err != nil { - t.Fatalf("startup update prompt returned unexpected error: %v", err) - } - if !updated { - t.Fatal("startup update prompt did not report that update ran") - } - if !ranUpdate { - t.Fatal("startup update prompt did not run updater") - } - - got := stderr.String() - for _, want := range []string{ - "A new Dedalus CLI is available.", - "Current: v" + Version, - "Latest: v9.9.9", - "Update now?\n> Yes\n Not now", - } { - if !strings.Contains(got, want) { - t.Errorf("prompt output = %q, want substring %q", got, want) - } - } -} - -func TestStartupUpdatePromptDeclinesUpdate(t *testing.T) { - now := time.Date(2026, 6, 24, 12, 0, 0, 0, time.UTC) - cachePath := filepath.Join(t.TempDir(), startupUpdateCacheFile) - if err := writeStartupVersionInfo(cachePath, startupVersionInfo{ - LatestVersion: "v9.9.9", - LastCheckedAt: now, - }); err != nil { - t.Fatalf("writeStartupVersionInfo() returned unexpected error: %v", err) - } - - var stderr bytes.Buffer - prompt := newTestStartupUpdatePrompt(t, cachePath, "n\n", &stderr) - prompt.now = func() time.Time { return now } - prompt.runUpdate = func(context.Context) error { - t.Fatal("declining the prompt should not run updater") - return nil - } - - updated, err := prompt.run(context.Background(), []string{"dedalus", "machines", "list"}) - if err != nil { - t.Fatalf("startup update prompt returned unexpected error: %v", err) - } - if updated { - t.Fatal("startup update prompt reported an update after user declined") - } - if got, want := stderr.String(), "Update now?\n> Yes\n Not now"; !strings.Contains(got, want) { - t.Errorf("prompt output = %q, want substring %q", got, want) - } -} - -func TestStartupUpdatePromptSkipsNonInteractiveLaunch(t *testing.T) { - cachePath := filepath.Join(t.TempDir(), startupUpdateCacheFile) - if err := writeStartupVersionInfo(cachePath, startupVersionInfo{ - LatestVersion: "v9.9.9", - LastCheckedAt: time.Now(), - }); err != nil { - t.Fatalf("writeStartupVersionInfo() returned unexpected error: %v", err) - } - - var stderr bytes.Buffer - prompt := newTestStartupUpdatePrompt(t, cachePath, "\n", &stderr) - prompt.isInteractive = func() bool { return false } - prompt.runUpdate = func(context.Context) error { - t.Fatal("noninteractive launch should not run updater") - return nil - } - - updated, err := prompt.run(context.Background(), []string{"dedalus", "machines", "list"}) - if err != nil { - t.Fatalf("startup update prompt returned unexpected error: %v", err) - } - if updated { - t.Fatal("noninteractive launch reported an update") - } - if got := stderr.String(); got != "" { - t.Errorf("noninteractive prompt output = %q, want empty", got) - } -} - -func TestStartupUpdatePromptRefreshesStaleCache(t *testing.T) { - now := time.Date(2026, 6, 24, 12, 0, 0, 0, time.UTC) - cachePath := filepath.Join(t.TempDir(), startupUpdateCacheFile) - if err := writeStartupVersionInfo(cachePath, startupVersionInfo{ - LatestVersion: "v" + Version, - LastCheckedAt: now.Add(-startupUpdateCheckInterval - time.Minute), - }); err != nil { - t.Fatalf("writeStartupVersionInfo() returned unexpected error: %v", err) - } - - var stderr bytes.Buffer - prompt := newTestStartupUpdatePrompt(t, cachePath, "n\n", &stderr) - prompt.now = func() time.Time { return now } - prompt.latestVersion = func(context.Context) (string, error) { - return "v9.9.9", nil - } - - updated, err := prompt.run(context.Background(), []string{"dedalus", "machines", "list"}) - if err != nil { - t.Fatalf("startup update prompt returned unexpected error: %v", err) - } - if updated { - t.Fatal("declining refreshed prompt reported an update") - } - - info, ok := readStartupVersionInfo(cachePath) - if !ok { - t.Fatal("expected refreshed startup version cache") - } - if info.LatestVersion != "v9.9.9" { - t.Errorf("refreshed latest version = %q, want v9.9.9", info.LatestVersion) - } - if !info.LastCheckedAt.Equal(now) { - t.Errorf("refreshed last_checked_at = %s, want %s", info.LastCheckedAt, now) - } -} - -func TestRootCommandArgSkipsRootUpdateOnly(t *testing.T) { - t.Parallel() - - if got, want := rootCommandArg([]string{"dedalus", "--format", "json", "update"}), "update"; got != want { - t.Errorf("rootCommandArg(root update) = %q, want %q", got, want) - } - if got, want := rootCommandArg([]string{"dedalus", "machines", "update"}), "machines"; got != want { - t.Errorf("rootCommandArg(nested update) = %q, want %q", got, want) - } -} - -func TestIsNewerVersion(t *testing.T) { - t.Parallel() - - tests := []struct { - candidate string - current string - want bool - }{ - {candidate: "v0.4.1", current: "0.4.0", want: true}, - {candidate: "v0.4.0", current: "0.4.0", want: false}, - {candidate: "v0.3.9", current: "0.4.0", want: false}, - {candidate: "v1.0.0-beta", current: "v1.0.0", want: false}, - {candidate: "v1.0.0", current: "v1.0.0-beta", want: true}, - } - - for _, tt := range tests { - if got := isNewerVersion(tt.candidate, tt.current); got != tt.want { - t.Errorf("isNewerVersion(%q, %q) = %t, want %t", tt.candidate, tt.current, got, tt.want) - } - } -} - -func newTestStartupUpdatePrompt(t *testing.T, cachePath string, stdin string, stderr *bytes.Buffer) *startupUpdatePrompt { - t.Helper() - - return &startupUpdatePrompt{ - stdin: strings.NewReader(stdin), - stderr: stderr, - getenv: func(string) string { return "" }, - homeDir: func() (string, error) { return t.TempDir(), nil }, - now: time.Now, - isInteractive: func() bool { return true }, - latestVersion: func(context.Context) (string, error) { return "v" + Version, nil }, - runUpdate: func(context.Context) error { return nil }, - cachePath: cachePath, - } -} diff --git a/pkg/cmd/suggest.go b/pkg/cmd/suggest.go deleted file mode 100644 index b4b637c..0000000 --- a/pkg/cmd/suggest.go +++ /dev/null @@ -1,126 +0,0 @@ -package cmd - -import ( - "fmt" - "math" - "slices" - "strings" - - "github.com/urfave/cli/v3" -) - -// This entire file is mostly taken from urfave/cli/v3's source, with the exception of suggestCommand which is -// modified for a nicer error message. - -// jaroDistance is the measure of similarity between two strings. It returns a -// value between 0 and 1, where 1 indicates identical strings and 0 indicates -// completely different strings. -// -// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro.go. -func jaroDistance(a, b string) float64 { - if len(a) == 0 && len(b) == 0 { - return 1 - } - if len(a) == 0 || len(b) == 0 { - return 0 - } - - lenA := float64(len(a)) - lenB := float64(len(b)) - hashA := make([]bool, len(a)) - hashB := make([]bool, len(b)) - maxDistance := int(math.Max(0, math.Floor(math.Max(lenA, lenB)/2.0)-1)) - - var matches float64 - for i := 0; i < len(a); i++ { - start := int(math.Max(0, float64(i-maxDistance))) - end := int(math.Min(lenB-1, float64(i+maxDistance))) - - for j := start; j <= end; j++ { - if hashB[j] { - continue - } - if a[i] == b[j] { - hashA[i] = true - hashB[j] = true - matches++ - break - } - } - } - if matches == 0 { - return 0 - } - - var transpositions float64 - var j int - for i := 0; i < len(a); i++ { - if !hashA[i] { - continue - } - for !hashB[j] { - j++ - } - if a[i] != b[j] { - transpositions++ - } - j++ - } - - transpositions /= 2 - return ((matches / lenA) + (matches / lenB) + ((matches - transpositions) / matches)) / 3.0 -} - -// jaroWinkler is more accurate when strings have a common prefix up to a -// defined maximum length. -// -// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro-winkler.go. -func jaroWinkler(a, b string) float64 { - const ( - boostThreshold = 0.7 - prefixSize = 4 - ) - jaroDist := jaroDistance(a, b) - if jaroDist <= boostThreshold { - return jaroDist - } - - prefix := int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b))))) - - var prefixMatch float64 - for i := 0; i < prefix; i++ { - if a[i] == b[i] { - prefixMatch++ - } else { - break - } - } - return jaroDist + 0.1*prefixMatch*(1.0-jaroDist) -} - -// suggestCommand takes a list of commands and a provided string to suggest a -// command name -func suggestCommand(commands []*cli.Command, provided string) string { - distance := 0.0 - var lineage []*cli.Command - for _, command := range commands { - for _, name := range command.Names() { - newDistance := jaroWinkler(name, provided) - if newDistance > distance { - distance = newDistance - lineage = command.Lineage() - } - } - } - - var parts []string - for _, command := range lineage { - parts = append(parts, command.Name) - } - slices.Reverse(parts) - return fmt.Sprintf("Did you mean '%s'?", strings.Join(parts, " ")) -} - -func init() { - cli.SuggestCommand = suggestCommand -} diff --git a/pkg/cmd/update.go b/pkg/cmd/update.go deleted file mode 100644 index 278c3c0..0000000 --- a/pkg/cmd/update.go +++ /dev/null @@ -1,377 +0,0 @@ -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/urfave/cli/v3" -) - -/* -The update command delegates installation to the mechanism that owns the -running binary. Its control flow is: - - 1. Fetch the newest GitHub release and stop if it matches the current build. - 2. Stop after reporting versions when --check is set. - 3. Classify the executable from its platform, resolved path, and installer - evidence. - 4. Run the matching installer, or print manual instructions when ownership - cannot be proved. - -Homebrew ownership requires both a path inside Homebrew's prefix and an -installed dedalus cask. Direct installs are recognized by their default path, -the marker written by install.sh, or DEDALUS_INSTALL_DIR. Detection checks -Homebrew first because its cask also installs a binary named dedalus. -*/ - -const ( - installScriptURL = "https://raw.githubusercontent.com/dedalus-labs/dedalus-cli/main/scripts/install.sh" - installPS1URL = "https://raw.githubusercontent.com/dedalus-labs/dedalus-cli/main/scripts/install.ps1" - latestReleaseURL = "https://api.github.com/repos/dedalus-labs/dedalus-cli/releases/latest" - defaultUnixBinDir = ".local/bin" - installMarkerFile = ".dedalus-cli-install" -) - -var updateCommand = cli.Command{ - Name: "update", - Usage: "Update the Dedalus CLI", - UsageText: "dedalus update [--check]", - Category: "CLI", - Suggest: true, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "check", - Usage: "Check for the latest release without installing it.", - }, - }, - Action: handleUpdate, - HideHelpCommand: true, -} - -func init() { - Command.Commands = append(Command.Commands, &updateCommand) -} - -type updateOptions struct { - checkOnly bool -} - -// installMethod identifies the update action selected for the running binary. -type installMethod int - -const ( - installMethodCurl installMethod = iota - installMethodHomebrewCask - installMethodWindows - installMethodUnknown -) - -// updater owns release discovery, install detection, and command execution. -// Function fields isolate operating-system effects so tests can replace them. -type updater struct { - goos string - stdout io.Writer - stderr io.Writer - executable func() (string, error) - lookPath func(string) (string, error) - commandOutput func(context.Context, string, ...string) (string, error) - runCommand func(context.Context, []string, string, ...string) error - httpClient *http.Client - latestURL string -} - -// detectedInstall records the selected action and the executable, when available. -type detectedInstall struct { - method installMethod - exe string -} - -type latestRelease struct { - TagName string `json:"tag_name"` -} - -func handleUpdate(ctx context.Context, c *cli.Command) error { - return newUpdater(c.Root().Writer, c.Root().ErrWriter).update(ctx, updateOptions{ - checkOnly: c.Bool("check"), - }) -} - -func newUpdater(stdout, stderr io.Writer) *updater { - if stdout == nil { - stdout = os.Stdout - } - if stderr == nil { - stderr = os.Stderr - } - u := &updater{ - goos: runtime.GOOS, - stdout: stdout, - stderr: stderr, - executable: os.Executable, - lookPath: exec.LookPath, - httpClient: http.DefaultClient, - latestURL: latestReleaseURL, - } - u.commandOutput = u.defaultCommandOutput - u.runCommand = u.defaultRunCommand - return u -} - -func (u *updater) update(ctx context.Context, opts updateOptions) error { - latest, err := u.latestVersion(ctx) - if err != nil { - return err - } - - current := versionTag(Version) - fmt.Fprintf(u.stdout, "Current version: %s\n", current) - fmt.Fprintf(u.stdout, "Latest version: %s\n", latest) - - if sameVersion(current, latest) { - fmt.Fprintf(u.stdout, "dedalus is already at %s\n", current) - return nil - } - if opts.checkOnly { - return nil - } - - install := u.detectInstall(ctx) - - switch install.method { - case installMethodHomebrewCask: - return u.updateWithHomebrew(ctx) - case installMethodWindows: - return u.printWindowsUpdateCommand(install.exe) - case installMethodCurl: - return u.updateWithInstallScript(ctx, install.exe) - case installMethodUnknown: - return u.printManualUpdate() - default: - return fmt.Errorf("unknown install method %d", install.method) - } -} - -func (u *updater) latestVersion(ctx context.Context) (string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.latestURL, nil) - if err != nil { - return "", fmt.Errorf("build latest release request: %w", err) - } - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", fmt.Sprintf("Dedalus/CLI %s", Version)) - - resp, err := u.httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("fetch latest release: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("fetch latest release: got %s", resp.Status) - } - - var release latestRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return "", fmt.Errorf("decode latest release: %w", err) - } - tag := strings.TrimSpace(release.TagName) - if tag == "" { - return "", fmt.Errorf("fetch latest release: missing tag_name") - } - return versionTag(tag), nil -} - -// detectInstall returns unknown unless one install mechanism has enough evidence. -// The checks are ordered from strongest ownership evidence to weakest. -func (u *updater) detectInstall(ctx context.Context) detectedInstall { - exe, err := u.executablePath() - if err != nil { - if u.goos == "windows" { - return detectedInstall{method: installMethodWindows} - } - return detectedInstall{method: installMethodUnknown} - } - - if u.goos == "windows" { - return detectedInstall{method: installMethodWindows, exe: exe} - } - if u.isHomebrewCask(ctx, exe) { - return detectedInstall{method: installMethodHomebrewCask, exe: exe} - } - if u.isLikelyCurlInstall(exe) { - return detectedInstall{method: installMethodCurl, exe: exe} - } - return detectedInstall{method: installMethodUnknown, exe: exe} -} - -// isHomebrewCask requires the executable path and Homebrew's cask database to -// agree. Merely finding brew on PATH does not establish ownership. -func (u *updater) isHomebrewCask(ctx context.Context, exe string) bool { - if u.goos != "darwin" { - return false - } - brew, err := u.lookPath("brew") - if err != nil { - return false - } - prefix, err := u.commandOutput(ctx, brew, "--prefix") - if err != nil || !pathWithin(exe, strings.TrimSpace(prefix)) { - return false - } - return u.commandSucceeds(ctx, brew, "list", "--cask", "--versions", "dedalus") -} - -// isLikelyCurlInstall recognizes direct installer layouts. The default path -// covers installs created before the marker existed. The marker persists custom -// install directories; the environment recognizes an active installer override. -func (u *updater) isLikelyCurlInstall(exe string) bool { - if u.goos == "windows" { - return false - } - if filepath.Base(exe) != "dedalus" { - return false - } - home, err := os.UserHomeDir() - if err == nil && pathWithin(exe, filepath.Join(home, defaultUnixBinDir)) { - return true - } - if hasInstallScriptMarker(exe) { - return true - } - installDir := os.Getenv("DEDALUS_INSTALL_DIR") - return installDir != "" && pathWithin(exe, installDir) -} - -func (u *updater) updateWithHomebrew(ctx context.Context) error { - brew, err := u.lookPath("brew") - if err != nil { - return fmt.Errorf("find brew: %w", err) - } - args := []string{"upgrade", "--cask", "dedalus"} - fmt.Fprintf(u.stdout, "Running: brew %s\n", strings.Join(args, " ")) - return u.runCommand(ctx, nil, brew, args...) -} - -// updateWithInstallScript pins the published installer to the executable's -// current directory so a custom direct install is updated in place. -func (u *updater) updateWithInstallScript(ctx context.Context, exe string) error { - installDir := filepath.Dir(exe) - fmt.Fprintf(u.stdout, "Running installer with DEDALUS_INSTALL_DIR=%s\n", installDir) - return u.runCommand(ctx, []string{"DEDALUS_INSTALL_DIR=" + installDir}, "bash", "-c", "curl -fsSL "+installScriptURL+" | bash") -} - -func (u *updater) printWindowsUpdateCommand(exe string) error { - installDir := "" - if exe != "" { - installDir = filepath.Dir(exe) - } - fmt.Fprintln(u.stdout, "Windows does not allow replacing the running dedalus.exe process.") - fmt.Fprintln(u.stdout, "Run this from a new PowerShell session:") - if installDir != "" { - fmt.Fprintf(u.stdout, " $env:DEDALUS_INSTALL_DIR = '%s'; irm %s | iex\n", powerShellSingleQuoted(installDir), installPS1URL) - return nil - } - fmt.Fprintf(u.stdout, " irm %s | iex\n", installPS1URL) - return nil -} - -func (u *updater) printManualUpdate() error { - fmt.Fprintln(u.stdout, "Could not determine how this dedalus binary was installed.") - fmt.Fprintln(u.stdout, "Update with the package manager or installer you originally used.") - return nil -} - -// executablePath resolves symlinks when possible so detection inspects the -// installation path instead of the path of a launcher or shim. -func (u *updater) executablePath() (string, error) { - exe, err := u.executable() - if err != nil { - return "", fmt.Errorf("locate current executable: %w", err) - } - if resolved, err := filepath.EvalSymlinks(exe); err == nil { - exe = resolved - } - return exe, nil -} - -func (u *updater) commandSucceeds(ctx context.Context, name string, args ...string) bool { - _, err := u.commandOutput(ctx, name, args...) - return err == nil -} - -func (u *updater) defaultCommandOutput(ctx context.Context, name string, args ...string) (string, error) { - c := exec.CommandContext(ctx, name, args...) - out, err := c.Output() - return string(out), err -} - -func (u *updater) defaultRunCommand(ctx context.Context, env []string, name string, args ...string) error { - c := exec.CommandContext(ctx, name, args...) - if len(env) > 0 { - c.Env = append(os.Environ(), env...) - } - c.Stdin = os.Stdin - c.Stdout = u.stdout - c.Stderr = u.stderr - return c.Run() -} - -func versionTag(version string) string { - version = strings.TrimSpace(version) - if version == "" || strings.HasPrefix(version, "v") { - return version - } - return "v" + version -} - -func sameVersion(a, b string) bool { - return strings.TrimPrefix(versionTag(a), "v") == strings.TrimPrefix(versionTag(b), "v") -} - -// pathWithin uses path components instead of string prefixes, so directories -// such as /opt/homebrew-old cannot match /opt/homebrew. -func pathWithin(path, dir string) bool { - if strings.TrimSpace(dir) == "" { - return false - } - absPath, ok := comparablePath(path) - if !ok { - return false - } - absDir, ok := comparablePath(dir) - if !ok { - return false - } - rel, err := filepath.Rel(absDir, absPath) - if err != nil { - return false - } - return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator))) -} - -func comparablePath(path string) (string, bool) { - abs, err := filepath.Abs(path) - if err != nil { - return "", false - } - if resolved, err := filepath.EvalSymlinks(abs); err == nil { - abs = resolved - } - return abs, true -} - -func hasInstallScriptMarker(exe string) bool { - info, err := os.Stat(filepath.Join(filepath.Dir(exe), installMarkerFile)) - return err == nil && !info.IsDir() -} - -func powerShellSingleQuoted(value string) string { - return strings.ReplaceAll(value, "'", "''") -} diff --git a/pkg/cmd/update_test.go b/pkg/cmd/update_test.go deleted file mode 100644 index 6ff4ee8..0000000 --- a/pkg/cmd/update_test.go +++ /dev/null @@ -1,323 +0,0 @@ -package cmd - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "slices" - "strings" - "testing" -) - -func TestLatestVersion(t *testing.T) { - t.Parallel() - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Errorf("latestVersion request method = %q, want GET", r.Method) - } - if r.URL.Path != "/latest" { - t.Errorf("latestVersion request path = %q, want /latest", r.URL.Path) - } - if got := r.Header.Get("Accept"); got != "application/vnd.github+json" { - t.Errorf("latestVersion Accept header = %q, want application/vnd.github+json", got) - } - if got := r.Header.Get("User-Agent"); got == "" { - t.Error("latestVersion User-Agent header is empty") - } - io.WriteString(w, `{"tag_name":"v9.8.7"}`) - })) - defer server.Close() - - updater := newTestUpdater(t) - updater.latestURL = server.URL + "/latest" - - got, err := updater.latestVersion(context.Background()) - if err != nil { - t.Fatalf("latestVersion() returned unexpected error: %v", err) - } - if want := "v9.8.7"; got != want { - t.Errorf("latestVersion() = %q, want %q", got, want) - } -} - -func TestUpdateCheckOnly(t *testing.T) { - t.Parallel() - - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var stdout bytes.Buffer - updater := newTestUpdater(t) - updater.stdout = &stdout - updater.latestURL = server.URL + "/latest" - updater.runCommand = func(context.Context, []string, string, ...string) error { - t.Fatal("update --check should not run commands") - return nil - } - - if err := updater.update(context.Background(), updateOptions{checkOnly: true}); err != nil { - t.Fatalf("update(checkOnly: true) returned unexpected error: %v", err) - } - - got := stdout.String() - for _, want := range []string{"Current version: v" + Version, "Latest version: v9.9.9"} { - if !strings.Contains(got, want) { - t.Errorf("update(checkOnly: true) output = %q, want substring %q", got, want) - } - } -} - -func TestUpdateSkipsCurrentVersion(t *testing.T) { - t.Parallel() - - server := latestVersionServer(t, "v"+Version) - defer server.Close() - - var stdout bytes.Buffer - updater := newTestUpdater(t) - updater.stdout = &stdout - updater.latestURL = server.URL + "/latest" - updater.runCommand = func(context.Context, []string, string, ...string) error { - t.Fatal("update should not run commands when current version is latest") - return nil - } - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - if got, want := stdout.String(), "dedalus is already at v"+Version; !strings.Contains(got, want) { - t.Errorf("update() output = %q, want substring %q", got, want) - } -} - -func TestUpdateHomebrewCask(t *testing.T) { - t.Parallel() - - prefix := t.TempDir() - exe := filepath.Join(prefix, "bin", "dedalus") - writeExecutable(t, exe) - - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var ranName string - var ranArgs []string - updater := newTestUpdater(t) - updater.goos = "darwin" - updater.executable = func() (string, error) { return exe, nil } - updater.latestURL = server.URL + "/latest" - updater.lookPath = func(name string) (string, error) { - if name == "brew" { - return "/opt/homebrew/bin/brew", nil - } - return "", errors.New("not found") - } - updater.commandOutput = func(_ context.Context, name string, args ...string) (string, error) { - switch { - case name == "/opt/homebrew/bin/brew" && slices.Equal(args, []string{"--prefix"}): - return prefix + "\n", nil - case name == "/opt/homebrew/bin/brew" && slices.Equal(args, []string{"list", "--cask", "--versions", "dedalus"}): - return "dedalus 9.8.0\n", nil - default: - return "", errors.New("unexpected command") - } - } - updater.runCommand = func(_ context.Context, _ []string, name string, args ...string) error { - ranName = name - ranArgs = slices.Clone(args) - return nil - } - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - if ranName != "/opt/homebrew/bin/brew" { - t.Errorf("update() command name = %q, want /opt/homebrew/bin/brew", ranName) - } - if want := []string{"upgrade", "--cask", "dedalus"}; !slices.Equal(ranArgs, want) { - t.Errorf("update() command args = %v, want %v", ranArgs, want) - } -} - -func TestUpdateCurlInstall(t *testing.T) { - home := t.TempDir() - t.Setenv("HOME", home) - - exe := filepath.Join(home, ".local", "bin", "dedalus") - writeExecutable(t, exe) - - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var ranEnv []string - var ranName string - var ranArgs []string - updater := newTestUpdater(t) - updater.executable = func() (string, error) { return exe, nil } - updater.latestURL = server.URL + "/latest" - updater.runCommand = func(_ context.Context, env []string, name string, args ...string) error { - ranEnv = slices.Clone(env) - ranName = name - ranArgs = slices.Clone(args) - return nil - } - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - resolvedExe, err := filepath.EvalSymlinks(exe) - if err != nil { - t.Fatalf("EvalSymlinks(%q) returned unexpected error: %v", exe, err) - } - if want := []string{"DEDALUS_INSTALL_DIR=" + filepath.Dir(resolvedExe)}; !slices.Equal(ranEnv, want) { - t.Errorf("update() env = %v, want %v", ranEnv, want) - } - if ranName != "bash" { - t.Errorf("update() command name = %q, want bash", ranName) - } - if want := []string{"-c", "curl -fsSL " + installScriptURL + " | bash"}; !slices.Equal(ranArgs, want) { - t.Errorf("update() command args = %v, want %v", ranArgs, want) - } -} - -func TestUpdateCustomCurlInstallWithMarker(t *testing.T) { - home := t.TempDir() - t.Setenv("HOME", home) - - exe := filepath.Join(t.TempDir(), "bin", "dedalus") - writeExecutable(t, exe) - writeInstallMarker(t, filepath.Dir(exe)) - - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var ranEnv []string - var ranName string - updater := newTestUpdater(t) - updater.executable = func() (string, error) { return exe, nil } - updater.latestURL = server.URL + "/latest" - updater.runCommand = func(_ context.Context, env []string, name string, args ...string) error { - ranEnv = slices.Clone(env) - ranName = name - return nil - } - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - resolvedExe, err := filepath.EvalSymlinks(exe) - if err != nil { - t.Fatalf("EvalSymlinks(%q) returned unexpected error: %v", exe, err) - } - if want := []string{"DEDALUS_INSTALL_DIR=" + filepath.Dir(resolvedExe)}; !slices.Equal(ranEnv, want) { - t.Errorf("update() env = %v, want %v", ranEnv, want) - } - if ranName != "bash" { - t.Errorf("update() command name = %q, want bash", ranName) - } -} - -func TestWindowsUpdatePrintsInstallerCommand(t *testing.T) { - t.Parallel() - - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var stdout bytes.Buffer - updater := newTestUpdater(t) - updater.goos = "windows" - updater.stdout = &stdout - updater.latestURL = server.URL + "/latest" - updater.executable = func() (string, error) { - return filepath.Join("C:", "Users", "me", ".local", "bin", "dedalus.exe"), nil - } - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - got := stdout.String() - for _, want := range []string{"Windows does not allow replacing the running dedalus.exe process.", "install.ps1", "DEDALUS_INSTALL_DIR"} { - if !strings.Contains(got, want) { - t.Errorf("windows update output = %q, want substring %q", got, want) - } - } -} - -func TestUnknownInstallPrintsManualInstructions(t *testing.T) { - t.Parallel() - - exe := filepath.Join(t.TempDir(), "dedalus") - server := latestVersionServer(t, "v9.9.9") - defer server.Close() - - var stdout bytes.Buffer - updater := newTestUpdater(t) - updater.stdout = &stdout - updater.executable = func() (string, error) { return exe, nil } - updater.latestURL = server.URL + "/latest" - - if err := updater.update(context.Background(), updateOptions{}); err != nil { - t.Fatalf("update() returned unexpected error: %v", err) - } - if got, want := stdout.String(), "Could not determine how this dedalus binary was installed."; !strings.Contains(got, want) { - t.Errorf("unknown install output = %q, want substring %q", got, want) - } -} - -func newTestUpdater(t *testing.T) *updater { - t.Helper() - - updater := newUpdater(io.Discard, io.Discard) - updater.goos = "linux" - updater.executable = func() (string, error) { - return filepath.Join(t.TempDir(), "dedalus"), nil - } - updater.lookPath = func(string) (string, error) { - return "", errors.New("not found") - } - updater.commandOutput = func(context.Context, string, ...string) (string, error) { - return "", errors.New("not found") - } - updater.runCommand = func(context.Context, []string, string, ...string) error { - return nil - } - return updater -} - -func latestVersionServer(t *testing.T, tag string) *httptest.Server { - t.Helper() - - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/latest" { - http.NotFound(w, r) - return - } - w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{"tag_name":"`+tag+`"}`) - })) -} - -func writeExecutable(t *testing.T, path string) { - t.Helper() - - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - t.Fatalf("MkdirAll(%q) returned unexpected error: %v", filepath.Dir(path), err) - } - if err := os.WriteFile(path, []byte("old"), 0755); err != nil { - t.Fatalf("WriteFile(%q) returned unexpected error: %v", path, err) - } -} - -func writeInstallMarker(t *testing.T, dir string) { - t.Helper() - - if err := os.WriteFile(filepath.Join(dir, installMarkerFile), []byte("method=install-script\n"), 0644); err != nil { - t.Fatalf("WriteFile(%q) returned unexpected error: %v", installMarkerFile, err) - } -} diff --git a/pkg/cmd/usage.go b/pkg/cmd/usage.go deleted file mode 100644 index ac3fa47..0000000 --- a/pkg/cmd/usage.go +++ /dev/null @@ -1,208 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - - "github.com/dedalus-labs/dedalus-cli/internal/apiquery" - "github.com/dedalus-labs/dedalus-cli/internal/requestflag" - "github.com/dedalus-labs/dedalus-go" - "github.com/dedalus-labs/dedalus-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var usageRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get usage summary", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "period-start", - Usage: "Billing period start (YYYY-MM-DD). Defaults to first of current month.", - QueryPath: "period_start", - }, - }, - Action: handleUsageRetrieve, - HideHelpCommand: true, -} - -var usageMachineCompute = cli.Command{ - Name: "machine-compute", - Usage: "List machine compute usage breakdown", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "granularity", - Usage: "Usage breakdown granularity: hour or day. Defaults to hour.", - QueryPath: "granularity", - }, - &requestflag.Flag[string]{ - Name: "machine-id", - Usage: "Optional machine ID filter.", - QueryPath: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "period-end", - Usage: "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", - QueryPath: "period_end", - }, - &requestflag.Flag[string]{ - Name: "period-start", - Usage: "Usage period start (YYYY-MM-DD). Defaults to first of current month.", - QueryPath: "period_start", - }, - }, - Action: handleUsageMachineCompute, - HideHelpCommand: true, -} - -var usageMachineStorage = cli.Command{ - Name: "machine-storage", - Usage: "List machine storage usage breakdown", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "machine-id", - Usage: "Optional machine ID filter.", - QueryPath: "machine_id", - }, - &requestflag.Flag[string]{ - Name: "period-end", - Usage: "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", - QueryPath: "period_end", - }, - &requestflag.Flag[string]{ - Name: "period-start", - Usage: "Usage period start (YYYY-MM-DD). Defaults to first of current month.", - QueryPath: "period_start", - }, - }, - Action: handleUsageMachineStorage, - HideHelpCommand: true, -} - -func handleUsageRetrieve(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.UsageGetParams{} - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Usage.Get(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: "usage retrieve", - Transform: transform, - }) -} - -func handleUsageMachineCompute(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.UsageMachineComputeParams{} - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Usage.MachineCompute(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: "usage machine-compute", - Transform: transform, - }) -} - -func handleUsageMachineStorage(ctx context.Context, cmd *cli.Command) error { - client := dedalus.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.ArrayQueryFormatRepeat, - EmptyBody, - false, - ) - if err != nil { - return err - } - - params := dedalus.UsageMachineStorageParams{} - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Usage.MachineStorage(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: "usage machine-storage", - Transform: transform, - }) -} diff --git a/pkg/cmd/usage_test.go b/pkg/cmd/usage_test.go deleted file mode 100644 index d40de16..0000000 --- a/pkg/cmd/usage_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/dedalus-labs/dedalus-cli/internal/mocktest" -) - -func TestUsageRetrieve(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "usage", "retrieve", - "--period-start", "period_start", - ) - }) -} - -func TestUsageMachineCompute(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "usage", "machine-compute", - "--granularity", "granularity", - "--machine-id", "machine_id", - "--period-end", "period_end", - "--period-start", "period_start", - ) - }) -} - -func TestUsageMachineStorage(t *testing.T) { - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "usage", "machine-storage", - "--machine-id", "machine_id", - "--period-end", "period_end", - "--period-start", "period_start", - ) - }) -} diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go deleted file mode 100644 index 86845a8..0000000 --- a/pkg/cmd/version.go +++ /dev/null @@ -1,5 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -const Version = "0.5.0" // x-release-please-version diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index ef95266..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "packages": { - ".": {} - }, - "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", - "include-v-in-tag": true, - "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": false, - "pull-request-header": "Automated Release PR", - "pull-request-title-pattern": "release: ${version}", - "changelog-sections": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "revert", - "section": "Reverts" - }, - { - "type": "chore", - "section": "Chores" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactors" - }, - { - "type": "test", - "section": "Tests", - "hidden": true - }, - { - "type": "build", - "section": "Build System" - }, - { - "type": "ci", - "section": "Continuous Integration", - "hidden": true - } - ], - "reviewers": [ - "@windsornguyen" - ], - "release-type": "simple", - "extra-files": [ - "pkg/cmd/version.go", - "README.md" - ] -} \ No newline at end of file diff --git a/scalar-sdk.manifest.json b/scalar-sdk.manifest.json new file mode 100644 index 0000000..7e51fcf --- /dev/null +++ b/scalar-sdk.manifest.json @@ -0,0 +1,8407 @@ +{ + "name": "Dedalus", + "slug": "dedalus", + "version": "0.1.4", + "servers": [ + "https://dcs.dedaluslabs.ai" + ], + "environments": { + "official_dcs_api": "https://dcs.dedaluslabs.ai", + "production": "https://api.dedaluslabs.ai" + }, + "environmentOrder": [ + "production", + "official_dcs_api" + ], + "auth": [ + "apiKey", + "bearer", + "bearer" + ], + "authDetails": [ + { + "kind": "apiKey", + "id": "ApiKeyAuth", + "in": "header", + "paramName": "x-api-key" + }, + { + "kind": "bearer", + "id": "BearerAuth", + "bearerFormat": "Dedalus API key" + }, + { + "kind": "bearer", + "id": "Bearer" + } + ], + "clientHeaderParams": [], + "schemas": [ + { + "name": "ArtifactListResponse", + "source": "ArtifactListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "ArtifactResponse" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ArtifactRef", + "source": "ArtifactRef", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "artifact_id", + "publicName": "artifact_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ArtifactResponse", + "source": "ArtifactResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "artifact_id", + "publicName": "artifact_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "download_url", + "publicName": "download_url", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "execution_id", + "publicName": "execution_id", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "expires_at", + "publicName": "expires_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "mime_type", + "publicName": "mime_type", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "name", + "publicName": "name", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "sha256", + "publicName": "sha256", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "size_bytes", + "publicName": "size_bytes", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateExecutionRequest", + "source": "CreateExecutionRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "command", + "publicName": "command", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "cwd", + "publicName": "cwd", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "env", + "publicName": "env", + "required": false, + "deprecated": false, + "type": { + "kind": "record", + "value": { + "kind": "primitive", + "type": "string" + }, + "propertyNames": { + "kind": "primitive", + "type": "string" + } + } + }, + { + "name": "stdin", + "publicName": "stdin", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "timeout_ms", + "publicName": "timeout_ms", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateMachineRequest", + "source": "CreateMachineRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "autosleep", + "publicName": "autosleep", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "memory_mib", + "publicName": "memory_mib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "storage_gib", + "publicName": "storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "vcpu", + "publicName": "vcpu", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number", + "format": "double" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreatePreviewRequest", + "source": "CreatePreviewRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "port", + "publicName": "port", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "protocol", + "publicName": "protocol", + "required": false, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "http", + "https" + ], + "names": [ + "HTTP", + "Https" + ], + "deprecations": [ + false, + false + ] + } + }, + { + "name": "visibility", + "publicName": "visibility", + "required": false, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "public", + "private", + "org" + ], + "names": [ + "Public", + "Private", + "Org" + ], + "deprecations": [ + false, + false, + false + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateSshSessionRequest", + "source": "CreateSshSessionRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "public_key", + "publicName": "public_key", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateTerminalRequest", + "source": "CreateTerminalRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "cwd", + "publicName": "cwd", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "env", + "publicName": "env", + "required": false, + "deprecated": false, + "type": { + "kind": "record", + "value": { + "kind": "primitive", + "type": "string" + }, + "propertyNames": { + "kind": "primitive", + "type": "string" + } + } + }, + { + "name": "height", + "publicName": "height", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "shell", + "publicName": "shell", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "width", + "publicName": "width", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ErrorDetail", + "source": "ErrorDetail", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "location", + "publicName": "location", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "message", + "publicName": "message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "value", + "publicName": "value", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ErrorModel", + "source": "ErrorModel", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "detail", + "publicName": "detail", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "errors", + "publicName": "errors", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "ErrorDetail" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "instance", + "publicName": "instance", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "uri" + } + }, + { + "name": "status", + "publicName": "status", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "title", + "publicName": "title", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "type", + "publicName": "type", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "uri" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ExecutionEvent", + "source": "ExecutionEvent", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "at", + "publicName": "at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "chunk", + "publicName": "chunk", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_code", + "publicName": "error_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_message", + "publicName": "error_message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "exit_code", + "publicName": "exit_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "sequence", + "publicName": "sequence", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "signal", + "publicName": "signal", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "status", + "publicName": "status", + "required": false, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "wake_in_progress", + "queued", + "running", + "succeeded", + "failed", + "cancelled", + "expired" + ], + "names": [ + "WakeInProgress", + "Queued", + "Running", + "Succeeded", + "Failed", + "Cancelled", + "Expired" + ], + "deprecations": [ + false, + false, + false, + false, + false, + false, + false + ] + } + }, + { + "name": "type", + "publicName": "type", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "lifecycle", + "stdout", + "stderr" + ], + "names": [ + "Lifecycle", + "Stdout", + "Stderr" + ], + "deprecations": [ + false, + false, + false + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ExecutionEventsResponse", + "source": "ExecutionEventsResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "ExecutionEvent" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ExecutionListResponse", + "source": "ExecutionListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "ExecutionResponse" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ExecutionOutputResponse", + "source": "ExecutionOutputResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "execution_id", + "publicName": "execution_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "stderr", + "publicName": "stderr", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "stderr_bytes", + "publicName": "stderr_bytes", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "stderr_truncated", + "publicName": "stderr_truncated", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "stdout", + "publicName": "stdout", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "stdout_bytes", + "publicName": "stdout_bytes", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "stdout_truncated", + "publicName": "stdout_truncated", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "ExecutionResponse", + "source": "ExecutionResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "artifacts", + "publicName": "artifacts", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "ArtifactRef" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "command", + "publicName": "command", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "completed_at", + "publicName": "completed_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "cwd", + "publicName": "cwd", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "env_keys", + "publicName": "env_keys", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "error_code", + "publicName": "error_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_message", + "publicName": "error_message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "execution_id", + "publicName": "execution_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "exit_code", + "publicName": "exit_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "expires_at", + "publicName": "expires_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "retry_after_ms", + "publicName": "retry_after_ms", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "signal", + "publicName": "signal", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "started_at", + "publicName": "started_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "wake_in_progress", + "queued", + "running", + "succeeded", + "failed", + "cancelled", + "expired" + ], + "names": [ + "WakeInProgress", + "Queued", + "Running", + "Succeeded", + "Failed", + "Cancelled", + "Expired" + ], + "deprecations": [ + false, + false, + false, + false, + false, + false, + false + ] + } + }, + { + "name": "stderr_bytes", + "publicName": "stderr_bytes", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "stderr_truncated", + "publicName": "stderr_truncated", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "stdout_bytes", + "publicName": "stdout_bytes", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "stdout_truncated", + "publicName": "stdout_truncated", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "LifecycleResponse", + "source": "LifecycleResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "autosleep_seconds", + "publicName": "autosleep_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64", + "validation": {} + } + }, + { + "name": "desired_state", + "publicName": "desired_state", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "running", + "sleeping", + "destroyed" + ], + "names": [ + "Running", + "Sleeping", + "Destroyed" + ], + "deprecations": [ + false, + false, + false + ] + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "memory_mib", + "publicName": "memory_mib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "LifecycleStatus" + } + }, + { + "name": "storage_gib", + "publicName": "storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "vcpu", + "publicName": "vcpu", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number", + "format": "double" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "LifecycleStatus", + "source": "LifecycleStatus", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "last_error", + "publicName": "last_error", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "last_progress_at", + "publicName": "last_progress_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "last_transition_at", + "publicName": "last_transition_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "phase", + "publicName": "phase", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "accepted", + "placement_pending", + "starting", + "running", + "stopping", + "sleeping", + "destroying", + "destroyed", + "failed" + ], + "names": [ + "Accepted", + "PlacementPending", + "Starting", + "Running", + "Stopping", + "Sleeping", + "Destroying", + "Destroyed", + "Failed" + ], + "deprecations": [ + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + } + }, + { + "name": "reason", + "publicName": "reason", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "retryable", + "publicName": "retryable", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "boolean" + } + }, + { + "name": "revision", + "publicName": "revision", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineComputeUsageBody", + "source": "MachineComputeUsageBody", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "granularity", + "publicName": "granularity", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "period_end", + "publicName": "period_end", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "period_start", + "publicName": "period_start", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "rows", + "publicName": "rows", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "MachineComputeUsageRowBody" + } + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineComputeUsageRowBody", + "source": "MachineComputeUsageRowBody", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "awake_seconds", + "publicName": "awake_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "bucket_end", + "publicName": "bucket_end", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "bucket_start", + "publicName": "bucket_start", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "cpu_millicore_seconds", + "publicName": "cpu_millicore_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "last_window_end", + "publicName": "last_window_end", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "latest_stripe_emitted_at", + "publicName": "latest_stripe_emitted_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "memory_mib_seconds", + "publicName": "memory_mib_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "org_metering_bucket_ids", + "publicName": "org_metering_bucket_ids", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "requested_memory_mib", + "publicName": "requested_memory_mib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int32" + } + }, + { + "name": "requested_storage_gib", + "publicName": "requested_storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int32" + } + }, + { + "name": "requested_vcpu", + "publicName": "requested_vcpu", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number", + "format": "double" + } + }, + { + "name": "spec_fingerprint", + "publicName": "spec_fingerprint", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "stripe_cpu_identifiers", + "publicName": "stripe_cpu_identifiers", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "stripe_memory_identifiers", + "publicName": "stripe_memory_identifiers", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "primitive", + "type": "string" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "window_count", + "publicName": "window_count", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineIDPathSegment", + "source": "MachineIDPathSegment", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "pattern": "^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "minLength": 4, + "maxLength": 253 + } + } + }, + { + "name": "MachineListItem", + "source": "MachineListItem", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "autosleep_seconds", + "publicName": "autosleep_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64", + "validation": {} + } + }, + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "desired_state", + "publicName": "desired_state", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "running", + "sleeping", + "destroyed" + ], + "names": [ + "Running", + "Sleeping", + "Destroyed" + ], + "deprecations": [ + false, + false, + false + ] + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "memory_mib", + "publicName": "memory_mib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "ref", + "name": "LifecycleStatus" + } + }, + { + "name": "storage_gib", + "publicName": "storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "vcpu", + "publicName": "vcpu", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number", + "format": "double" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineListResponse", + "source": "MachineListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "MachineListItem" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineStorageUsageBody", + "source": "MachineStorageUsageBody", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "period_end", + "publicName": "period_end", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "period_start", + "publicName": "period_start", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "rows", + "publicName": "rows", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "MachineStorageUsageRowBody" + } + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "MachineStorageUsageRowBody", + "source": "MachineStorageUsageRowBody", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "bucket_end", + "publicName": "bucket_end", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "bucket_start", + "publicName": "bucket_start", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "latest_stripe_emitted_at", + "publicName": "latest_stripe_emitted_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "logical_storage_bytes", + "publicName": "logical_storage_bytes", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "org_metering_bucket_id", + "publicName": "org_metering_bucket_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "storage_mib_seconds", + "publicName": "storage_mib_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "stripe_storage_identifier", + "publicName": "stripe_storage_identifier", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "PreviewListResponse", + "source": "PreviewListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "PreviewResponse" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "PreviewResponse", + "source": "PreviewResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "error_code", + "publicName": "error_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_message", + "publicName": "error_message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "expires_at", + "publicName": "expires_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "port", + "publicName": "port", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "preview_id", + "publicName": "preview_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "protocol", + "publicName": "protocol", + "required": false, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "http", + "https" + ], + "names": [ + "HTTP", + "Https" + ], + "deprecations": [ + false, + false + ] + } + }, + { + "name": "ready_at", + "publicName": "ready_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "retry_after_ms", + "publicName": "retry_after_ms", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "names": [ + "WakeInProgress", + "Ready", + "Closed", + "Expired", + "Failed" + ], + "deprecations": [ + false, + false, + false, + false, + false + ] + } + }, + { + "name": "url", + "publicName": "url", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "visibility", + "publicName": "visibility", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "public", + "private", + "org" + ], + "names": [ + "Public", + "Private", + "Org" + ], + "deprecations": [ + false, + false, + false + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "PublicPathSegment", + "source": "PublicPathSegment", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "validation": { + "pattern": "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$", + "minLength": 1, + "maxLength": 253 + } + } + }, + { + "name": "SshConnection", + "source": "SshConnection", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "endpoint", + "publicName": "endpoint", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "host_trust", + "publicName": "host_trust", + "required": false, + "deprecated": false, + "type": { + "kind": "ref", + "name": "SshHostTrust" + } + }, + { + "name": "port", + "publicName": "port", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "ssh_username", + "publicName": "ssh_username", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "user_certificate", + "publicName": "user_certificate", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "SshHostTrust", + "source": "SshHostTrust", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "host_pattern", + "publicName": "host_pattern", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "kind", + "publicName": "kind", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "cert_authority" + ], + "names": [ + "CertAuthority" + ], + "deprecations": [ + false + ] + } + }, + { + "name": "public_key", + "publicName": "public_key", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "SshSessionListResponse", + "source": "SshSessionListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "SshSessionResponse" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "SshSessionResponse", + "source": "SshSessionResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "connection", + "publicName": "connection", + "required": false, + "deprecated": false, + "type": { + "kind": "ref", + "name": "SshConnection" + } + }, + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "error_code", + "publicName": "error_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_message", + "publicName": "error_message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "expires_at", + "publicName": "expires_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "ready_at", + "publicName": "ready_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "retry_after_ms", + "publicName": "retry_after_ms", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "session_id", + "publicName": "session_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "names": [ + "WakeInProgress", + "Ready", + "Closed", + "Expired", + "Failed" + ], + "deprecations": [ + false, + false, + false, + false, + false + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "TerminalListResponse", + "source": "TerminalListResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "items", + "publicName": "items", + "required": true, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "array", + "items": { + "kind": "ref", + "name": "TerminalResponse" + } + }, + { + "kind": "null" + } + ] + } + }, + { + "name": "next_cursor", + "publicName": "next_cursor", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "TerminalResponse", + "source": "TerminalResponse", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "created_at", + "publicName": "created_at", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "error_code", + "publicName": "error_code", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "error_message", + "publicName": "error_message", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "expires_at", + "publicName": "expires_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "height", + "publicName": "height", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "machine_id", + "publicName": "machine_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "protocol", + "publicName": "protocol", + "required": false, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "websocket" + ], + "names": [ + "Websocket" + ], + "deprecations": [ + false + ] + } + }, + { + "name": "ready_at", + "publicName": "ready_at", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string", + "format": "date-time" + } + }, + { + "name": "retry_after_ms", + "publicName": "retry_after_ms", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "status", + "publicName": "status", + "required": true, + "deprecated": false, + "type": { + "kind": "enum", + "values": [ + "wake_in_progress", + "ready", + "closed", + "expired", + "failed" + ], + "names": [ + "WakeInProgress", + "Ready", + "Closed", + "Expired", + "Failed" + ], + "deprecations": [ + false, + false, + false, + false, + false + ] + } + }, + { + "name": "stream_url", + "publicName": "stream_url", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "terminal_id", + "publicName": "terminal_id", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "width", + "publicName": "width", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "UpdateMachineRequest", + "source": "UpdateMachineRequest", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "autosleep", + "publicName": "autosleep", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "memory_mib", + "publicName": "memory_mib", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "storage_gib", + "publicName": "storage_gib", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "vcpu", + "publicName": "vcpu", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "number", + "format": "double" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "UsageBody", + "source": "UsageBody", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "billed_awake_seconds", + "publicName": "billed_awake_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "billed_cpu_millicore_seconds", + "publicName": "billed_cpu_millicore_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "billed_logical_storage_mib_seconds", + "publicName": "billed_logical_storage_mib_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "billed_memory_mib_seconds", + "publicName": "billed_memory_mib_seconds", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "included_storage_gib", + "publicName": "included_storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + }, + { + "name": "plan_slug", + "publicName": "plan_slug", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "provisioned_storage_gib", + "publicName": "provisioned_storage_gib", + "required": true, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "integer", + "format": "int64" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateResponseHeaders", + "source": "CreateResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "CreateResponseHeaders", + "source": "CreateResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "DeleteResponseHeaders", + "source": "DeleteResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "DeleteResponseHeaders", + "source": "DeleteResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "RetrieveResponseHeaders", + "source": "RetrieveResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + } + ], + "additionalProperties": false + } + }, + { + "name": "PatchResponseHeaders", + "source": "PatchResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "PatchResponseHeaders", + "source": "PatchResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "SleepResponseHeaders", + "source": "SleepResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "SleepResponseHeaders", + "source": "SleepResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "WakeResponseHeaders", + "source": "WakeResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + }, + { + "name": "WakeResponseHeaders", + "source": "WakeResponseHeaders", + "publicAliases": [], + "deprecated": false, + "type": { + "kind": "object", + "properties": [ + { + "name": "ETag", + "publicName": "ETag", + "required": false, + "deprecated": false, + "type": { + "kind": "primitive", + "type": "string" + } + }, + { + "name": "X-Dedalus-Storage-Operation-Id", + "publicName": "X-Dedalus-Storage-Operation-Id", + "required": false, + "deprecated": false, + "type": { + "kind": "union", + "variants": [ + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "null" + } + ] + } + } + ], + "additionalProperties": false + } + } + ], + "resources": [ + "machineLifecycle", + "usage", + "usage.machines" + ], + "publicResources": [ + "machineLifecycle", + "usage", + "usage.machines" + ], + "operations": [ + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/machines", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "MachineListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "create", + "publicOperation": "create", + "deprecated": false, + "method": "POST", + "path": "/v1/machines", + "pathParams": [], + "publicPathParams": [], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "autosleep", + "memory_mib", + "storage_gib", + "vcpu" + ], + "publicBodyParams": [ + "autosleep", + "memory_mib", + "storage_gib", + "vcpu" + ], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleCreateParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "CreateResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "delete", + "publicOperation": "delete", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeleteParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "DeleteResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrieve", + "publicOperation": "retrieve", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrieveParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "RetrieveResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "patch", + "publicOperation": "patch", + "deprecated": false, + "method": "PATCH", + "path": "/v1/machines/{machine_id}", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "autosleep", + "memory_mib", + "storage_gib", + "vcpu" + ], + "publicBodyParams": [ + "autosleep", + "memory_mib", + "storage_gib", + "vcpu" + ], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecyclePatchParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "PatchResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listArtifacts", + "publicOperation": "listArtifacts", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/artifacts", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListArtifactsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ArtifactListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "deleteArtifact", + "publicOperation": "deleteArtifact", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}/artifacts/{artifact_id}", + "pathParams": [ + "machine_id", + "artifact_id" + ], + "publicPathParams": [ + "machine_id", + "artifact_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "artifact_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeleteArtifactParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ArtifactResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrieveArtifact", + "publicOperation": "retrieveArtifact", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/artifacts/{artifact_id}", + "pathParams": [ + "machine_id", + "artifact_id" + ], + "publicPathParams": [ + "machine_id", + "artifact_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "artifact_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrieveArtifactParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ArtifactResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listExecutions", + "publicOperation": "listExecutions", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/executions", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListExecutionsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "createExecution", + "publicOperation": "createExecution", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/executions", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "command", + "cwd", + "env", + "stdin", + "timeout_ms" + ], + "publicBodyParams": [ + "command", + "cwd", + "env", + "stdin", + "timeout_ms" + ], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleCreateExecutionParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "deleteExecution", + "publicOperation": "deleteExecution", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}/executions/{execution_id}", + "pathParams": [ + "machine_id", + "execution_id" + ], + "publicPathParams": [ + "machine_id", + "execution_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "execution_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeleteExecutionParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrieveExecution", + "publicOperation": "retrieveExecution", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/executions/{execution_id}", + "pathParams": [ + "machine_id", + "execution_id" + ], + "publicPathParams": [ + "machine_id", + "execution_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "execution_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrieveExecutionParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listExecutionEvents", + "publicOperation": "listExecutionEvents", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/executions/{execution_id}/events", + "pathParams": [ + "machine_id", + "execution_id" + ], + "publicPathParams": [ + "machine_id", + "execution_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "execution_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListExecutionEventsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionEventsResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listExecutionOutput", + "publicOperation": "listExecutionOutput", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/executions/{execution_id}/output", + "pathParams": [ + "machine_id", + "execution_id" + ], + "publicPathParams": [ + "machine_id", + "execution_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "execution_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListExecutionOutputParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "ExecutionOutputResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listPreviews", + "publicOperation": "listPreviews", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/previews", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListPreviewsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "PreviewListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "createPreview", + "publicOperation": "createPreview", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/previews", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "port", + "protocol", + "visibility" + ], + "publicBodyParams": [ + "port", + "protocol", + "visibility" + ], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleCreatePreviewParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "PreviewResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "deletePreview", + "publicOperation": "deletePreview", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}/previews/{preview_id}", + "pathParams": [ + "machine_id", + "preview_id" + ], + "publicPathParams": [ + "machine_id", + "preview_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "preview_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeletePreviewParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "PreviewResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrievePreview", + "publicOperation": "retrievePreview", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/previews/{preview_id}", + "pathParams": [ + "machine_id", + "preview_id" + ], + "publicPathParams": [ + "machine_id", + "preview_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "preview_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrievePreviewParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "PreviewResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "sleep", + "publicOperation": "sleep", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/sleep", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleSleepParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "SleepResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listSshSessions", + "publicOperation": "listSshSessions", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/ssh", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListSshSessionsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "SshSessionListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "createSshSession", + "publicOperation": "createSshSession", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/ssh", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "public_key" + ], + "publicBodyParams": [ + "public_key" + ], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleCreateSshSessionParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "SshSessionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "deleteSshSession", + "publicOperation": "deleteSshSession", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}/ssh/{session_id}", + "pathParams": [ + "machine_id", + "session_id" + ], + "publicPathParams": [ + "machine_id", + "session_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "session_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeleteSshSessionParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "SshSessionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrieveSshSession", + "publicOperation": "retrieveSshSession", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/ssh/{session_id}", + "pathParams": [ + "machine_id", + "session_id" + ], + "publicPathParams": [ + "machine_id", + "session_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "session_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrieveSshSessionParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "SshSessionResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "watchStatus", + "publicOperation": "watchStatus", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/status/stream", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id", + "Last-Event-ID" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id", + "Last-Event-ID" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + }, + { + "name": "Last-Event-ID", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleWatchStatusParams" + }, + "response": { + "status": "200", + "contentType": "text/event-stream", + "encoding": "text", + "contents": [ + { + "contentType": "text/event-stream", + "encoding": "text" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "http", + "streaming": "sse" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "listTerminals", + "publicOperation": "listTerminals", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/terminals", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [ + "limit", + "cursor" + ], + "publicQueryParams": [ + "limit", + "cursor" + ], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [ + { + "name": "limit", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "cursor", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleListTerminalsParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "TerminalListResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "createTerminal", + "publicOperation": "createTerminal", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/terminals", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [ + "cwd", + "env", + "height", + "shell", + "width" + ], + "publicBodyParams": [ + "cwd", + "env", + "height", + "shell", + "width" + ], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleCreateTerminalParams" + }, + "requestBody": { + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ], + "required": true, + "publicName": "body", + "publicIdentifier": "body" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "TerminalResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "deleteTerminal", + "publicOperation": "deleteTerminal", + "deprecated": false, + "method": "DELETE", + "path": "/v1/machines/{machine_id}/terminals/{terminal_id}", + "pathParams": [ + "machine_id", + "terminal_id" + ], + "publicPathParams": [ + "machine_id", + "terminal_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "terminal_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleDeleteTerminalParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "TerminalResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "retrieveTerminal", + "publicOperation": "retrieveTerminal", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/terminals/{terminal_id}", + "pathParams": [ + "machine_id", + "terminal_id" + ], + "publicPathParams": [ + "machine_id", + "terminal_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "terminal_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleRetrieveTerminalParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "TerminalResponse", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "connectTerminal", + "publicOperation": "connectTerminal", + "deprecated": false, + "method": "GET", + "path": "/v1/machines/{machine_id}/terminals/{terminal_id}/stream", + "pathParams": [ + "machine_id", + "terminal_id" + ], + "publicPathParams": [ + "machine_id", + "terminal_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + }, + { + "name": "terminal_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleConnectTerminalParams" + }, + "result": { + "successStatus": "101", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + } + ], + "responseLinks": [], + "transport": "websocket", + "websocket": {} + }, + { + "resource": "machineLifecycle", + "publicResource": "machineLifecycle", + "operation": "wake", + "publicOperation": "wake", + "deprecated": false, + "method": "POST", + "path": "/v1/machines/{machine_id}/wake", + "pathParams": [ + "machine_id" + ], + "publicPathParams": [ + "machine_id" + ], + "queryParams": [], + "publicQueryParams": [], + "headerParams": [ + "X-Dedalus-Org-Id" + ], + "publicHeaderParams": [ + "X-Dedalus-Org-Id" + ], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [ + { + "name": "machine_id", + "required": true, + "style": "simple", + "explode": false + } + ], + "queryParamDetails": [], + "headerParamDetails": [ + { + "name": "X-Dedalus-Org-Id", + "required": false, + "style": "form", + "explode": true + } + ], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineLifecycleWakeParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "LifecycleResponse", + "publicAliases": [] + }, + "responseHeadersModel": { + "name": "WakeResponseHeaders", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "401", + "403", + "409", + "429", + "503", + "default" + ] + }, + "errorResponses": [ + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "409", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "429", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "503", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "usage", + "publicResource": "usage", + "operation": "list", + "publicOperation": "list", + "deprecated": false, + "method": "GET", + "path": "/v1/usage", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "period_start" + ], + "publicQueryParams": [ + "period_start" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "period_start", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "UsageListParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "UsageBody", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "500", + "502", + "default" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "502", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "usage.machines", + "publicResource": "usage.machines", + "operation": "listComputeUsage", + "publicOperation": "listComputeUsage", + "deprecated": false, + "method": "GET", + "path": "/v1/usage/machines/compute", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "period_start", + "period_end", + "machine_id", + "granularity" + ], + "publicQueryParams": [ + "period_start", + "period_end", + "machine_id", + "granularity" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "period_start", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "period_end", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "machine_id", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "granularity", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineListComputeUsageParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "MachineComputeUsageBody", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "500", + "502", + "default" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "502", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + }, + { + "resource": "usage.machines", + "publicResource": "usage.machines", + "operation": "listStorageUsage", + "publicOperation": "listStorageUsage", + "deprecated": false, + "method": "GET", + "path": "/v1/usage/machines/storage", + "pathParams": [], + "publicPathParams": [], + "queryParams": [ + "period_start", + "period_end", + "machine_id" + ], + "publicQueryParams": [ + "period_start", + "period_end", + "machine_id" + ], + "headerParams": [], + "publicHeaderParams": [], + "bodyParams": [], + "publicBodyParams": [], + "publicPositionalParams": [], + "pathParamDetails": [], + "queryParamDetails": [ + { + "name": "period_start", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "period_end", + "required": false, + "style": "form", + "explode": false + }, + { + "name": "machine_id", + "required": false, + "style": "form", + "explode": false + } + ], + "headerParamDetails": [], + "cookieParams": [], + "publicCookieParams": [], + "cookieParamDetails": [], + "paramsModel": { + "publicName": "MachineListStorageUsageParams" + }, + "response": { + "status": "200", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + "responseModel": { + "name": "MachineStorageUsageBody", + "publicAliases": [] + }, + "result": { + "successStatus": "200", + "errorStatuses": [ + "400", + "401", + "403", + "500", + "502", + "default" + ] + }, + "errorResponses": [ + { + "status": "400", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "401", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "403", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "500", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "502", + "contentType": "application/json", + "encoding": "json", + "contents": [ + { + "contentType": "application/json", + "encoding": "json" + } + ] + }, + { + "status": "default", + "contentType": "application/problem+json", + "encoding": "json", + "contents": [ + { + "contentType": "application/problem+json", + "encoding": "json" + } + ], + "model": { + "name": "ErrorModel", + "publicAliases": [] + } + } + ], + "responseLinks": [], + "transport": "http" + } + ], + "webhooks": [] +} diff --git a/scripts/bootstrap b/scripts/bootstrap deleted file mode 100755 index bbc786d..0000000 --- a/scripts/bootstrap +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then - brew bundle check >/dev/null 2>&1 || { - echo -n "==> Install Homebrew dependencies? (y/N): " - read -r response - case "$response" in - [yY][eE][sS]|[yY]) - brew bundle - ;; - *) - ;; - esac - echo - } -fi -echo "==> Installing Go dependencies…" -go mod tidy -e || true diff --git a/scripts/build b/scripts/build deleted file mode 100755 index af8e3c9..0000000 --- a/scripts/build +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# Mark the necessary Go modules as private to avoid Go's proxy -export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go" - -echo "==> Building dedalus" -go build ./cmd/dedalus diff --git a/scripts/finalize-build.mjs b/scripts/finalize-build.mjs new file mode 100644 index 0000000..346ae82 --- /dev/null +++ b/scripts/finalize-build.mjs @@ -0,0 +1,50 @@ +import { chmod, readdir, readFile, writeFile } from 'node:fs/promises' +import { dirname, extname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const RELATIVE_SPECIFIER_RE = /(from\s+["']|import\(\s*["'])(\.{1,2}\/[^"']+)(["'])/g + +await Promise.all([ + rewriteRelativeSpecifiers(resolve(root, 'dist/esm')), + markCommonJsOutput(resolve(root, 'dist/cjs')), +]) +await chmod(resolve(root, 'dist/esm/bin.js'), 0o755).catch(() => {}) + +async function rewriteRelativeSpecifiers(dir) { + let entries + try { + entries = await readdir(dir, { withFileTypes: true }) + } catch (error) { + if (error && error.code === 'ENOENT') return + throw error + } + + await Promise.all( + entries.map(async (entry) => { + const path = resolve(dir, entry.name) + if (entry.isDirectory()) { + await rewriteRelativeSpecifiers(path) + return + } + if (!path.endsWith('.js') && !path.endsWith('.d.ts')) return + const source = await readFile(path, 'utf8') + await writeFile(path, source.replace(RELATIVE_SPECIFIER_RE, addJsExtension), 'utf8') + }), + ) +} + +async function markCommonJsOutput(dir) { + try { + await readdir(dir) + await writeFile(resolve(dir, 'package.json'), '{\n "type": "commonjs"\n}\n', 'utf8') + } catch (error) { + if (error && error.code === 'ENOENT') return + throw error + } +} + +function addJsExtension(match, prefix, specifier, suffix) { + if (extname(specifier)) return match + return `${prefix}${specifier}.js${suffix}` +} diff --git a/scripts/format b/scripts/format deleted file mode 100755 index db2a3fa..0000000 --- a/scripts/format +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Running gofmt -s -w" -gofmt -s -w . diff --git a/scripts/install.ps1 b/scripts/install.ps1 deleted file mode 100644 index 036ab3d..0000000 --- a/scripts/install.ps1 +++ /dev/null @@ -1,289 +0,0 @@ -<# -.SYNOPSIS - The installer for the Dedalus CLI on Windows. - -.DESCRIPTION - Detects the host architecture, downloads the matching release archive from - https://github.com/dedalus-labs/dedalus-cli/releases, extracts dedalus.exe, - and installs it to $HOME\.local\bin (overridable). Optionally adds the - install directory to the user PATH via the registry. - - Mirrors scripts/install.sh 1:1 so docs, env vars, and muscle memory are - symmetric across macOS, Linux, and Windows. - -.PARAMETER InstallDir - Install directory. Defaults to $env:DEDALUS_INSTALL_DIR, then $HOME\.local\bin. - -.PARAMETER Version - Version tag to install (e.g. v0.1.0). Defaults to $env:DEDALUS_VERSION, - then the latest GitHub release. - -.PARAMETER NoModifyPath - Skip modifying the user PATH. Also honored via $env:DEDALUS_NO_MODIFY_PATH=1. - -.EXAMPLE - irm https://raw.githubusercontent.com/dedalus-labs/dedalus-cli/main/scripts/install.ps1 | iex - -.EXAMPLE - # Pin a version - iex "& {$(irm https://raw.githubusercontent.com/dedalus-labs/dedalus-cli/main/scripts/install.ps1)} -Version v0.1.0" -#> - -[CmdletBinding()] -param ( - [string]$InstallDir, - [string]$Version, - [switch]$NoModifyPath -) - -$ErrorActionPreference = 'Stop' - -$Repo = 'dedalus-labs/dedalus-cli' -$Binary = 'dedalus' - -function Write-Info { param([string]$Message) Write-Host "[INFO] $Message" -ForegroundColor Cyan } -function Write-Ok { param([string]$Message) Write-Host "[OK] $Message" -ForegroundColor Green } -function Write-Warn { param([string]$Message) Write-Host "[WARN] $Message" -ForegroundColor Yellow } -function Write-Err { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } - -function Show-Banner { - $art = @' - - .. - .. - ... - .... - .... - ..... - ..... - ...... - ........ - ........ - ......... - ........... ... - ............ .... - ............. .... - .............. ..... - .............. ...... - .............. ....... - ............ ....... - ......... ........ - ........ ......... - ...... ........... -...... ............ -..... .. -.... .... -... .... -.. ....... -. .......... -. .................... -. .................. - - Dedalus CLI - dedaluslabs.ai - -'@ - Write-Host $art -} - -function Assert-Environment { - if ($PSVersionTable.PSVersion.Major -lt 5) { - Write-Err "PowerShell 5.1 or newer is required (found $($PSVersionTable.PSVersion))." - Write-Err "Upgrade: https://learn.microsoft.com/powershell/scripting/install/installing-powershell" - exit 1 - } - - $policy = Get-ExecutionPolicy - $allowed = @('Unrestricted', 'RemoteSigned', 'Bypass') - if ($policy -notin $allowed) { - Write-Err "PowerShell execution policy is '$policy'; need one of: $($allowed -join ', ')." - Write-Err "Run: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser" - exit 1 - } - - # GitHub requires TLS 1.2. PS 5.1 defaults to TLS 1.0/1.1 on older Windows. - if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') { - [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 - } -} - -function Get-Arch { - $raw = $null - try { - $raw = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() - } catch { - $raw = $env:PROCESSOR_ARCHITECTURE - } - - switch -Regex ($raw) { - '^(X64|AMD64)$' { return 'amd64' } - '^ARM64$' { return 'arm64' } - '^(X86|IA32)$' { return '386' } - default { - Write-Err "Unsupported architecture: $raw" - exit 1 - } - } -} - -function Get-LatestVersion { - $url = "https://api.github.com/repos/$Repo/releases/latest" - $headers = @{ - 'Accept' = 'application/vnd.github+json' - 'User-Agent' = 'dedalus-cli-installer' - } - - try { - $release = Invoke-RestMethod -Uri $url -Headers $headers -ErrorAction Stop - } catch { - Write-Err "Could not determine latest version from $url" - Write-Err $_.Exception.Message - exit 1 - } - - $tag = $release.tag_name - if (-not $tag) { - Write-Err "Could not parse version tag from GitHub API response: $url" - exit 1 - } - return $tag.Trim() -} - -function Install-Dedalus { - param( - [string]$Arch, - [string]$VersionTag, - [string]$Destination - ) - - $versionNum = $VersionTag.TrimStart('v') - $archiveName = "${Binary}_${versionNum}_windows_${Arch}.zip" - $url = "https://github.com/$Repo/releases/download/$VersionTag/$archiveName" - - $tmpdir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())) - try { - $archivePath = Join-Path $tmpdir.FullName $archiveName - Write-Info "Downloading $url ..." - try { - Invoke-WebRequest -Uri $url -OutFile $archivePath -UseBasicParsing - } catch { - Write-Err "Download failed. Check that a release exists for windows/$Arch." - Write-Err $_.Exception.Message - exit 1 - } - - Write-Info "Extracting..." - Expand-Archive -Path $archivePath -DestinationPath $tmpdir.FullName -Force - - $extracted = Join-Path $tmpdir.FullName "$Binary.exe" - if (-not (Test-Path $extracted)) { - Write-Err "Archive did not contain $Binary.exe" - exit 1 - } - - if (-not (Test-Path $Destination)) { - New-Item -ItemType Directory -Path $Destination -Force | Out-Null - } - - $target = Join-Path $Destination "$Binary.exe" - Move-Item -Path $extracted -Destination $target -Force - Write-Ok "Installed $Binary to $target" - } finally { - Remove-Item -Path $tmpdir.FullName -Recurse -Force -ErrorAction SilentlyContinue - } -} - -function Test-PathContains { - param([string]$Directory) - - $sep = [System.IO.Path]::PathSeparator - $current = ($env:PATH -split $sep) | Where-Object { $_ } - foreach ($entry in $current) { - if ([string]::Equals($entry.TrimEnd('\'), $Directory.TrimEnd('\'), [System.StringComparison]::OrdinalIgnoreCase)) { - return $true - } - } - return $false -} - -function Add-UserPath { - param([string]$Directory) - - $registryPath = 'registry::HKEY_CURRENT_USER\Environment' - $existing = (Get-Item -LiteralPath $registryPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') - $entries = @() - if ($existing) { - $entries = $existing -split ';' | Where-Object { $_ } - } - - foreach ($entry in $entries) { - if ([string]::Equals($entry.TrimEnd('\'), $Directory.TrimEnd('\'), [System.StringComparison]::OrdinalIgnoreCase)) { - return $false - } - } - - $newPath = (@($Directory) + $entries) -join ';' - Set-ItemProperty -Type ExpandString -LiteralPath $registryPath -Name Path -Value $newPath - - # Broadcast WM_SETTINGCHANGE so explorer/new shells pick up the change without reboot. - # Uses a dummy env var round-trip, same trick uv uses. - $dummy = 'DEDALUS_CLI_PATH_' + [guid]::NewGuid().ToString('N') - [Environment]::SetEnvironmentVariable($dummy, '1', 'User') - [Environment]::SetEnvironmentVariable($dummy, [NullString]::Value, 'User') - - $env:PATH = "$Directory;$env:PATH" - return $true -} - -function Main { - Show-Banner - Assert-Environment - - if (-not $InstallDir) { - if ($env:DEDALUS_INSTALL_DIR) { - $InstallDir = $env:DEDALUS_INSTALL_DIR - } else { - $InstallDir = Join-Path $HOME '.local\bin' - } - } - - if (-not $Version) { - if ($env:DEDALUS_VERSION) { - $Version = $env:DEDALUS_VERSION - } - } - - if (-not $NoModifyPath -and $env:DEDALUS_NO_MODIFY_PATH) { - $NoModifyPath = $true - } - - $arch = Get-Arch - Write-Info "Detected windows/$arch" - - if (-not $Version) { - $Version = Get-LatestVersion - } - Write-Info "Version: $Version" - - Install-Dedalus -Arch $arch -VersionTag $Version -Destination $InstallDir - - if (-not (Test-PathContains -Directory $InstallDir)) { - if ($NoModifyPath) { - Write-Warn "$InstallDir is not in your PATH" - Write-Host " Add it manually in PowerShell:" - Write-Host " [Environment]::SetEnvironmentVariable('Path', `"$InstallDir;`" + [Environment]::GetEnvironmentVariable('Path','User'), 'User')" - Write-Host "" - } else { - if (Add-UserPath -Directory $InstallDir) { - Write-Ok "Added $InstallDir to your user PATH" - Write-Host " Restart your shell for the change to take effect in new sessions." - Write-Host "" - } - } - } - - Write-Ok "Ready! Run:" - Write-Host " $Binary --help" - Write-Host "" -} - -Main diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100755 index 0d348d2..0000000 --- a/scripts/install.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -REPO="dedalus-labs/dedalus-cli" -BINARY="dedalus" -INSTALL_DIR="${DEDALUS_INSTALL_DIR:-$HOME/.local/bin}" -INSTALL_MARKER=".dedalus-cli-install" -TMPDIR_CLEANUP="" - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } -info() { echo -e "${BLUE}[INFO]${NC} $1"; } -success() { echo -e "${GREEN}[OK]${NC} $1"; } -warning() { echo -e "${YELLOW}[WARN]${NC} $1"; } - -cleanup_tmpdir() { - if [[ -n "${TMPDIR_CLEANUP:-}" ]]; then - rm -rf -- "${TMPDIR_CLEANUP}" - TMPDIR_CLEANUP="" - fi -} - -trap cleanup_tmpdir EXIT - -detect_platform() { - local os arch - - case "$(uname -s)" in - Linux) os="linux" ;; - Darwin) os="macos" ;; - *) error "Unsupported OS: $(uname -s)"; exit 1 ;; - esac - - case "$(uname -m)" in - x86_64|amd64) arch="amd64" ;; - aarch64|arm64) arch="arm64" ;; - *) error "Unsupported architecture: $(uname -m)"; exit 1 ;; - esac - - PLATFORM="${os}" - ARCH="${arch}" - info "Detected ${PLATFORM}/${ARCH}" -} - -get_latest_version() { - if ! command -v curl &>/dev/null; then - error "curl is required" - exit 1 - fi - - VERSION=$(curl -fsSL \ - -H 'Accept: application/vnd.github+json' \ - -H 'User-Agent: dedalus-cli-installer' \ - "https://api.github.com/repos/${REPO}/releases/latest" \ - | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') - - if [[ -z "$VERSION" ]]; then - error "Could not determine latest version from GitHub API" - exit 1 - fi - - info "Latest version: ${VERSION}" -} - -download_and_install() { - local version_num="${VERSION#v}" - local archive_name="${BINARY}_${version_num}_${PLATFORM}_${ARCH}" - local ext="tar.gz" - - if [[ "$PLATFORM" == "macos" ]]; then - ext="zip" - fi - - local url="https://github.com/${REPO}/releases/download/${VERSION}/${archive_name}.${ext}" - - cleanup_tmpdir - TMPDIR_CLEANUP=$(mktemp -d) - local tmpdir="${TMPDIR_CLEANUP}" - - info "Downloading ${url}..." - if ! curl -fsSL "$url" -o "${tmpdir}/archive.${ext}"; then - error "Download failed. Check that a release exists for ${PLATFORM}/${ARCH}" - exit 1 - fi - - info "Extracting..." - if [[ "$ext" == "zip" ]]; then - unzip -oq "${tmpdir}/archive.zip" -d "$tmpdir" - else - tar -xzf "${tmpdir}/archive.tar.gz" -C "$tmpdir" - fi - - mkdir -p "$INSTALL_DIR" - mv "${tmpdir}/${BINARY}" "${INSTALL_DIR}/${BINARY}" - chmod +x "${INSTALL_DIR}/${BINARY}" - printf 'method=install-script\nversion=%s\n' "$VERSION" > "${INSTALL_DIR}/${INSTALL_MARKER}" - success "Installed ${BINARY} to ${INSTALL_DIR}/${BINARY}" -} - -main() { - cat <<'ART' - - .. - .. - ... - .... - .... - ..... - ..... - ...... - ........ - ........ - ......... - ........... ... - ............ .... - ............. .... - .............. ..... - .............. ...... - .............. ....... - ............ ....... - ......... ........ - ........ ......... - ...... ........... -...... ............ -..... .. -.... .... -... .... -.. ....... -. .......... -. .................... -. .................. - - Dedalus CLI - dedaluslabs.ai - -ART - detect_platform - get_latest_version - download_and_install - - if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then - warning "${INSTALL_DIR} is not in your PATH" - echo " Add it to your shell profile:" - echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" - echo - fi - - if command -v "$BINARY" &>/dev/null; then - success "Ready! Run:" - echo " dedalus --help" - echo - fi -} - -main diff --git a/scripts/link b/scripts/link deleted file mode 100755 index c9d0047..0000000 --- a/scripts/link +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# Mark the necessary Go modules as private to avoid Go's proxy -export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go" - -REPLACEMENT="${1:-"../Dedalus-go"}" -echo "==> Replacing Go SDK with $REPLACEMENT" -if [[ -d "$REPLACEMENT" ]] || go list -m "$REPLACEMENT" >/dev/null; then - go mod edit -replace github.com/dedalus-labs/dedalus-go="$REPLACEMENT" - go mod tidy -e -else - echo "Skipping Go SDK replacement (branch may not exist on Go SDK)" -fi diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 86eae73..0000000 --- a/scripts/lint +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# Mark the necessary Go modules as private to avoid Go's proxy -export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go" - -echo "==> Running Go build" -go build ./... diff --git a/scripts/mock b/scripts/mock deleted file mode 100755 index 9c7c439..0000000 --- a/scripts/mock +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -if [[ -n "$1" && "$1" != '--'* ]]; then - URL="$1" - shift -else - URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" -fi - -# Check if the URL is empty -if [ -z "$URL" ]; then - echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" - exit 1 -fi - -echo "==> Starting mock server with URL ${URL}" - -# Run steady mock on the given spec -if [ "$1" == "--daemon" ]; then - # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.22.1 -- steady --version - - npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & - - # Wait for server to come online via health endpoint (max 30s) - echo -n "Waiting for server" - attempts=0 - while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do - if ! kill -0 $! 2>/dev/null; then - echo - cat .stdy.log - exit 1 - fi - attempts=$((attempts + 1)) - if [ "$attempts" -ge 300 ]; then - echo - echo "Timed out waiting for Steady server to start" - cat .stdy.log - exit 1 - fi - echo -n "." - sleep 0.1 - done - - echo -else - npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" -fi diff --git a/scripts/run b/scripts/run deleted file mode 100755 index 065b67a..0000000 --- a/scripts/run +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# Mark the necessary Go modules as private to avoid Go's proxy -export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go" - -go run ./cmd/dedalus "$@" diff --git a/scripts/test b/scripts/test deleted file mode 100755 index a967031..0000000 --- a/scripts/test +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -# Mark the necessary Go modules as private to avoid Go's proxy -export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/dedalus-labs/dedalus-go,github.com/stainless-sdks/dedalus-go" - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[0;33m' -NC='\033[0m' # No Color - -function steady_is_running() { - curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 -} - -kill_server_on_port() { - pids=$(lsof -t -i tcp:"$1" || echo "") - if [ "$pids" != "" ]; then - kill "$pids" - echo "Stopped $pids." - fi -} - -function is_overriding_api_base_url() { - [ -n "${TEST_API_BASE_URL:-}" ] -} - -if ! is_overriding_api_base_url && ! steady_is_running ; then - # When we exit this script, make sure to kill the background mock server process - trap 'kill_server_on_port 4010' EXIT - - # Start the dev server - ./scripts/mock --daemon -fi - -if is_overriding_api_base_url ; then - echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" - echo -elif ! steady_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" - echo -e "running against your OpenAPI spec." - echo - echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the steady command:" - echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" - echo - - exit 1 -else - echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" - echo -fi - -echo "==> Running tests" -go test ./... "$@" - -echo "==> Checking tests on Windows" -GOARCH=amd64 GOOS=windows go test -c ./... "$@" -# `go test -c` produces a bunch of .exe files; make sure to clean those up -find . -name "*.test.exe" -exec rm {} \; diff --git a/scripts/unlink b/scripts/unlink deleted file mode 100755 index e53990a..0000000 --- a/scripts/unlink +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -e - -cd "$(dirname "$0")/.." - -echo "==> Unlinking with local directory" -go mod edit -dropreplace github.com/dedalus-labs/dedalus-go diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh deleted file mode 100755 index 81fdc42..0000000 --- a/scripts/utils/upload-artifact.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -exuo pipefail - -BINARY_NAME="dedalus" -DIST_DIR="dist" -FILENAME="dist.zip" - -files=() -while IFS= read -r -d '' file; do - files+=("$file") -done < <(find "$DIST_DIR" -type f \( \ - -path "*amd64*/$BINARY_NAME" -o \ - -path "*arm64*/$BINARY_NAME" -o \ - -path "*amd64*/${BINARY_NAME}.exe" -o \ - -path "*arm64*/${BINARY_NAME}.exe" \ - \) -print0) - -if [[ ${#files[@]} -eq 0 ]]; then - echo -e "\033[31mNo binaries found for packaging.\033[0m" - exit 1 -fi - -rm -f "${DIST_DIR}/${FILENAME}" - -while IFS= read -r -d '' dir; do - printf "Remove the quarantine attribute before running the executable:\n\nxattr -d com.apple.quarantine %s\n" \ - "$BINARY_NAME" >"${dir}/README.txt" - files+=("${dir}/README.txt") -done < <(find "$DIST_DIR" -type d -path '*macos*' -print0) - -relative_files=() -for file in "${files[@]}"; do - relative_files+=("${file#"${DIST_DIR}"/}") -done - -(cd "$DIST_DIR" && zip -r "$FILENAME" "${relative_files[@]}") - -RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ - -H "Authorization: Bearer $AUTH" \ - -H "Content-Type: application/json") - -SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') - -if [[ "$SIGNED_URL" == "null" ]]; then - echo -e "\033[31mFailed to get signed URL.\033[0m" - exit 1 -fi - -UPLOAD_RESPONSE=$(curl -v -X PUT \ - -H "Content-Type: application/zip" \ - --data-binary "@${DIST_DIR}/${FILENAME}" "$SIGNED_URL" 2>&1) - -if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then - echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: Download and unzip: 'https://pkg.stainless.com/s/dedalus-cli/$SHA'. On macOS, run 'xattr -d com.apple.quarantine {executable name}'.\033[0m" -else - echo -e "\033[31mFailed to upload artifact.\033[0m" - exit 1 -fi diff --git a/src/bin.ts b/src/bin.ts new file mode 100644 index 0000000..dbe523e --- /dev/null +++ b/src/bin.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env node +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { run } from './index.js' + +void run() diff --git a/src/cli/runtime.ts b/src/cli/runtime.ts new file mode 100644 index 0000000..bd94700 --- /dev/null +++ b/src/cli/runtime.ts @@ -0,0 +1,707 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { stdin as processStdin, stdout as processStdout } from 'node:process' + +import as from 'ansis' +import { Command } from 'commander' +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' + +type OutputFormat = 'auto' | 'json' | 'jsonl' | 'pretty' | 'raw' | 'yaml' + +export type CliValueKind = + | 'string' + | 'number' + | 'integer' + | 'boolean' + | 'object' + | 'array' + | 'unknown' + +export type CliFlagDefinition = { + readonly name: string + readonly optionKey: string + readonly paramKey: string + readonly location: 'path' | 'query' | 'header' | 'cookie' | 'body' + readonly required: boolean + readonly description?: string + readonly valueKind: CliValueKind + // Array-valued flag accepted as a repeatable singular switch (`--status a --status b`). + readonly repeatable?: boolean + // Wire-property path under the parent param for dotted leaf flags (e.g. `--address.city`). + readonly objectPath?: readonly string[] +} + +export type CliCommandDefinition = { + readonly resourcePath: readonly string[] + readonly commandPath: readonly string[] + readonly methodName: string + readonly summary?: string + readonly description?: string + readonly transport: 'http' | 'websocket' + readonly streaming?: 'sse' | 'jsonl' + readonly iterable: boolean + readonly callShape: 'options' | 'params' | 'body' + // Param key of a non-flattenable body argument forwarded to the SDK method as a single value. + readonly bodyParamKey?: string + readonly positional: readonly CliFlagDefinition[] + readonly flags: readonly CliFlagDefinition[] +} + +export type CliClientOptionDefinition = { + readonly clientKey: string + readonly sdkKey: string + readonly name: string + readonly optionKey: string + readonly env?: string + readonly description?: string + readonly auth: boolean +} + +export type CreateProgramOptions = { + readonly SDK: new (...args: any[]) => unknown + readonly binaryName: string + readonly version: string + readonly description: string + readonly defaultFormat: OutputFormat + readonly defaultErrorFormat: OutputFormat + readonly clientOptions: readonly CliClientOptionDefinition[] + readonly commands: readonly CliCommandDefinition[] +} + +type OutputOptions = { + readonly format: OutputFormat + // Command path shown above each `pretty` card (e.g. `workers list`). + readonly title?: string + readonly transform?: string + readonly rawOutput?: boolean + readonly maxItems?: number + readonly failOnWebSocketError?: boolean + readonly onLimit?: () => void +} + +type GlobalOptions = { + readonly baseUrl?: string + readonly timeout?: string + readonly maxRetries?: string + readonly format?: OutputFormat + readonly formatError?: OutputFormat + readonly transform?: string + readonly transformError?: string + readonly rawOutput?: boolean + readonly debug?: boolean + readonly maxItems?: string +} + +export const createProgram = ({ SDK, binaryName, version, description, defaultFormat, defaultErrorFormat, clientOptions, commands }: CreateProgramOptions): Command => { + const program = new Command() + program + .enablePositionalOptions() + .name(binaryName) + .description(description) + .version(version, '-v, --version') + .showHelpAfterError() + .option('--base-url ', 'Override the base URL for API requests') + .option('--timeout ', 'Request timeout in milliseconds') + .option('--max-retries ', 'Number of retries for retryable failures') + .option('--format ', 'Output format: auto, json, jsonl, pretty, raw, yaml', defaultFormat) + .option('--format-error ', 'Error output format: auto, json, jsonl, pretty, raw, yaml', defaultErrorFormat) + .option('--transform ', 'Dot-path transform for data output') + .option('--transform-error ', 'Dot-path transform for error output') + .option('-r, --raw-output', 'Print transformed string values without JSON quotes') + .option('--debug', 'Enable SDK debug logging') + + // Register configured client options (auth credentials, org headers, etc.) as global flags. + // Mirrored on each subcommand below so users can supply them either before or after the verb. + for (const option of clientOptions) { + program.option("--" + option.name + " ", clientOptionDescription(option)) + } + + for (const definition of commands) addGeneratedCommand(program, SDK, clientOptions, definition) + + return program +} + +const clientOptionDescription = (option: CliClientOptionDefinition): string => { + const base = option.description ?? "" + if (!option.env) return base + const envHint = "(can also be set with " + option.env + " env var)" + return base ? base + " " + envHint : envHint +} + +const addGeneratedCommand = ( + program: Command, + SDK: CreateProgramOptions["SDK"], + clientOptions: readonly CliClientOptionDefinition[], + definition: CliCommandDefinition, +): void => { + const parent = ensureCommandPath(program, definition.commandPath.slice(0, -1)) + const commandName = definition.commandPath.at(-1) ?? definition.methodName + const command = new Command(commandName) + .description(definition.summary ?? definition.description ?? "") + .showHelpAfterError() + .option('--base-url ', 'Override the base URL for API requests') + .option('--timeout ', 'Request timeout in milliseconds') + .option('--max-retries ', 'Number of retries for retryable failures') + .option('--format ', 'Output format: auto, json, jsonl, pretty, raw, yaml') + .option('--format-error ', 'Error output format: auto, json, jsonl, pretty, raw, yaml') + .option('--transform ', 'Dot-path transform for data output') + .option('--transform-error ', 'Dot-path transform for error output') + .option('-r, --raw-output', 'Print transformed string values without JSON quotes') + .option('--debug', 'Enable SDK debug logging') + + // Mirror configured client-option flags on the subcommand so they can appear before or after the verb. + for (const option of clientOptions) { + command.option("--" + option.name + " ", clientOptionDescription(option)) + } + + if (definition.iterable) command.option("--max-items ", "Maximum number of streamed items to print; use -1 for unlimited") + + // Positionals are registered as optional Commander arguments because each one is also + // accepted as an equivalent flag (e.g. `workers retrieve wkr_1` or `workers retrieve --id + // wkr_1`); requiredness is enforced at call time once both spellings have been merged. + for (const positional of definition.positional) { + command.argument("[" + positional.name + "]", positional.description ?? "") + } + + for (const flag of definition.flags) { + const value = flag.valueKind === "boolean" ? "" : " " + if (flag.name === "send") { + command.option("--" + flag.name + value, flag.description ?? "", (value: string, previous: string[] | undefined) => [...(previous ?? []), value]) + continue + } + // Array params are repeatable single-value switches (`--status a --status b`); the custom + // option-argument accumulates each occurrence so Commander does not overwrite the prior value. + if (flag.repeatable) { + command.option("--" + flag.name + value, flag.description ?? "", (value: string, previous: string[] | undefined) => [...(previous ?? []), value]) + continue + } + command.option("--" + flag.name + value, flag.description ?? "") + } + + // Flag spelling for path params (`--id wkr_1`); skipped when the name is already taken by a + // client option or generated flag so Commander does not throw on a duplicate registration. + for (const positional of definition.positional) { + if (command.options.some((option) => option.long === "--" + positional.name)) continue + command.option("--" + positional.name + " ", positional.description ?? "") + } + + command.action(async (...args: unknown[]) => { + const command = args.at(-1) + if (!(command instanceof Command)) throw new Error("Expected Commander command context") + const positionalValues = args.slice(0, -1) + await runGeneratedCommand(SDK, clientOptions, definition, command, positionalValues) + }) + + parent.addCommand(command) +} + +const ensureCommandPath = (program: Command, path: readonly string[]): Command => { + let parent = program + for (const part of path) { + const existing = parent.commands.find((command) => command.name() === part) + if (existing) { + parent = existing + continue + } + const next = new Command(part).showHelpAfterError() + parent.addCommand(next) + parent = next + } + return parent +} + +const runGeneratedCommand = async ( + SDK: CreateProgramOptions["SDK"], + clientOptions: readonly CliClientOptionDefinition[], + definition: CliCommandDefinition, + command: Command, + positionalValues: readonly unknown[], +): Promise => { + const rootOptions = command.optsWithGlobals() + const commandOptions = command.opts() + const maxItems = definition.iterable ? normalizeMaxItems(commandOptions.maxItems) : undefined + const outputOptions: OutputOptions = { + format: normalizeFormat(commandOptions.format ?? rootOptions.format, "auto"), + title: definition.commandPath.join(" "), + ...(commandOptions.transform ?? rootOptions.transform ? { transform: commandOptions.transform ?? rootOptions.transform } : {}), + ...(commandOptions.rawOutput || rootOptions.rawOutput ? { rawOutput: true } : {}), + ...(maxItems !== undefined ? { maxItems } : {}), + } + const errorOptions: OutputOptions = { + format: normalizeFormat(commandOptions.formatError ?? rootOptions.formatError, "auto"), + ...(commandOptions.transformError ?? rootOptions.transformError ? { transform: commandOptions.transformError ?? rootOptions.transformError } : {}), + ...(commandOptions.rawOutput || rootOptions.rawOutput ? { rawOutput: true } : {}), + } + + try { + const client = new SDK(sdkClientOptions(rootOptions, command, clientOptions)) as Record + const method = sdkMethod(client, definition) + const call = await callArguments(definition, command.opts>(), positionalValues) + + // Required positionals are validated here (not by Commander) because each one may also be + // supplied through its flag spelling or stdin; `call.params` has all sources merged. + for (const param of definition.positional) { + if (param.required && call.params[param.paramKey] === undefined) { + command.error("error: missing required argument '" + param.name + "'") + } + } + + const result = method(...call.args) + + if (definition.transport === "websocket") { + await handleWebSocket(result, call.params, outputOptions) + return + } + + if (definition.iterable && !definition.streaming) { + await writePaginated(result, outputOptions) + return + } + + const resolved = await result + if (definition.streaming) { + await writeIterable(resolved, outputOptions) + return + } + + await writeOutput(resolved, outputOptions) + } catch (error) { + await writeError(error, errorOptions, clientOptions) + process.exitCode = 1 + } +} + +const sdkClientOptions = ( + options: GlobalOptions, + command: Command, + clientOptions: readonly CliClientOptionDefinition[], +): Record => { + // Forward configured client-option flags (auth keys, org headers, etc.) to the embedded SDK + // using the SDK-facing camelCased key. Only forward values that were explicitly set so the + // SDK's own env-var fallback keeps working when no CLI flag was passed. + const raw = options as unknown as Record + const forwarded: Record = {} + for (const option of clientOptions) { + const value = raw[option.optionKey] + if (value !== undefined) forwarded[option.sdkKey] = value + } + return { + ...(options.baseUrl ? { baseURL: options.baseUrl } : {}), + ...(options.timeout ? { timeout: Number(options.timeout) } : {}), + ...(options.maxRetries ? { maxRetries: Number(options.maxRetries) } : {}), + ...(options.debug ? { logLevel: "debug" } : {}), + ...forwarded, + defaultHeaders: { + "X-Scalar-Lang": "cli", + "X-Scalar-Runtime": "cli", + "X-Scalar-CLI-Command": command.name(), + }, + } +} + +const sdkMethod = (client: Record, definition: CliCommandDefinition): ((...args: unknown[]) => unknown) => { + let target: unknown = client + for (const resource of definition.resourcePath) { + target = (target as Record)[resource] + } + const method = (target as Record)[definition.methodName] + if (typeof method !== "function") { + throw new Error("Generated CLI could not find SDK method " + [...definition.resourcePath, definition.methodName].join(".")) + } + return method.bind(target) as (...args: unknown[]) => unknown +} + +const callArguments = async ( + definition: CliCommandDefinition, + options: Record, + positionalValues: readonly unknown[], +): Promise<{ readonly args: readonly unknown[]; readonly params: Record }> => { + const positionalParams: Record = {} + definition.positional.forEach((param, index) => { + const value = positionalValues[index] ?? options[param.optionKey] + if (value !== undefined) positionalParams[param.paramKey] = coerceValue(value, param.valueKind) + }) + + const flagParams: Record = {} + for (const flag of definition.flags) { + if (flag.objectPath) continue + const value = options[flag.optionKey] + if (value !== undefined) flagParams[flag.paramKey] = coerceValue(value, flag.valueKind) + } + + // Dotted leaf flags (e.g. `--address.city`) are applied after the JSON-blob flag for the same + // param so an explicit leaf value always overrides the corresponding blob field. + for (const flag of definition.flags) { + if (!flag.objectPath || flag.objectPath.length === 0) continue + const value = options[flag.optionKey] + if (value === undefined) continue + flagParams[flag.paramKey] = setNestedValue(flagParams[flag.paramKey], flag.objectPath, coerceValue(value, flag.valueKind)) + } + + const stdin = await readStdinValue() + const params = mergeObjects(stdin, { ...flagParams, ...positionalParams }) + const positionalArgs = definition.positional.map((param) => params[param.paramKey]) + const sdkParams = definition.transport === "websocket" ? omitParams(params, ["send"]) : params + + if (definition.callShape === "options") return { args: [...positionalArgs, undefined], params } + if (definition.callShape === "body") return { args: [...positionalArgs, bodyValue(sdkParams, definition), undefined], params } + return { args: [...positionalArgs, sdkParams, undefined], params } +} + +const bodyValue = (params: Record, definition: CliCommandDefinition): unknown => { + // A non-flattenable body is forwarded as a single value: the SDK method takes that param + // directly, so return it bare. Its dotted leaf flags (`--payload.city`) share this key and have + // already been merged into the param value, so counting body flags would wrongly treat one + // logical body as many and re-wrap it under the param key. `params.body` covers a bare value + // piped via stdin. + if (definition.bodyParamKey !== undefined) { + if (params[definition.bodyParamKey] !== undefined) return params[definition.bodyParamKey] + if (params.body !== undefined) return params.body + } + // A flattenable body is reassembled from its per-property flags into a single object. Leaf flags + // share their parent property key, so keying by paramKey collapses them back onto that property. + const bodyFlags = definition.flags.filter((flag) => flag.location === "body" && flag.paramKey !== "send") + const body: Record = {} + for (const flag of bodyFlags) { + if (params[flag.paramKey] !== undefined) body[flag.paramKey] = params[flag.paramKey] + } + return Object.keys(body).length > 0 ? body : params +} + +const readStdinValue = async (): Promise> => { + if (processStdin.isTTY) return {} + const source = await readStdinSource() + if (!source) return {} + const parsed = parseStructuredValue(source) + if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record + return { body: parsed } +} + +const readStdinSource = async (): Promise => { + const chunks: Buffer[] = [] + const done = new Promise((resolve, reject) => { + const cleanup = () => { + clearTimeout(timer) + processStdin.off("data", onData) + processStdin.off("end", onEnd) + processStdin.off("error", onError) + processStdin.pause() + } + const onData = (chunk: Buffer | string) => { + clearTimeout(timer) + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) + } + const onEnd = () => { + cleanup() + resolve(Buffer.concat(chunks).toString("utf8").trim()) + } + const onError = (error: Error) => { + cleanup() + reject(error) + } + const timer = setTimeout(() => { + cleanup() + resolve("") + }, 25) + processStdin.on("data", onData) + processStdin.on("end", onEnd) + processStdin.on("error", onError) + }) + processStdin.resume() + return done +} + +const parseStructuredValue = (source: string): unknown => { + try { + return JSON.parse(source) + } catch { + return parseYaml(source) + } +} + +const setNestedValue = (target: unknown, path: readonly string[], value: unknown): Record => { + const root = isPlainObject(target) ? { ...target } : {} + let cursor = root + for (const segment of path.slice(0, -1)) { + const existing = cursor[segment] + const next = isPlainObject(existing) ? { ...existing } : {} + cursor[segment] = next + cursor = next + } + const leaf = path.at(-1) + if (leaf !== undefined) cursor[leaf] = value + return root +} + +const isPlainObject = (value: unknown): value is Record => + !!value && typeof value === "object" && !Array.isArray(value) + +const mergeObjects = (base: Record, overlay: Record): Record => ({ + ...base, + ...Object.fromEntries(Object.entries(overlay).filter(([, value]) => value !== undefined)), +}) + +const omitParams = (params: Record, names: readonly string[]): Record => { + const out = { ...params } + for (const name of names) delete out[name] + return out +} + +const coerceValue = (value: unknown, kind: CliValueKind): unknown => { + if (Array.isArray(value)) return value.map((item) => coerceValue(item, kind === "array" ? "unknown" : kind)) + if (typeof value !== "string") return value + if (kind === "boolean") return value === "true" || value === "1" + if (kind === "number" || kind === "integer") return Number(value) + if (kind === "object" || kind === "array" || kind === "unknown") return parseStructuredValue(value) + return value +} + +// Iterator commands print one item at a time so pipes can consume long-running streams immediately. +const writeIterable = async (value: unknown, options: OutputOptions): Promise => { + if (!isAsyncIterable(value)) { + await writeOutput(value, options) + return + } + if (options.maxItems === 0) { + options.onLimit?.() + return + } + let written = 0 + for await (const item of value) { + if (options.failOnWebSocketError) throwWebSocketEventError(item) + processStdout.write(serializeOutput(transformValue(item, options.transform), options) + "\n") + if (!countsTowardLimit(item, options)) continue + written += 1 + if (options.maxItems !== undefined && options.maxItems > -1 && written >= options.maxItems) { + options.onLimit?.() + break + } + } +} + +// Paginated list commands auto-page across cursors: the SDK PagePromise iterates items across +// pages. `raw` is the explicit escape hatch for the unmodified single-response envelope, so it +// stays a single request with no auto-paging. For every other format we hand the page iterator +// to writeOutput, which already makes the right split: `jsonl` streams each item as it arrives, +// while `json`/`auto`/`pretty`/`yaml` collect the fully auto-paged items into one value (honoring +// --max-items either way). Routing through writeIterable here instead would stream items for +// every format, emitting newline-delimited objects under `--format json` (invalid as a single +// JSON document) and nothing at all for an empty result. +const writePaginated = async (result: unknown, options: OutputOptions): Promise => { + if (options.format === "raw") { + const page = await result + const envelope = page && typeof page === "object" && "body" in page ? (page as { body?: unknown }).body : undefined + await writeOutput(envelope ?? page, options) + return + } + if (isAsyncIterable(result)) { + await writeOutput(result, options) + return + } + await writeOutput(await result, options) +} + +const countsTowardLimit = (item: unknown, options: OutputOptions): boolean => { + if (!options.failOnWebSocketError) return true + if (!item || typeof item !== "object") return false + const type = (item as { type?: unknown }).type + return type === "message" || type === "raw" +} + +// WebSocket SDKs expose lifecycle events as iterator values; error events should fail CLI commands. +const handleWebSocket = async (socket: unknown, params: Record, options: OutputOptions): Promise => { + const closer = () => { + closeSocket(socket, "interrupted") + } + process.once("SIGINT", closer) + try { + const output = writeIterable(socket, { ...options, failOnWebSocketError: true, onLimit: () => closeSocket(socket, "max-items reached") }) + await Promise.resolve() + const sendValue = params.send + if (sendValue !== undefined) sendSocketValue(socket, sendValue) + if (!processStdin.isTTY) { + const stdin = await readStdinValue() + if (Object.keys(stdin).length > 0) sendSocketValue(socket, stdin.body ?? stdin) + } + await output + } finally { + process.off("SIGINT", closer) + } +} + +const closeSocket = (socket: unknown, reason: string): void => { + const close = (socket as { close?: (options?: unknown) => void }).close + if (typeof close === "function") close.call(socket, { code: 1000, reason }) +} + +const sendSocketValue = (socket: unknown, value: unknown): void => { + const send = (socket as { send?: (message: unknown) => void }).send + if (typeof send !== "function") throw new Error("Generated CLI could not send on SDK WebSocket client") + if (Array.isArray(value)) { + for (const item of value) send.call(socket, item) + return + } + send.call(socket, value) +} + +const writeOutput = async (value: unknown, options: OutputOptions): Promise => { + if (isAsyncIterable(value)) { + if (options.format === "jsonl") { + await writeIterable(value, options) + return + } + await writeOutput(await collectIterable(value, options.maxItems), options) + return + } + processStdout.write(serializeOutput(transformValue(value, options.transform), options) + "\n") +} + +const collectIterable = async (value: AsyncIterable, maxItems?: number): Promise => { + const items: unknown[] = [] + for await (const item of value) { + if (maxItems === 0) break + items.push(item) + if (maxItems !== undefined && maxItems > -1 && items.length >= maxItems) break + } + return items +} + +// `auto` renders like `json` (2-space pretty-printed): `pretty` is reserved for the distinct +// human-readable card view, matching warp-style CLI defaults. +const serializeOutput = (value: unknown, options: OutputOptions): string => { + const normalized = options.format === "auto" ? "json" : options.format + if (options.rawOutput && typeof value === "string") return value + const safeValue = value === undefined ? null : value + if (normalized === "raw") return typeof safeValue === "string" ? safeValue : JSON.stringify(safeValue) + if (normalized === "yaml") return stringifyYaml(JSON.parse(JSON.stringify(safeValue))).trimEnd() + if (normalized === "jsonl") return JSON.stringify(safeValue) + if (normalized === "pretty") return prettyCard(safeValue, options) + return JSON.stringify(safeValue, null, 2) +} + +// Human-readable `pretty` view: a bordered key/value card titled with the command path, +// rendering booleans as yes/no and array entries as numbered items. +const prettyCard = (value: unknown, options: OutputOptions): string => { + const lines = prettyLines(value, "") + const width = Math.max(0, ...lines.map((line) => line.length)) + const body = lines.length > 0 ? lines : [""] + return [ + ...(options.title ? [" " + options.title] : []), + "\u256d" + "\u2500".repeat(width + 2) + "\u256e", + ...body.map((line) => "\u2502 " + line.padEnd(width, " ") + " \u2502"), + "\u2570" + "\u2500".repeat(width + 2) + "\u256f", + ].join("\n") +} + +const prettyLines = (value: unknown, indent: string): string[] => { + if (Array.isArray(value)) { + return value.flatMap((item, index) => { + const label = indent + (index + 1) + "." + if (item && typeof item === "object") return [label, ...prettyLines(item, indent + " ")] + return [label + " " + prettyScalar(item)] + }) + } + if (value && typeof value === "object") { + return Object.entries(value as Record).flatMap(([key, entry]) => { + if (entry && typeof entry === "object") return [indent + key + ":", ...prettyLines(entry, indent + " ")] + return [indent + key + ": " + prettyScalar(entry)] + }) + } + return [indent + prettyScalar(value)] +} + +const prettyScalar = (value: unknown): string => { + if (value === true) return "yes" + if (value === false) return "no" + if (value === null || value === undefined) return "" + return String(value) +} + +const writeError = async ( + error: unknown, + options: OutputOptions, + clientOptions: readonly CliClientOptionDefinition[], +): Promise => { + const body = transformValue(errorBody(error, clientOptions), options.transform) + if (options.rawOutput && typeof body === "string") { + process.stderr.write(body + "\n") + return + } + if (options.format === "raw") { + process.stderr.write(String(errorMessage(body)) + "\n") + return + } + const output = options.format === "auto" ? "pretty" : options.format + const safeBody = body === undefined ? null : body + const serialized = output === "yaml" ? stringifyYaml(safeBody).trimEnd() : JSON.stringify(safeBody, null, output === "jsonl" ? 0 : 2) + process.stderr.write((output === "pretty" ? as.red(serialized) : serialized) + "\n") +} + +const errorMessage = (value: unknown): unknown => + value && typeof value === "object" && "message" in value ? (value as { message?: unknown }).message : value + +const errorBody = ( + error: unknown, + clientOptions: readonly CliClientOptionDefinition[], +): Record => { + if (error && typeof error === "object") { + const record = error as Record + const hint = authHint(record, clientOptions) + return { + name: record.name, + message: hint ?? record.message ?? String(error), + status: record.status, + requestId: record.requestID ?? record.requestId, + body: record.body, + } + } + return { message: String(error) } +} + +const authHint = ( + error: Record, + clientOptions: readonly CliClientOptionDefinition[], +): string | undefined => { + if (error.status !== 401) return undefined + const authOptions = clientOptions.filter((option) => option.auth) + if (authOptions.length === 0) return undefined + const env = authOptions.map((option) => option.env).filter((value): value is string => !!value).join(", ") + return env ? "Authentication failed. Set " + env + " and try again." : "Authentication failed. Set the required authentication environment variable and try again." +} + +// Keep transforms small and dependency-free; the CLI supports the common dot-path extraction case. +const transformValue = (value: unknown, transform: string | undefined): unknown => { + if (!transform) return value + return transform.split(".").filter(Boolean).reduce((current, segment) => { + if (current === undefined || current === null) return undefined + if (Array.isArray(current) && /^\\d+$/u.test(segment)) return current[Number(segment)] + if (typeof current === "object") return (current as Record)[segment] + return undefined + }, value) +} + +const throwWebSocketEventError = (value: unknown): void => { + if (!value || typeof value !== "object") return + const record = value as Record + if (record.type !== "error") return + const error = record.error + if (error instanceof Error) throw error + throw new Error(typeof error === "string" ? error : JSON.stringify(error ?? record)) +} + +const normalizeFormat = (value: string | undefined, fallback: OutputFormat): OutputFormat => { + if (value === "auto" || value === "json" || value === "jsonl" || value === "pretty" || value === "raw" || value === "yaml") { + return value + } + return fallback +} + +const normalizeMaxItems = (value: string | undefined): number | undefined => { + if (value === undefined) return undefined + const parsed = Number(value) + if (!Number.isFinite(parsed)) return undefined + return Math.trunc(parsed) +} + +const isAsyncIterable = (value: unknown): value is AsyncIterable => + !!value && typeof (value as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === "function" diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..193ae47 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,1602 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import type { Command } from 'commander' +import SDK from '../sdk/index' +import { createProgram, type CliClientOptionDefinition, type CliCommandDefinition } from '../cli/runtime' + +const clientOptions = [ + { + "clientKey": "apiKeyAuth", + "sdkKey": "apiKeyAuth", + "name": "api-key-auth", + "optionKey": "apiKeyAuth", + "env": "API_KEY_AUTH", + "description": "API key authentication using X-API-Key header", + "auth": true + }, + { + "clientKey": "bearerAuth", + "sdkKey": "bearerAuth", + "name": "bearer-auth", + "optionKey": "bearerAuth", + "env": "BEARER_AUTH", + "description": "Dedalus API key in Authorization: Bearer .", + "auth": true + }, + { + "clientKey": "bearer", + "sdkKey": "bearer", + "name": "bearer", + "optionKey": "bearer", + "env": "BEARER", + "description": "API key authentication using Bearer token", + "auth": true + }, + { + "clientKey": "provider", + "sdkKey": "provider", + "name": "provider", + "optionKey": "provider", + "env": "DEDALUS_PROVIDER", + "description": "Provider name for BYOK mode.", + "auth": false + }, + { + "clientKey": "providerKey", + "sdkKey": "providerKey", + "name": "provider-key", + "optionKey": "providerKey", + "env": "DEDALUS_PROVIDER_KEY", + "description": "Provider API key for BYOK mode.", + "auth": false + }, + { + "clientKey": "providerModel", + "sdkKey": "providerModel", + "name": "provider-model", + "optionKey": "providerModel", + "env": "DEDALUS_PROVIDER_MODEL", + "description": "Model identifier for BYOK provider.", + "auth": false + } +] as const satisfies readonly CliClientOptionDefinition[] + +const commands = [ + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list" + ], + "methodName": "list", + "summary": "List machines", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "create" + ], + "methodName": "create", + "summary": "Create machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "autosleep", + "optionKey": "autosleep", + "paramKey": "autosleep", + "location": "body", + "required": false, + "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", + "valueKind": "string" + }, + { + "name": "memory-mib", + "optionKey": "memoryMib", + "paramKey": "memory_mib", + "location": "body", + "required": true, + "description": "Memory in MiB.", + "valueKind": "integer" + }, + { + "name": "storage-gib", + "optionKey": "storageGib", + "paramKey": "storage_gib", + "location": "body", + "required": true, + "description": "Storage in GiB.", + "valueKind": "integer" + }, + { + "name": "vcpu", + "optionKey": "vcpu", + "paramKey": "vcpu", + "location": "body", + "required": true, + "description": "CPU in vCPUs.", + "valueKind": "number" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete" + ], + "methodName": "delete", + "summary": "Destroy machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve" + ], + "methodName": "retrieve", + "summary": "Get machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "patch" + ], + "methodName": "patch", + "summary": "Update machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "autosleep", + "optionKey": "autosleep", + "paramKey": "autosleep", + "location": "body", + "required": false, + "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", + "valueKind": "string" + }, + { + "name": "memory-mib", + "optionKey": "memoryMib", + "paramKey": "memory_mib", + "location": "body", + "required": false, + "description": "Memory in MiB.", + "valueKind": "integer" + }, + { + "name": "storage-gib", + "optionKey": "storageGib", + "paramKey": "storage_gib", + "location": "body", + "required": false, + "description": "Storage in GiB.", + "valueKind": "integer" + }, + { + "name": "vcpu", + "optionKey": "vcpu", + "paramKey": "vcpu", + "location": "body", + "required": false, + "description": "CPU in vCPUs.", + "valueKind": "number" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-artifacts" + ], + "methodName": "listArtifacts", + "summary": "List artifacts", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete-artifact" + ], + "methodName": "deleteArtifact", + "summary": "Delete artifact", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "artifact-id", + "optionKey": "artifactId", + "paramKey": "artifact_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve-artifact" + ], + "methodName": "retrieveArtifact", + "summary": "Get artifact", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "artifact-id", + "optionKey": "artifactId", + "paramKey": "artifact_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-executions" + ], + "methodName": "listExecutions", + "summary": "List executions", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "create-execution" + ], + "methodName": "createExecution", + "summary": "Create execution", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "command", + "optionKey": "command", + "paramKey": "command", + "location": "body", + "required": true, + "valueKind": "array" + }, + { + "name": "cwd", + "optionKey": "cwd", + "paramKey": "cwd", + "location": "body", + "required": false, + "valueKind": "string" + }, + { + "name": "env", + "optionKey": "env", + "paramKey": "env", + "location": "body", + "required": false, + "valueKind": "object" + }, + { + "name": "stdin", + "optionKey": "stdin", + "paramKey": "stdin", + "location": "body", + "required": false, + "valueKind": "string" + }, + { + "name": "timeout-ms", + "optionKey": "timeoutMs", + "paramKey": "timeout_ms", + "location": "body", + "required": false, + "valueKind": "integer" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete-execution" + ], + "methodName": "deleteExecution", + "summary": "Delete execution", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "execution-id", + "optionKey": "executionId", + "paramKey": "execution_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve-execution" + ], + "methodName": "retrieveExecution", + "summary": "Get execution", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "execution-id", + "optionKey": "executionId", + "paramKey": "execution_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-execution-events" + ], + "methodName": "listExecutionEvents", + "summary": "List execution events", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "execution-id", + "optionKey": "executionId", + "paramKey": "execution_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-execution-output" + ], + "methodName": "listExecutionOutput", + "summary": "Get execution output", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "execution-id", + "optionKey": "executionId", + "paramKey": "execution_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-previews" + ], + "methodName": "listPreviews", + "summary": "List previews", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "create-preview" + ], + "methodName": "createPreview", + "summary": "Create preview", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "port", + "optionKey": "port", + "paramKey": "port", + "location": "body", + "required": true, + "valueKind": "integer" + }, + { + "name": "protocol", + "optionKey": "protocol", + "paramKey": "protocol", + "location": "body", + "required": false, + "valueKind": "string" + }, + { + "name": "visibility", + "optionKey": "visibility", + "paramKey": "visibility", + "location": "body", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete-preview" + ], + "methodName": "deletePreview", + "summary": "Delete preview", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "preview-id", + "optionKey": "previewId", + "paramKey": "preview_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve-preview" + ], + "methodName": "retrievePreview", + "summary": "Get preview", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "preview-id", + "optionKey": "previewId", + "paramKey": "preview_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "sleep" + ], + "methodName": "sleep", + "summary": "Sleep a running machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-ssh-sessions" + ], + "methodName": "listSSHSessions", + "summary": "List SSH sessions", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "create-ssh-session" + ], + "methodName": "createSSHSession", + "summary": "Create SSH session", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "public-key", + "optionKey": "publicKey", + "paramKey": "public_key", + "location": "body", + "required": true, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete-ssh-session" + ], + "methodName": "deleteSSHSession", + "summary": "Delete SSH session", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "session-id", + "optionKey": "sessionId", + "paramKey": "session_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve-ssh-session" + ], + "methodName": "retrieveSSHSession", + "summary": "Get SSH session", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "session-id", + "optionKey": "sessionId", + "paramKey": "session_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "watch-status" + ], + "methodName": "watchStatus", + "summary": "Watch machine lifecycle status", + "description": "Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state.", + "transport": "http", + "streaming": "sse", + "iterable": true, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "description": "Machine identifier.", + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "description": "Organization ID header applied to all DCS requests.", + "valueKind": "string" + }, + { + "name": "last-event-id", + "optionKey": "lastEventId", + "paramKey": "Last-Event-ID", + "location": "header", + "required": false, + "description": "Optional resourceVersion bookmark used to resume a previous stream.", + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "list-terminals" + ], + "methodName": "listTerminals", + "summary": "List terminals", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "limit", + "optionKey": "limit", + "paramKey": "limit", + "location": "query", + "required": false, + "valueKind": "integer" + }, + { + "name": "cursor", + "optionKey": "cursor", + "paramKey": "cursor", + "location": "query", + "required": false, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "create-terminal" + ], + "methodName": "createTerminal", + "summary": "Create terminal", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + }, + { + "name": "cwd", + "optionKey": "cwd", + "paramKey": "cwd", + "location": "body", + "required": false, + "valueKind": "string" + }, + { + "name": "env", + "optionKey": "env", + "paramKey": "env", + "location": "body", + "required": false, + "valueKind": "object" + }, + { + "name": "height", + "optionKey": "height", + "paramKey": "height", + "location": "body", + "required": true, + "valueKind": "integer" + }, + { + "name": "shell", + "optionKey": "shell", + "paramKey": "shell", + "location": "body", + "required": false, + "valueKind": "string" + }, + { + "name": "width", + "optionKey": "width", + "paramKey": "width", + "location": "body", + "required": true, + "valueKind": "integer" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "delete-terminal" + ], + "methodName": "deleteTerminal", + "summary": "Delete terminal", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "terminal-id", + "optionKey": "terminalId", + "paramKey": "terminal_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "retrieve-terminal" + ], + "methodName": "retrieveTerminal", + "summary": "Get terminal", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "terminal-id", + "optionKey": "terminalId", + "paramKey": "terminal_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "connect-terminal" + ], + "methodName": "connectTerminal", + "summary": "Connect to terminal WebSocket stream", + "description": "Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields.", + "transport": "websocket", + "iterable": true, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "description": "Machine identifier.", + "valueKind": "string" + }, + { + "name": "terminal-id", + "optionKey": "terminalId", + "paramKey": "terminal_id", + "location": "path", + "required": true, + "description": "Terminal identifier.", + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "description": "Organization ID header applied to all DCS requests.", + "valueKind": "string" + }, + { + "name": "send", + "optionKey": "send", + "paramKey": "send", + "location": "body", + "required": false, + "description": "JSON message to send after connecting.", + "valueKind": "unknown" + } + ] + }, + { + "resourcePath": [ + "machineLifecycle" + ], + "commandPath": [ + "machine-lifecycle", + "wake" + ], + "methodName": "wake", + "summary": "Wake a sleeping machine", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "path", + "required": true, + "valueKind": "string" + }, + { + "name": "x-dedalus-org-id", + "optionKey": "xDedalusOrgId", + "paramKey": "X-Dedalus-Org-Id", + "location": "header", + "required": false, + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "usage" + ], + "commandPath": [ + "usage", + "list" + ], + "methodName": "list", + "summary": "Get usage summary", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "period-start", + "optionKey": "periodStart", + "paramKey": "period_start", + "location": "query", + "required": false, + "description": "Billing period start (YYYY-MM-DD). Defaults to first of current month.", + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "usage", + "machines" + ], + "commandPath": [ + "usage:machines", + "list-compute-usage" + ], + "methodName": "listComputeUsage", + "summary": "List machine compute usage breakdown", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "period-start", + "optionKey": "periodStart", + "paramKey": "period_start", + "location": "query", + "required": false, + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "valueKind": "string" + }, + { + "name": "period-end", + "optionKey": "periodEnd", + "paramKey": "period_end", + "location": "query", + "required": false, + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "valueKind": "string" + }, + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "query", + "required": false, + "description": "Optional machine ID filter.", + "valueKind": "string" + }, + { + "name": "granularity", + "optionKey": "granularity", + "paramKey": "granularity", + "location": "query", + "required": false, + "description": "Usage breakdown granularity: hour or day. Defaults to hour.", + "valueKind": "string" + } + ] + }, + { + "resourcePath": [ + "usage", + "machines" + ], + "commandPath": [ + "usage:machines", + "list-storage-usage" + ], + "methodName": "listStorageUsage", + "summary": "List machine storage usage breakdown", + "transport": "http", + "iterable": false, + "callShape": "params", + "positional": [], + "flags": [ + { + "name": "period-start", + "optionKey": "periodStart", + "paramKey": "period_start", + "location": "query", + "required": false, + "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", + "valueKind": "string" + }, + { + "name": "period-end", + "optionKey": "periodEnd", + "paramKey": "period_end", + "location": "query", + "required": false, + "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", + "valueKind": "string" + }, + { + "name": "machine-id", + "optionKey": "machineId", + "paramKey": "machine_id", + "location": "query", + "required": false, + "description": "Optional machine ID filter.", + "valueKind": "string" + } + ] + } +] as const satisfies readonly CliCommandDefinition[] + +export const getProgram = (): Command => + createProgram({ + SDK, + binaryName: "dedalus", + version: "0.1.4", + description: "CLI for Dedalus", + defaultFormat: "auto", + defaultErrorFormat: "auto", + clientOptions, + commands, + }) diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..24e82a1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { getProgram } from './commands/index' + +export { getProgram } + +export const run = async (argv: readonly string[] = process.argv): Promise => { + await getProgram().parseAsync(argv) +} diff --git a/src/sdk/api-promise.ts b/src/sdk/api-promise.ts new file mode 100644 index 0000000..11abf56 --- /dev/null +++ b/src/sdk/api-promise.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** @deprecated Import from ./core/api-promise instead */ +export * from './core/api-promise'; diff --git a/src/sdk/client.ts b/src/sdk/client.ts new file mode 100644 index 0000000..895d4f1 --- /dev/null +++ b/src/sdk/client.ts @@ -0,0 +1,1005 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { APIPromise } from './api-promise'; +import type { APIResponseProps } from './internal/parse'; +import * as Errors from './error'; +import { uuid4 } from './internal/utils/uuid'; +import { validatePositiveInteger, isAbsoluteURL, safeJSON, isEmptyObj } from './internal/utils/values'; +import { sleep } from './internal/utils/sleep'; +import { castToError, isAbortError } from './internal/errors'; +import { getPlatformHeaders } from './internal/detect-platform'; +import * as Shims from './internal/shims'; +import * as Opts from './internal/request-options'; +import { readEnv } from './internal/utils/env'; +import { formatRequestDetails, loggerFor, parseLogLevel, type LogLevel, type Logger } from './internal/utils/log'; +export type { Logger, LogLevel } from './internal/utils/log'; +import type { RequestInit, RequestInfo, BodyInit, Fetch } from './internal/builtin-types'; +import { buildHeaders, type HeadersLike } from './internal/headers'; +import type { FinalRequestOptions, RequestOptions } from './internal/request-options'; +import type { HTTPMethod, FinalizedRequestInit, MergedRequestInit, PromiseOrValue } from './internal/types'; +import { stringify as stringifyQuery } from './internal/qs/stringify'; +import type { StringifyOptions } from './internal/qs/types'; +import { toFile } from './core/uploads'; +import { VERSION } from './version'; +import { MachineLifecycle, type CreateMachineRequest, type UpdateMachineRequest, type CreateExecutionRequest, type CreatePreviewRequest, type CreateSSHSessionRequest, type CreateTerminalRequest, type MachineLifecycleListResponse, type MachineLifecycleCreateResponse, type MachineLifecycleDeleteResponse, type MachineLifecycleRetrieveResponse, type MachineLifecyclePatchResponse, type MachineLifecycleListArtifactsResponse, type MachineLifecycleDeleteArtifactResponse, type MachineLifecycleRetrieveArtifactResponse, type MachineLifecycleListExecutionsResponse, type MachineLifecycleCreateExecutionResponse, type MachineLifecycleDeleteExecutionResponse, type MachineLifecycleRetrieveExecutionResponse, type MachineLifecycleListExecutionEventsResponse, type MachineLifecycleListExecutionOutputResponse, type MachineLifecycleListPreviewsResponse, type MachineLifecycleCreatePreviewResponse, type MachineLifecycleDeletePreviewResponse, type MachineLifecycleRetrievePreviewResponse, type MachineLifecycleSleepResponse, type MachineLifecycleListSSHSessionsResponse, type MachineLifecycleCreateSSHSessionResponse, type MachineLifecycleDeleteSSHSessionResponse, type MachineLifecycleRetrieveSSHSessionResponse, type MachineLifecycleWatchStatusResponse, type MachineLifecycleListTerminalsResponse, type MachineLifecycleCreateTerminalResponse, type MachineLifecycleDeleteTerminalResponse, type MachineLifecycleRetrieveTerminalResponse, type MachineLifecycleWakeResponse, type MachineLifecycleListParams, type MachineLifecycleCreateParams, type MachineLifecycleDeleteParams, type MachineLifecycleRetrieveParams, type MachineLifecyclePatchParams, type MachineLifecycleListArtifactsParams, type MachineLifecycleDeleteArtifactParams, type MachineLifecycleRetrieveArtifactParams, type MachineLifecycleListExecutionsParams, type MachineLifecycleCreateExecutionParams, type MachineLifecycleDeleteExecutionParams, type MachineLifecycleRetrieveExecutionParams, type MachineLifecycleListExecutionEventsParams, type MachineLifecycleListExecutionOutputParams, type MachineLifecycleListPreviewsParams, type MachineLifecycleCreatePreviewParams, type MachineLifecycleDeletePreviewParams, type MachineLifecycleRetrievePreviewParams, type MachineLifecycleSleepParams, type MachineLifecycleListSSHSessionsParams, type MachineLifecycleCreateSSHSessionParams, type MachineLifecycleDeleteSSHSessionParams, type MachineLifecycleRetrieveSSHSessionParams, type MachineLifecycleWatchStatusParams, type MachineLifecycleListTerminalsParams, type MachineLifecycleCreateTerminalParams, type MachineLifecycleDeleteTerminalParams, type MachineLifecycleRetrieveTerminalParams, type MachineLifecycleConnectTerminalParams, type MachineLifecycleWakeParams } from "./resources/machine-lifecycle/machine-lifecycle"; +import { Usage, type UsageListResponse, type UsageListParams } from "./resources/usage/usage"; + +export type AuthTokenProvider = () => string | Promise; + +const queryArrayFormat: NonNullable = "comma"; +const queryAllowDots = false; + +const environments = { + production: "https://api.dedaluslabs.ai", + official_dcs_api: "https://dcs.dedaluslabs.ai", +}; +type Environment = keyof typeof environments; + +export interface ClientOptions { + /** + * API key authentication using X-API-Key header + */ + apiKeyAuth?: string | AuthTokenProvider | undefined; + + /** + * Dedalus API key in Authorization: Bearer . + */ + bearerAuth?: string | AuthTokenProvider | undefined; + + /** + * API key authentication using Bearer token + */ + bearer?: string | AuthTokenProvider | undefined; + + /** + * Provider name for BYOK mode. + */ + provider?: string | null | undefined; + + /** + * Provider API key for BYOK mode. + */ + providerKey?: string | null | undefined; + + /** + * Model identifier for BYOK provider. + */ + providerModel?: string | null | undefined; + + /** + * MCP Authorization Server URL. + */ + asBaseURL?: string | null | undefined; + + /** + * Organization ID for request scoping. + */ + dedalusOrgID?: string | null | undefined; + + /** + * Specifies the environment to use for the API. + * + * Each environment maps to a different base URL: + * - `production` corresponds to `https://api.dedaluslabs.ai` + * - `official_dcs_api` corresponds to `https://dcs.dedaluslabs.ai` + */ + environment?: Environment | undefined; + + /** + * Override the default base URL for the API, e.g., "https://api.example.com/v2/" + * + * Defaults to process.env["DEDALUS_BASE_URL"]. + */ + baseURL?: string | null | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * Note that request timeouts are retried by default, so in a worst-case scenario you may wait + * much longer than this timeout before the promise succeeds or fails. + * + * @unit milliseconds + */ + timeout?: number | undefined; + + /** + * Additional `RequestInit` options to be passed to `fetch` calls. + * Properties will be overridden by per-request `fetchOptions`. + */ + fetchOptions?: MergedRequestInit | undefined; + + /** + * Specify a custom `fetch` function implementation. + * + * If not provided, we expect that `fetch` is defined globally. + */ + fetch?: Fetch | undefined; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number | undefined; + + /** + * Default headers to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * header to `null` in request options. + */ + defaultHeaders?: HeadersLike | undefined; + + /** + * Default query parameters to include with every request to the API. + * + * These can be removed in individual requests by explicitly setting the + * param to `undefined` in request options. + */ + defaultQuery?: Record | undefined; + + /** + * Set the log level. + * + * Defaults to process.env["DEDALUS_LOG"] or 'warn' if it isn't set. + */ + logLevel?: LogLevel | undefined; + + /** + * Set the logger. + * + * Defaults to globalThis.console. + */ + logger?: Logger | undefined; +} + +export type DedalusOptions = ClientOptions; + +/** + * API Client for interfacing with the Dedalus API. + */ +export class Dedalus { + apiKeyAuth: string | AuthTokenProvider | undefined; + bearerAuth: string | AuthTokenProvider | undefined; + bearer: string | AuthTokenProvider | undefined; + provider: string | null; + providerKey: string | null; + providerModel: string | null; + asBaseURL: string | null; + dedalusOrgID: string | null; + + baseURL: string; + maxRetries: number; + timeout: number; + logger: Logger; + logLevel: LogLevel | undefined; + fetchOptions: MergedRequestInit | undefined; + private fetch: Fetch; + #encoder: Opts.RequestEncoder; + protected idempotencyHeader?: string; + private _baseURLOverridden: boolean; + private _defaultBaseURL: string; + private _options: ClientOptions; + + /** + * API Client for interfacing with the Dedalus API. + * + * @param {string | AuthTokenProvider | undefined} [opts.apiKeyAuth=process.env["API_KEY_AUTH"] ?? undefined] + * @param {string | AuthTokenProvider | undefined} [opts.bearerAuth=process.env["BEARER_AUTH"] ?? undefined] + * @param {string | AuthTokenProvider | undefined} [opts.bearer=process.env["BEARER"] ?? undefined] + * @param {string | null | undefined} [opts.provider=process.env["DEDALUS_PROVIDER"] ?? null] + * @param {string | null | undefined} [opts.providerKey=process.env["DEDALUS_PROVIDER_KEY"] ?? null] + * @param {string | null | undefined} [opts.providerModel=process.env["DEDALUS_PROVIDER_MODEL"] ?? null] + * @param {string | null | undefined} [opts.asBaseURL=process.env["DEDALUS_AS_URL"] ?? null] + * @param {string | null | undefined} [opts.dedalusOrgID=process.env["DEDALUS_ORG_ID"] ?? null] + * @param {Environment} [opts.environment=production] - Specifies the environment URL to use for the API. + * @param {string} [opts.baseURL=process.env["DEDALUS_BASE_URL"] ?? https://api.dedaluslabs.ai] - Override the default base URL for the API. + * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. + * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. + * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. + * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. + * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. + */ + constructor({ + baseURL = readEnv("DEDALUS_BASE_URL"), + apiKeyAuth = readEnv("API_KEY_AUTH"), + bearerAuth = readEnv("BEARER_AUTH"), + bearer = readEnv("BEARER"), + provider = readEnv("DEDALUS_PROVIDER") ?? null, + providerKey = readEnv("DEDALUS_PROVIDER_KEY") ?? null, + providerModel = readEnv("DEDALUS_PROVIDER_MODEL") ?? null, + asBaseURL = readEnv("DEDALUS_AS_URL") ?? null, + dedalusOrgID = readEnv("DEDALUS_ORG_ID") ?? null, + ...opts + }: ClientOptions = {}) { + const options: ClientOptions = { + apiKeyAuth, + bearerAuth, + bearer, + provider, + providerKey, + providerModel, + asBaseURL, + dedalusOrgID, + ...opts, + baseURL: baseURL || null, + }; + const environment = options.environment ?? "production"; + const baseURLOverridden = baseURL !== null && baseURL !== undefined && baseURL !== ""; + if (baseURLOverridden && options.environment) throw new Errors.DedalusError("Ambiguous URL; The `baseURL` option (or DEDALUS_BASE_URL env var) and the `environment` option are given. If you want to use the environment you must pass baseURL: null"); + const defaultBaseURL = environments[environment]; + this.baseURL = options.baseURL || defaultBaseURL; + this.timeout = options.timeout ?? Dedalus.DEFAULT_TIMEOUT /* 1 minute */; + this.logger = options.logger ?? console; + const defaultLogLevel = 'warn'; + // Set default logLevel early so that we can log a warning in parseLogLevel. + this.logLevel = defaultLogLevel; + this.logLevel = + parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? + parseLogLevel(readEnv("DEDALUS_LOG"), "process.env[\"DEDALUS_LOG\"]", this) ?? + defaultLogLevel; + this.fetchOptions = options.fetchOptions; + this.maxRetries = options.maxRetries ?? 2; + this.fetch = options.fetch ?? Shims.getDefaultFetch(); + this.#encoder = Opts.FallbackEncoder; + + const customHeadersEnv = readEnv("DEDALUS_CUSTOM_HEADERS"); + if (customHeadersEnv) { + const parsed: Record = {}; + for (const line of customHeadersEnv.split('\n')) { + const colon = line.indexOf(':'); + if (colon >= 0) { + parsed[line.substring(0, colon).trim()] = line.substring(colon + 1).trim(); + } + } + options.defaultHeaders = { ...parsed, ...options.defaultHeaders }; + } + + this._options = { ...options, baseURL: baseURLOverridden ? this.baseURL : undefined, environment }; + this._baseURLOverridden = baseURLOverridden; + this._defaultBaseURL = defaultBaseURL; + this.idempotencyHeader = "Idempotency-Key"; + + this.apiKeyAuth = apiKeyAuth; + this.bearerAuth = bearerAuth; + this.bearer = bearer; + this.provider = provider; + this.providerKey = providerKey; + this.providerModel = providerModel; + this.asBaseURL = asBaseURL; + this.dedalusOrgID = dedalusOrgID; + } + + withOptions(options: Partial): this { + const client = new (this.constructor as new (props: ClientOptions) => this)({ + ...this._options, + ...(this.#baseURLOverridden() ? { baseURL: this.baseURL } : {}), + maxRetries: this.maxRetries, + timeout: this.timeout, + logger: this.logger, + logLevel: this.logLevel, + fetch: this.fetch, + fetchOptions: this.fetchOptions, + apiKeyAuth: this.apiKeyAuth, + bearerAuth: this.bearerAuth, + bearer: this.bearer, + provider: this.provider, + providerKey: this.providerKey, + providerModel: this.providerModel, + asBaseURL: this.asBaseURL, + dedalusOrgID: this.dedalusOrgID, + ...options, + }); + return client; + } + + #baseURLOverridden(): boolean { + // A named environment selects a default URL; only explicit overrides should bypass per-request defaults. + return this._baseURLOverridden || this.baseURL !== this._defaultBaseURL; + } + + protected defaultQuery(): Record | undefined { + return this._options.defaultQuery; + } + + protected stringifyQuery(query: object | Record): string { + return stringifyQuery(query, { arrayFormat: queryArrayFormat, allowDots: queryAllowDots }); + } + + private getUserAgent(): string { + return `${this.constructor.name}/JS ${VERSION}`; + } + + protected defaultIdempotencyKey(): string { + return `scalar-node-retry-${uuid4()}`; + } + + protected makeStatusError( + status: number, + error: object | undefined, + message: string | undefined, + headers: Headers, + ): Errors.APIError { + return Errors.APIError.generate(status, error, message, headers); + } + + buildURL( + path: string, + query: Record | null | undefined, + defaultBaseURL?: string | undefined, + ): string { + const baseURL = (!this.#baseURLOverridden() && defaultBaseURL) || this.baseURL; + // Guarantee exactly one "/" between baseURL and path so that bases without a trailing slash + // and paths without a leading slash do not fuse into a malformed URL (e.g. ".../v1" + "widgets"). + const url = + isAbsoluteURL(path) ? + new URL(path) + : new URL((baseURL.endsWith('/') ? baseURL : baseURL + '/') + (path.startsWith('/') ? path.slice(1) : path)); + + const defaultQuery = this.defaultQuery(); + const pathQuery = Object.fromEntries(url.searchParams); + if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) { + query = { ...pathQuery, ...defaultQuery, ...query }; + } + + if (typeof query === "object" && query && !Array.isArray(query)) { + url.search = this.stringifyQuery(query); + } + + return url.toString(); + } + + /** + * Used as a callback for mutating the given `FinalRequestOptions` object. + */ + protected async prepareOptions(options: FinalRequestOptions): Promise {} + + /** + * Used as a callback for mutating the given `RequestInit` object. + * + * This is useful for cases where you want to add certain headers based off of + * the request properties, e.g. `method` or `url`. + */ + protected async prepareRequest( + request: RequestInit, + { url, options }: { url: string; options: FinalRequestOptions }, + ): Promise {} + + get(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('get', path, opts); + } + + post(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('post', path, opts); + } + + patch(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('patch', path, opts); + } + + put(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('put', path, opts); + } + + delete(path: string, opts?: PromiseOrValue): APIPromise { + return this.methodRequest('delete', path, opts); + } + + private methodRequest( + method: HTTPMethod, + path: string, + opts?: PromiseOrValue, + ): APIPromise { + return this.request( + Promise.resolve(opts).then((opts) => { + return { method, path, ...opts } as FinalRequestOptions; + }), + ); + } + + request( + options: PromiseOrValue, + remainingRetries: number | null = null, + ): APIPromise { + return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined)); + } + + private async makeRequest( + optionsInput: PromiseOrValue, + retriesRemaining: number | null, + retryOfRequestLogID: string | undefined, + ): Promise { + const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; + if (retriesRemaining == null) { + retriesRemaining = maxRetries; + } + + await this.prepareOptions(options); + + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); + + await this.prepareRequest(req, { url, options }); + + /** Not an API request ID, just for correlating local log entries. */ + const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0'); + const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`; + const startTime = Date.now(); + + loggerFor(this).debug( + `[${requestLogID}] sending request`, + formatRequestDetails({ + retryOfRequestLogID, + method: options.method, + url, + options, + headers: req.headers, + }), + ); + + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + + const controller = new AbortController(); + const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError); + const headersTime = Date.now(); + + if (response instanceof globalThis.Error) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + if (options.signal?.aborted) { + throw new Errors.APIUserAbortError(); + } + // detect native connection timeout errors + // deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)" + // undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)" + // others do not provide enough information to distinguish timeouts from other connection errors + const isTimeout = + isAbortError(response) || + /timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : '')); + if (retriesRemaining) { + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID); + } + loggerFor(this).info( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`, + ); + loggerFor(this).debug( + `[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, + formatRequestDetails({ + retryOfRequestLogID, + url, + durationMs: headersTime - startTime, + message: response.message, + }), + ); + if (isTimeout) { + throw new Errors.APIConnectionTimeoutError(); + } + throw new Errors.APIConnectionError({ cause: response }); + } + + const responseInfo = `[${requestLogID}${retryLogStr}] ${req.method} ${url} ${ + response.ok ? 'succeeded' : 'failed' + } with status ${response.status} in ${headersTime - startTime}ms`; + + if (!response.ok) { + const shouldRetry = await this.shouldRetry(response); + if (retriesRemaining && shouldRetry) { + const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; + + // We don't need the body of this response. + await Shims.CancelReadableStream(response.body); + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + return this.retryRequest( + options, + retriesRemaining, + retryOfRequestLogID ?? requestLogID, + response.headers, + ); + } + + const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`; + + loggerFor(this).info(`${responseInfo} - ${retryMessage}`); + + const errText = await response.text().catch((err: any) => castToError(err).message); + const errJSON = safeJSON(errText) as any; + const errMessage = errJSON ? undefined : errText; + + loggerFor(this).debug( + `[${requestLogID}] response error (${retryMessage})`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + message: errMessage, + durationMs: Date.now() - startTime, + }), + ); + + const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers); + throw err; + } + + loggerFor(this).info(responseInfo); + loggerFor(this).debug( + `[${requestLogID}] response start`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + headers: response.headers, + durationMs: headersTime - startTime, + }), + ); + + return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; + } + + async fetchWithTimeout(url: RequestInfo, init: RequestInit | undefined, ms: number, controller: AbortController): Promise { + const { signal, method, ...options } = init || {}; + const abort = this._makeAbort(controller); + if (signal) signal.addEventListener('abort', abort, { once: true }); + + const timeout = setTimeout(abort, ms); + + const isReadableBody = + ((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) || + (typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body); + + const fetchOptions: RequestInit = { + signal: controller.signal as any, + ...(isReadableBody ? { duplex: 'half' } : {}), + method: 'GET', + ...options, + }; + if (method) { + // Custom methods like 'patch' need to be uppercased + // See https://github.com/nodejs/undici/issues/2294 + fetchOptions.method = method.toUpperCase(); + } + + try { + // use undefined this binding; fetch errors if bound to something else in browser/cloudflare + return await this.fetch.call(undefined, url, fetchOptions); + } finally { + clearTimeout(timeout); + } + } + + private async shouldRetry(response: Response): Promise { + // Note this is not a standard header. + const shouldRetryHeader = response.headers.get('x-should-retry'); + + // If the server explicitly says whether or not to retry, obey. + if (shouldRetryHeader === 'true') return true; + if (shouldRetryHeader === 'false') return false; + + // Retry on request timeouts. + if (response.status === 408) return true; + + // Retry on lock timeouts. + if (response.status === 409) return true; + + // Retry on rate limits. + if (response.status === 429) return true; + + // Retry internal errors. + if (response.status >= 500) return true; + + return false; + } + + private async retryRequest( + options: FinalRequestOptions, + retriesRemaining: number, + requestLogID: string, + responseHeaders?: Headers | undefined, + ): Promise { + let timeoutMillis: number | undefined; + + // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it. + const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms'); + if (retryAfterMillisHeader) { + const timeoutMs = parseFloat(retryAfterMillisHeader); + if (!Number.isNaN(timeoutMs)) { + timeoutMillis = timeoutMs; + } + } + + // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + const retryAfterHeader = responseHeaders?.get('retry-after'); + if (retryAfterHeader && !timeoutMillis) { + const timeoutSeconds = parseFloat(retryAfterHeader); + if (!Number.isNaN(timeoutSeconds)) { + timeoutMillis = timeoutSeconds * 1000; + } else { + timeoutMillis = Date.parse(retryAfterHeader) - Date.now(); + } + } + + // If the API asks us to wait a certain amount of time, just do what it says, + // but cap server-provided delays at 60s so an oversized or malformed Retry-After + // (e.g. `retry-after-ms: 999999999`, a past HTTP-date, or a value that Date.parse + // failed on) cannot block retries for an unbounded amount of time. Otherwise fall + // back to the default exponential-backoff calculation. + const maxRetryAfterMillis = 60 * 1000; + if ( + timeoutMillis === undefined || + !Number.isFinite(timeoutMillis) || + timeoutMillis <= 0 || + timeoutMillis > maxRetryAfterMillis + ) { + const maxRetries = options.maxRetries ?? this.maxRetries; + timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); + } + await sleep(timeoutMillis); + + return this.makeRequest(options, retriesRemaining - 1, requestLogID); + } + + private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { + const initialRetryDelay = 0.5; + const maxRetryDelay = 8.0; + + const numRetries = maxRetries - retriesRemaining; + + // Apply exponential backoff, but not more than the max. + const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay); + + // Apply some jitter, take up to at most 25 percent of the retry time. + const jitter = 1 - Math.random() * 0.25; + + return sleepSeconds * jitter * 1000; + } + + async buildRequest( + inputOptions: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { + const options = { ...inputOptions }; + const { method, path, query, defaultBaseURL } = options; + + const url = this.buildURL(path!, query as Record, defaultBaseURL); + if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); + options.timeout = options.timeout ?? this.timeout; + const { bodyHeaders, body } = this.buildBody({ options }); + const reqHeaders = await this.buildHeaders({ options, method, bodyHeaders, retryCount, url }); + + const req: FinalizedRequestInit = { + method, + headers: reqHeaders, + ...(options.signal && { signal: options.signal }), + ...((globalThis as any).ReadableStream && + body instanceof (globalThis as any).ReadableStream && { duplex: 'half' }), + // `buildBody` already collapses no-body into `undefined`; here we only need to drop that + // sentinel. A truthiness spread would also strip an intentional empty-string body. + ...(body !== undefined && { body }), + ...((this.fetchOptions as any) ?? {}), + ...((options.fetchOptions as any) ?? {}), + }; + return { req, url, timeout: options.timeout }; + } + + private async buildHeaders({ + options, + method, + bodyHeaders, + retryCount, + url, + }: { + options: FinalRequestOptions; + method: HTTPMethod; + bodyHeaders: HeadersLike; + retryCount: number; + url: string; + }): Promise { + let idempotencyHeaders: HeadersLike = {}; + if (this.idempotencyHeader && method !== 'get') { + if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); + idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey; + } + + const headers = buildHeaders([ + idempotencyHeaders, + { + Accept: 'application/json', + 'User-Agent': this.getUserAgent(), + 'X-Scalar-Retry-Count': String(retryCount), + ...(options.timeout ? { 'X-Scalar-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), + ...getPlatformHeaders(), + ...{ "X-SDK-Version": "1.0.0" }, + 'X-Provider': this.provider, + 'X-Provider-Key': this.providerKey, + 'X-Provider-Model': this.providerModel, + }, + await this.authHeaders(options), + this._options.defaultHeaders, + bodyHeaders, + options.headers, + ]); + appendAuthCookies(headers.values, await this.authCookiesAsync()); + + this.validateAuth(url, headers.values, options); + + return headers.values; + } + + private _makeAbort(controller: AbortController) { + // note: we can't just inline this method inside `fetchWithTimeout()` because then the closure + // would capture all request options, and cause a memory leak. + return () => controller.abort(); + } + + private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + bodyHeaders: HeadersLike; + body: BodyInit | undefined; + } { + // Skip only `null`/`undefined` so an intentional empty-string (or 0/false) payload still + // reaches the encoder. A plain `!body` check would silently drop those falsy-but-valid bodies, + // and `null` must be excluded here too because the iterator check below uses `in`, which + // throws on null. + if (body == null) { + return { bodyHeaders: undefined, body: undefined }; + } + const headers = buildHeaders([rawHeaders]); + if ( + // Pass raw type verbatim + ArrayBuffer.isView(body) || + body instanceof ArrayBuffer || + body instanceof DataView || + // Always pass strings through verbatim. The previous guard required a caller-set + // `content-type` and otherwise fell through to `FallbackEncoder`, which JSON.stringifies + // the value and labels it `application/json` — silently quoting plain-text payloads and + // mislabeling them as JSON. fetch defaults a string body to `text/plain;charset=UTF-8` + // when no `content-type` is set, which is a safer default than misclaiming JSON. + typeof body === 'string' || + // `Blob` is superset of `File` + ((globalThis as any).Blob && body instanceof (globalThis as any).Blob) || + // `FormData` -> `multipart/form-data` + body instanceof FormData || + // `URLSearchParams` -> `application/x-www-form-urlencoded` + body instanceof URLSearchParams || + // Send chunked stream (each chunk has own `length`) + ((globalThis as any).ReadableStream && body instanceof (globalThis as any).ReadableStream) + ) { + return { bodyHeaders: undefined, body: body as BodyInit }; + } else if ( + typeof body === 'object' && + (Symbol.asyncIterator in body || + (Symbol.iterator in body && 'next' in body && typeof body.next === 'function')) + ) { + return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body as AsyncIterable) }; + } else if ( + typeof body === 'object' && + headers.values.get('content-type') === 'application/x-www-form-urlencoded' + ) { + return { + bodyHeaders: { 'content-type': 'application/x-www-form-urlencoded' }, + body: this.stringifyQuery(body), + }; + } else { + return this.#encoder({ body, headers }); + } + } + + private validateAuth(url: string, headers: Headers, options: FinalRequestOptions): void { + if (headers.has("x-api-key")) return; + if (headerExplicitlyOmitted(options.headers, "x-api-key")) return; + if (headers.has("Authorization")) return; + if (headerExplicitlyOmitted(options.headers, "Authorization")) return; + throw new Errors.AuthenticationError(401, {}, "Could not resolve authentication method. Expected x-api-key or Authorization to be set.", headers); + } + + authHeadersSync(): Record { + const headers: Record = {}; + const apiKeyAuth = this.resolveAuthOptionSync("apiKeyAuth", this.apiKeyAuth); + if (apiKeyAuth) headers["x-api-key"] = apiKeyAuth; + const bearerAuth = this.resolveAuthOptionSync("bearerAuth", this.bearerAuth); + if (bearerAuth) headers['Authorization'] = `Bearer ${bearerAuth}`; + const bearer = this.resolveAuthOptionSync("bearer", this.bearer); + if (bearer) headers['Authorization'] = `Bearer ${bearer}`; + return headers; + } + + webSocketAuthHeaders(): Record { + const bearerAuth = this.resolveAuthOptionSync("bearerAuth", this.bearerAuth); + if (bearerAuth) return { Authorization: `Bearer ${bearerAuth}` }; + const apiKeyAuth = this.resolveAuthOptionSync("apiKeyAuth", this.apiKeyAuth); + if (apiKeyAuth) return { "x-api-key": apiKeyAuth }; + return {}; + } + + protected async authHeaders(options: FinalRequestOptions): Promise { + return buildHeaders([await this.authHeadersAsync()]); + } + + private async authQueryAsync(): Promise> { + const query: Record = {}; + return query; + } + + private async authCookiesAsync(): Promise> { + const cookies: Record = {}; + return cookies; + } + + private async authHeadersAsync(): Promise> { + const headers: Record = {}; + const apiKeyAuth = await this.resolveAuthOption("apiKeyAuth", this.apiKeyAuth); + if (apiKeyAuth) headers["x-api-key"] = apiKeyAuth; + const bearerAuth = await this.resolveAuthOption("bearerAuth", this.bearerAuth); + if (bearerAuth) headers['Authorization'] = `Bearer ${bearerAuth}`; + const bearer = await this.resolveAuthOption("bearer", this.bearer); + if (bearer) headers['Authorization'] = `Bearer ${bearer}`; + return headers; + } + + private async resolveAuthOption(optionName: string, value: string | AuthTokenProvider | null | undefined): Promise { + if (value == null) return undefined; + const token = typeof value === "function" ? await value() : value; + if (!token) throw new Errors.DedalusError(`Expected '${optionName}' to resolve to a non-empty string.`); + return token; + } + + private resolveAuthOptionSync(optionName: string, value: string | AuthTokenProvider | null | undefined): string | undefined { + if (value == null) return undefined; + const token = typeof value === "function" ? value() : value; + if (typeof token !== "string" || !token) throw new Errors.DedalusError(`Expected '${optionName}' to resolve to a non-empty string.`); + return token; + } + + static Dedalus = this; + static DEFAULT_TIMEOUT = 60000; // 1 minute + + static DedalusError = Errors.DedalusError; + static APIError = Errors.APIError; + static APIConnectionError = Errors.APIConnectionError; + static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; + static APIUserAbortError = Errors.APIUserAbortError; + static NotFoundError = Errors.NotFoundError; + static ConflictError = Errors.ConflictError; + static RateLimitError = Errors.RateLimitError; + static BadRequestError = Errors.BadRequestError; + static AuthenticationError = Errors.AuthenticationError; + static InternalServerError = Errors.InternalServerError; + static PermissionDeniedError = Errors.PermissionDeniedError; + static UnprocessableEntityError = Errors.UnprocessableEntityError; + + static toFile = toFile; + + machineLifecycle: MachineLifecycle = new MachineLifecycle(this); + usage: Usage = new Usage(this); +} + +Dedalus.MachineLifecycle = MachineLifecycle; +Dedalus.Usage = Usage; + +export declare namespace Dedalus { + export type RequestOptions = Opts.RequestOptions; + export { + MachineLifecycle as MachineLifecycle, + type CreateMachineRequest as CreateMachineRequest, + type UpdateMachineRequest as UpdateMachineRequest, + type CreateExecutionRequest as CreateExecutionRequest, + type CreatePreviewRequest as CreatePreviewRequest, + type CreateSSHSessionRequest as CreateSSHSessionRequest, + type CreateTerminalRequest as CreateTerminalRequest, + type MachineLifecycleListResponse as MachineLifecycleListResponse, + type MachineLifecycleCreateResponse as MachineLifecycleCreateResponse, + type MachineLifecycleDeleteResponse as MachineLifecycleDeleteResponse, + type MachineLifecycleRetrieveResponse as MachineLifecycleRetrieveResponse, + type MachineLifecyclePatchResponse as MachineLifecyclePatchResponse, + type MachineLifecycleListArtifactsResponse as MachineLifecycleListArtifactsResponse, + type MachineLifecycleDeleteArtifactResponse as MachineLifecycleDeleteArtifactResponse, + type MachineLifecycleRetrieveArtifactResponse as MachineLifecycleRetrieveArtifactResponse, + type MachineLifecycleListExecutionsResponse as MachineLifecycleListExecutionsResponse, + type MachineLifecycleCreateExecutionResponse as MachineLifecycleCreateExecutionResponse, + type MachineLifecycleDeleteExecutionResponse as MachineLifecycleDeleteExecutionResponse, + type MachineLifecycleRetrieveExecutionResponse as MachineLifecycleRetrieveExecutionResponse, + type MachineLifecycleListExecutionEventsResponse as MachineLifecycleListExecutionEventsResponse, + type MachineLifecycleListExecutionOutputResponse as MachineLifecycleListExecutionOutputResponse, + type MachineLifecycleListPreviewsResponse as MachineLifecycleListPreviewsResponse, + type MachineLifecycleCreatePreviewResponse as MachineLifecycleCreatePreviewResponse, + type MachineLifecycleDeletePreviewResponse as MachineLifecycleDeletePreviewResponse, + type MachineLifecycleRetrievePreviewResponse as MachineLifecycleRetrievePreviewResponse, + type MachineLifecycleSleepResponse as MachineLifecycleSleepResponse, + type MachineLifecycleListSSHSessionsResponse as MachineLifecycleListSSHSessionsResponse, + type MachineLifecycleCreateSSHSessionResponse as MachineLifecycleCreateSSHSessionResponse, + type MachineLifecycleDeleteSSHSessionResponse as MachineLifecycleDeleteSSHSessionResponse, + type MachineLifecycleRetrieveSSHSessionResponse as MachineLifecycleRetrieveSSHSessionResponse, + type MachineLifecycleWatchStatusResponse as MachineLifecycleWatchStatusResponse, + type MachineLifecycleListTerminalsResponse as MachineLifecycleListTerminalsResponse, + type MachineLifecycleCreateTerminalResponse as MachineLifecycleCreateTerminalResponse, + type MachineLifecycleDeleteTerminalResponse as MachineLifecycleDeleteTerminalResponse, + type MachineLifecycleRetrieveTerminalResponse as MachineLifecycleRetrieveTerminalResponse, + type MachineLifecycleWakeResponse as MachineLifecycleWakeResponse, + type MachineLifecycleListParams as MachineLifecycleListParams, + type MachineLifecycleCreateParams as MachineLifecycleCreateParams, + type MachineLifecycleDeleteParams as MachineLifecycleDeleteParams, + type MachineLifecycleRetrieveParams as MachineLifecycleRetrieveParams, + type MachineLifecyclePatchParams as MachineLifecyclePatchParams, + type MachineLifecycleListArtifactsParams as MachineLifecycleListArtifactsParams, + type MachineLifecycleDeleteArtifactParams as MachineLifecycleDeleteArtifactParams, + type MachineLifecycleRetrieveArtifactParams as MachineLifecycleRetrieveArtifactParams, + type MachineLifecycleListExecutionsParams as MachineLifecycleListExecutionsParams, + type MachineLifecycleCreateExecutionParams as MachineLifecycleCreateExecutionParams, + type MachineLifecycleDeleteExecutionParams as MachineLifecycleDeleteExecutionParams, + type MachineLifecycleRetrieveExecutionParams as MachineLifecycleRetrieveExecutionParams, + type MachineLifecycleListExecutionEventsParams as MachineLifecycleListExecutionEventsParams, + type MachineLifecycleListExecutionOutputParams as MachineLifecycleListExecutionOutputParams, + type MachineLifecycleListPreviewsParams as MachineLifecycleListPreviewsParams, + type MachineLifecycleCreatePreviewParams as MachineLifecycleCreatePreviewParams, + type MachineLifecycleDeletePreviewParams as MachineLifecycleDeletePreviewParams, + type MachineLifecycleRetrievePreviewParams as MachineLifecycleRetrievePreviewParams, + type MachineLifecycleSleepParams as MachineLifecycleSleepParams, + type MachineLifecycleListSSHSessionsParams as MachineLifecycleListSSHSessionsParams, + type MachineLifecycleCreateSSHSessionParams as MachineLifecycleCreateSSHSessionParams, + type MachineLifecycleDeleteSSHSessionParams as MachineLifecycleDeleteSSHSessionParams, + type MachineLifecycleRetrieveSSHSessionParams as MachineLifecycleRetrieveSSHSessionParams, + type MachineLifecycleWatchStatusParams as MachineLifecycleWatchStatusParams, + type MachineLifecycleListTerminalsParams as MachineLifecycleListTerminalsParams, + type MachineLifecycleCreateTerminalParams as MachineLifecycleCreateTerminalParams, + type MachineLifecycleDeleteTerminalParams as MachineLifecycleDeleteTerminalParams, + type MachineLifecycleRetrieveTerminalParams as MachineLifecycleRetrieveTerminalParams, + type MachineLifecycleConnectTerminalParams as MachineLifecycleConnectTerminalParams, + type MachineLifecycleWakeParams as MachineLifecycleWakeParams, + }; + + export { + Usage as Usage, + type UsageListResponse as UsageListResponse, + type UsageListParams as UsageListParams, + }; +} + + +const headerExplicitlyOmitted = (source: HeadersLike | undefined, name: string): boolean => { + if (!source || Array.isArray(source) || source instanceof Headers) return false; + const target = name.toLowerCase(); + return Object.entries(source).some(([key, value]) => key.toLowerCase() === target && value === null); +}; + +const appendAuthCookies = (headers: Headers, cookies: Record): void => { + for (const [name, value] of Object.entries(cookies)) { + if (cookieHeaderHas(headers.get("Cookie"), name)) continue; + const cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value); + const existing = headers.get("Cookie"); + headers.set("Cookie", existing ? existing + "; " + cookie : cookie); + } +}; + +const cookieHeaderHas = (value: string | null, name: string): boolean => { + if (!value) return false; + const target = encodeURIComponent(name) + "="; + return value.split(";").some((cookie) => cookie.trim().startsWith(target)); +}; + diff --git a/src/sdk/core/EventEmitter.ts b/src/sdk/core/EventEmitter.ts new file mode 100644 index 0000000..24c7fd6 --- /dev/null +++ b/src/sdk/core/EventEmitter.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +type EventListener = Events[EventType]; + +type EventListeners = Array<{ listener: EventListener; once?: boolean; }>; + +export type EventParameters = { + [Event in EventType]: EventListener extends (...args: infer P) => unknown ? P : never; +}[EventType]; + +export class EventEmitter unknown>> { + #listeners: { [Event in keyof EventTypes]?: EventListeners; } = {}; + + on(event: Event, listener: EventListener): this { + const listeners: EventListeners = this.#listeners[event] || (this.#listeners[event] = []); + listeners.push({ listener }); + return this; + } + + off(event: Event, listener: EventListener): this { + const listeners = this.#listeners[event]; + if (!listeners) return this; + const index = listeners.findIndex((item) => item.listener === listener); + if (index >= 0) listeners.splice(index, 1); + return this; + } + + once(event: Event, listener: EventListener): this { + const listeners: EventListeners = this.#listeners[event] || (this.#listeners[event] = []); + listeners.push({ listener, once: true }); + return this; + } + + protected _emit(event: Event, ...args: EventParameters): void { + const listeners = this.#listeners[event]; + if (!listeners) return; + this.#listeners[event] = listeners.filter((listener) => !listener.once) as EventListeners; + for (const { listener } of listeners) (listener as (...args: EventParameters) => unknown)(...args); + } + + protected _hasListener(event: keyof EventTypes): boolean { + return (this.#listeners[event]?.length ?? 0) > 0; + } +} + +export class InternalEventEmitter unknown>> extends EventEmitter { + override _emit(event: Event, ...args: EventParameters): void { + super._emit(event, ...args); + } +} diff --git a/src/sdk/core/api-promise.ts b/src/sdk/core/api-promise.ts new file mode 100644 index 0000000..9c22b4c --- /dev/null +++ b/src/sdk/core/api-promise.ts @@ -0,0 +1,92 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { type Dedalus } from '../client'; + +import { type PromiseOrValue } from '../internal/types'; +import { APIResponseProps, defaultParseResponse } from '../internal/parse'; + +/** + * A subclass of `Promise` providing additional helper methods + * for interacting with the SDK. + */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + #client: Dedalus; + + constructor( + client: Dedalus, + private responsePromise: Promise, + private parseResponse: ( + client: Dedalus, + props: APIResponseProps, + ) => PromiseOrValue = defaultParseResponse, + ) { + super((resolve) => { + // this is maybe a bit weird but this has to be a no-op to not implicitly + // parse the response body; instead .then, .catch, .finally are overridden + // to parse the response + resolve(null as any); + }); + this.#client = client; + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.#client, this.responsePromise, async (client, props) => + transform(await this.parseResponse(client, props), props), + ); + } + + /** + * Gets the raw `Response` instance instead of parsing the response + * data. + * + * If you want to parse the response body but still get the `Response` + * instance, you can use {@link withResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + asResponse(): Promise { + return this.responsePromise.then((p) => p.response); + } + + /** + * Gets the parsed response data and the raw `Response` instance. + * + * If you just want to get the raw `Response` instance without parsing it, + * you can use {@link asResponse()}. + * + * 👋 Getting the wrong TypeScript type for `Response`? + * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` + * to your `tsconfig.json`. + */ + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/sdk/core/error.ts b/src/sdk/core/error.ts new file mode 100644 index 0000000..4d6b170 --- /dev/null +++ b/src/sdk/core/error.ts @@ -0,0 +1,130 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { castToError } from '../internal/errors'; + +export class DedalusError extends Error {} + +export class APIError< + TStatus extends number | undefined = number | undefined, + THeaders extends Headers | undefined = Headers | undefined, + TError extends Object | undefined = Object | undefined, +> extends DedalusError { + /** HTTP status for the response that caused the error */ + readonly status: TStatus; + /** HTTP headers for the response that caused the error */ + readonly headers: THeaders; + /** JSON body of the response that caused the error */ + readonly error: TError; + + constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { + super(`${APIError.makeMessage(status, error, message)}`); + this.status = status; + this.headers = headers; + this.error = error; + } + + private static makeMessage(status: number | undefined, error: any, message: string | undefined) { + const msg = + error?.message ? + typeof error.message === 'string' ? + error.message + : JSON.stringify(error.message) + : error ? JSON.stringify(error) + : message; + + if (status && msg) { + return `${status} ${msg}`; + } + if (status) { + return `${status} status code (no body)`; + } + if (msg) { + return msg; + } + return '(no status code or body)'; + } + + static generate( + status: number | undefined, + errorResponse: Object | undefined, + message: string | undefined, + headers: Headers | undefined, + ): APIError { + if (!status || !headers) { + return new APIConnectionError({ message, cause: castToError(errorResponse) }); + } + + const error = errorResponse as Record; + + if (status === 400) { + return new BadRequestError(status, error, message, headers); + } + + if (status === 401) { + return new AuthenticationError(status, error, message, headers); + } + + if (status === 403) { + return new PermissionDeniedError(status, error, message, headers); + } + + if (status === 404) { + return new NotFoundError(status, error, message, headers); + } + + if (status === 409) { + return new ConflictError(status, error, message, headers); + } + + if (status === 422) { + return new UnprocessableEntityError(status, error, message, headers); + } + + if (status === 429) { + return new RateLimitError(status, error, message, headers); + } + + if (status >= 500) { + return new InternalServerError(status, error, message, headers); + } + + return new APIError(status, error, message, headers); + } +} + +export class APIUserAbortError extends APIError { + constructor({ message }: { message?: string } = {}) { + super(undefined, undefined, message || 'Request was aborted.', undefined); + } +} + +export class APIConnectionError extends APIError { + constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { + super(undefined, undefined, message || 'Connection error.', undefined); + // in some environments the 'cause' property is already declared + // @ts-ignore + if (cause) this.cause = cause; + } +} + +export class APIConnectionTimeoutError extends APIConnectionError { + constructor({ message }: { message?: string } = {}) { + super({ message: message ?? 'Request timed out.' }); + } +} + +export class BadRequestError extends APIError<400, Headers> {} + +export class AuthenticationError extends APIError<401, Headers> {} + +export class PermissionDeniedError extends APIError<403, Headers> {} + +export class NotFoundError extends APIError<404, Headers> {} + +export class ConflictError extends APIError<409, Headers> {} + +export class UnprocessableEntityError extends APIError<422, Headers> {} + +export class RateLimitError extends APIError<429, Headers> {} + +export class InternalServerError extends APIError {} diff --git a/src/sdk/core/streaming.ts b/src/sdk/core/streaming.ts new file mode 100644 index 0000000..6b8893e --- /dev/null +++ b/src/sdk/core/streaming.ts @@ -0,0 +1,333 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { DedalusError } from './error'; +import { type ReadableStream } from '../internal/shim-types'; +import { makeReadableStream } from '../internal/shims'; +import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line'; +import { ReadableStreamToAsyncIterable } from '../internal/shims'; +import { isAbortError } from '../internal/errors'; +import { safeJSON } from '../internal/utils/values'; +import { encodeUTF8 } from '../internal/utils/bytes'; +import { loggerFor } from '../internal/utils/log'; +import type { Dedalus } from '../client'; + +import { APIError } from './error'; + +type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; + +export type ServerSentEvent = { + event: string | null; + data: string; + raw: string[]; +}; + +export class Stream implements AsyncIterable { + controller: AbortController; + #client: Dedalus | undefined; + + constructor( + private iterator: () => AsyncIterator, + controller: AbortController, + client?: Dedalus, + ) { + this.controller = controller; + this.#client = client; + } + + static fromSSEResponse( + response: Response, + controller: AbortController, + client?: Dedalus, + ): Stream { + let consumed = false; + const logger = client ? loggerFor(client) : console; + + async function* iterator(): AsyncIterator { + if (consumed) { + throw new DedalusError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); + } + consumed = true; + let done = false; + try { + for await (const sse of _iterSSEMessages(response, controller)) { + if (done) continue; + + if (sse.data.startsWith('[DONE]')) { + done = true; + continue; + } + + if (sse.event === 'error') { + throw new APIError(undefined, safeJSON(sse.data) ?? sse.data, undefined, response.headers); + } + + if (sse.event === null) { + try { + yield JSON.parse(sse.data); + } catch (e) { + logger.error(`Could not parse message into JSON:`, sse.data); + logger.error(`From chunk:`, sse.raw); + throw e; + } + } + } + done = true; + } catch (e) { + // If the user calls `stream.controller.abort()`, we should exit without throwing. + if (isAbortError(e)) return; + throw e; + } finally { + // If the user `break`s, abort the ongoing request. + if (!done) controller.abort(); + } + } + + return new Stream(iterator, controller, client); + } + + /** + * Generates a Stream from a newline-separated ReadableStream + * where each item is a JSON value. + */ + static fromReadableStream( + readableStream: ReadableStream, + controller: AbortController, + client?: Dedalus, + ): Stream { + let consumed = false; + + async function* iterLines(): AsyncGenerator { + const lineDecoder = new LineDecoder(); + + const iter = ReadableStreamToAsyncIterable(readableStream); + for await (const chunk of iter) { + for (const line of lineDecoder.decode(chunk)) { + yield line; + } + } + + for (const line of lineDecoder.flush()) { + yield line; + } + } + + async function* iterator(): AsyncIterator { + if (consumed) { + throw new DedalusError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); + } + consumed = true; + let done = false; + try { + for await (const line of iterLines()) { + if (done) continue; + if (line) yield JSON.parse(line); + } + done = true; + } catch (e) { + // If the user calls `stream.controller.abort()`, we should exit without throwing. + if (isAbortError(e)) return; + throw e; + } finally { + // If the user `break`s, abort the ongoing request. + if (!done) controller.abort(); + } + } + + return new Stream(iterator, controller, client); + } + + [Symbol.asyncIterator](): AsyncIterator { + return this.iterator(); + } + + /** + * Splits the stream into two streams which can be + * independently read from at different speeds. + */ + tee(): [Stream, Stream] { + const left: Array>> = []; + const right: Array>> = []; + const iterator = this.iterator(); + + const teeIterator = (queue: Array>>): AsyncIterator => { + return { + next: () => { + if (queue.length === 0) { + const result = iterator.next(); + left.push(result); + right.push(result); + } + return queue.shift()!; + }, + }; + }; + + return [ + new Stream(() => teeIterator(left), this.controller, this.#client), + new Stream(() => teeIterator(right), this.controller, this.#client), + ]; + } + + /** + * Converts this stream to a newline-separated ReadableStream of + * JSON stringified values in the stream + * which can be turned back into a Stream with `Stream.fromReadableStream()`. + */ + toReadableStream(): ReadableStream { + const self = this; + let iter: AsyncIterator; + + return makeReadableStream({ + async start() { + iter = self[Symbol.asyncIterator](); + }, + async pull(ctrl: any) { + try { + const { value, done } = await iter.next(); + if (done) return ctrl.close(); + + const bytes = encodeUTF8(JSON.stringify(value) + '\n'); + + ctrl.enqueue(bytes); + } catch (err) { + ctrl.error(err); + } + }, + async cancel() { + await iter.return?.(); + }, + }); + } +} + +export async function* _iterSSEMessages( + response: Response, + controller: AbortController, +): AsyncGenerator { + if (!response.body) { + controller.abort(); + if ( + typeof (globalThis as any).navigator !== 'undefined' && + (globalThis as any).navigator.product === 'ReactNative' + ) { + throw new DedalusError( + `The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`, + ); + } + throw new DedalusError(`Attempted to iterate over a response with no body`); + } + + const sseDecoder = new SSEDecoder(); + const lineDecoder = new LineDecoder(); + + const iter = ReadableStreamToAsyncIterable(response.body); + for await (const sseChunk of iterSSEChunks(iter)) { + for (const line of lineDecoder.decode(sseChunk)) { + const sse = sseDecoder.decode(line); + if (sse) yield sse; + } + } + + for (const line of lineDecoder.flush()) { + const sse = sseDecoder.decode(line); + if (sse) yield sse; + } +} + +/** + * Given an async iterable iterator, iterates over it and yields full + * SSE chunks, i.e. yields when a double new-line is encountered. + */ +async function* iterSSEChunks(iterator: AsyncIterableIterator): AsyncGenerator { + let data = new Uint8Array(); + + for await (const chunk of iterator) { + if (chunk == null) { + continue; + } + + const binaryChunk = + chunk instanceof ArrayBuffer ? new Uint8Array(chunk) + : typeof chunk === 'string' ? encodeUTF8(chunk) + : chunk; + + let newData = new Uint8Array(data.length + binaryChunk.length); + newData.set(data); + newData.set(binaryChunk, data.length); + data = newData; + + let patternIndex; + while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) { + yield data.slice(0, patternIndex); + data = data.slice(patternIndex); + } + } + + if (data.length > 0) { + yield data; + } +} + +class SSEDecoder { + private data: string[]; + private event: string | null; + private chunks: string[]; + + constructor() { + this.event = null; + this.data = []; + this.chunks = []; + } + + decode(line: string) { + if (line.endsWith('\r')) { + line = line.substring(0, line.length - 1); + } + + if (!line) { + // empty line and we didn't previously encounter any messages + if (!this.event && !this.data.length) return null; + + const sse: ServerSentEvent = { + event: this.event, + data: this.data.join('\n'), + raw: this.chunks, + }; + + this.event = null; + this.data = []; + this.chunks = []; + + return sse; + } + + this.chunks.push(line); + + if (line.startsWith(':')) { + return null; + } + + let [fieldname, _, value] = partition(line, ':'); + + if (value.startsWith(' ')) { + value = value.substring(1); + } + + if (fieldname === 'event') { + this.event = value; + } else if (fieldname === 'data') { + this.data.push(value); + } + + return null; + } +} + +function partition(str: string, delimiter: string): [string, string, string] { + const index = str.indexOf(delimiter); + if (index !== -1) { + return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)]; + } + + return [str, '', '']; +} diff --git a/src/sdk/core/uploads.ts b/src/sdk/core/uploads.ts new file mode 100644 index 0000000..536cfe6 --- /dev/null +++ b/src/sdk/core/uploads.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { type Uploadable } from '../internal/uploads'; +export { toFile, type ToFileInput } from '../internal/to-file'; diff --git a/src/sdk/error.ts b/src/sdk/error.ts new file mode 100644 index 0000000..834f539 --- /dev/null +++ b/src/sdk/error.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** @deprecated Import from ./core/error instead */ +export * from './core/error'; diff --git a/src/sdk/index.ts b/src/sdk/index.ts new file mode 100644 index 0000000..46ba028 --- /dev/null +++ b/src/sdk/index.ts @@ -0,0 +1,23 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { Dedalus as default } from './client.js'; + +export { type Uploadable, toFile } from './core/uploads'; +export { APIPromise } from './api-promise'; +export { type RawWebSocketData, type ReconnectingEvent, type ReconnectingOverrides, type UnsentMessage } from './internal/ws'; +export { Dedalus, type ClientOptions, type DedalusOptions, type Logger, type LogLevel } from './client.js'; +export { + DedalusError, + APIError, + APIConnectionError, + APIConnectionTimeoutError, + APIUserAbortError, + NotFoundError, + ConflictError, + RateLimitError, + BadRequestError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, +} from './error'; diff --git a/src/sdk/internal/README.md b/src/sdk/internal/README.md new file mode 100644 index 0000000..3ef5a25 --- /dev/null +++ b/src/sdk/internal/README.md @@ -0,0 +1,3 @@ +# `internal` + +The modules in this directory are not importable outside this package and will change between releases. diff --git a/src/sdk/internal/builtin-types.ts b/src/sdk/internal/builtin-types.ts new file mode 100644 index 0000000..40d03c4 --- /dev/null +++ b/src/sdk/internal/builtin-types.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export type Fetch = (input: string | URL | Request, init?: RequestInit) => Promise; + +/** + * An alias to the builtin `RequestInit` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit + */ +type _RequestInit = RequestInit; + +/** + * An alias to the builtin `Response` type so we can + * easily alias it in import statements if there are name clashes. + * + * https://developer.mozilla.org/docs/Web/API/Response + */ +type _Response = Response; + +/** + * The type for the first argument to `fetch`. + * + * https://developer.mozilla.org/docs/Web/API/Window/fetch#resource + */ +type _RequestInfo = Request | URL | string; + +/** + * The type for constructing `RequestInit` Headers. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#setting_headers + */ +type _HeadersInit = RequestInit['headers']; + +/** + * The type for constructing `RequestInit` body. + * + * https://developer.mozilla.org/docs/Web/API/RequestInit#body + */ +type _BodyInit = RequestInit['body']; + +/** + * An alias to the builtin `Array` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Array = Array; + +/** + * An alias to the builtin `Record` type so we can + * easily alias it in import statements if there are name clashes. + */ +type _Record = Record; + +export type { + _Array as Array, + _BodyInit as BodyInit, + _HeadersInit as HeadersInit, + _Record as Record, + _RequestInfo as RequestInfo, + _RequestInit as RequestInit, + _Response as Response, +}; + +/** + * A copy of the builtin `EndingType` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L27941 + */ +type EndingType = 'native' | 'transparent'; + +/** + * A copy of the builtin `BlobPropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L154 + * https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#options + */ +export interface BlobPropertyBag { + endings?: EndingType; + type?: string; +} + +/** + * A copy of the builtin `FilePropertyBag` type as it isn't fully supported in certain + * environments and attempting to reference the global version will error. + * + * https://github.com/microsoft/TypeScript/blob/49ad1a3917a0ea57f5ff248159256e12bb1cb705/src/lib/dom.generated.d.ts#L503 + * https://developer.mozilla.org/en-US/docs/Web/API/File/File#options + */ +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} diff --git a/src/sdk/internal/decoders/line.ts b/src/sdk/internal/decoders/line.ts new file mode 100644 index 0000000..b3bfa97 --- /dev/null +++ b/src/sdk/internal/decoders/line.ts @@ -0,0 +1,135 @@ +import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes'; + +export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; + +/** + * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally + * reading lines from text. + * + * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258 + */ +export class LineDecoder { + // prettier-ignore + static NEWLINE_CHARS = new Set(['\n', '\r']); + static NEWLINE_REGEXP = /\r\n|[\n\r]/g; + + #buffer: Uint8Array; + #carriageReturnIndex: number | null; + + constructor() { + this.#buffer = new Uint8Array(); + this.#carriageReturnIndex = null; + } + + decode(chunk: Bytes): string[] { + if (chunk == null) { + return []; + } + + const binaryChunk = + chunk instanceof ArrayBuffer ? new Uint8Array(chunk) + : typeof chunk === 'string' ? encodeUTF8(chunk) + : chunk; + + this.#buffer = concatBytes([this.#buffer, binaryChunk]); + + const lines: string[] = []; + let patternIndex; + while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) { + if (patternIndex.carriage && this.#carriageReturnIndex == null) { + // skip until we either get a corresponding `\n`, a new `\r` or nothing + this.#carriageReturnIndex = patternIndex.index; + continue; + } + + // we got double \r or \rtext\n + if ( + this.#carriageReturnIndex != null && + (patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage) + ) { + lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1))); + this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex); + this.#carriageReturnIndex = null; + continue; + } + + const endIndex = + this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding; + + const line = decodeUTF8(this.#buffer.subarray(0, endIndex)); + lines.push(line); + + this.#buffer = this.#buffer.subarray(patternIndex.index); + this.#carriageReturnIndex = null; + } + + return lines; + } + + flush(): string[] { + if (!this.#buffer.length) { + return []; + } + return this.decode('\n'); + } +} + +/** + * This function searches the buffer for the end patterns, (\r or \n) + * and returns an object with the index preceding the matched newline and the + * index after the newline char. `null` is returned if no new line is found. + * + * ```ts + * findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 } + * ``` + */ +function findNewlineIndex( + buffer: Uint8Array, + startIndex: number | null, +): { preceding: number; index: number; carriage: boolean } | null { + const newline = 0x0a; // \n + const carriage = 0x0d; // \r + + for (let i = startIndex ?? 0; i < buffer.length; i++) { + if (buffer[i] === newline) { + return { preceding: i, index: i + 1, carriage: false }; + } + + if (buffer[i] === carriage) { + return { preceding: i, index: i + 1, carriage: true }; + } + } + + return null; +} + +export function findDoubleNewlineIndex(buffer: Uint8Array): number { + // This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n) + // and returns the index right after the first occurrence of any pattern, + // or -1 if none of the patterns are found. + const newline = 0x0a; // \n + const carriage = 0x0d; // \r + + for (let i = 0; i < buffer.length - 1; i++) { + if (buffer[i] === newline && buffer[i + 1] === newline) { + // \n\n + return i + 2; + } + if (buffer[i] === carriage && buffer[i + 1] === carriage) { + // \r\r + return i + 2; + } + if ( + buffer[i] === carriage && + buffer[i + 1] === newline && + i + 3 < buffer.length && + buffer[i + 2] === carriage && + buffer[i + 3] === newline + ) { + // \r\n\r\n + return i + 4; + } + } + + return -1; +} diff --git a/src/sdk/internal/detect-platform.ts b/src/sdk/internal/detect-platform.ts new file mode 100644 index 0000000..7fc9267 --- /dev/null +++ b/src/sdk/internal/detect-platform.ts @@ -0,0 +1,196 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { VERSION } from '../version'; + +export const isRunningInBrowser = () => { + return ( + // @ts-ignore + typeof window !== 'undefined' && + // @ts-ignore + typeof window.document !== 'undefined' && + // @ts-ignore + typeof navigator !== 'undefined' + ); +}; + +type DetectedPlatform = 'deno' | 'node' | 'edge' | 'unknown'; + +/** + * Note this does not detect 'browser'; for that, use getBrowserInfo(). + */ +function getDetectedPlatform(): DetectedPlatform { + if (typeof Deno !== 'undefined' && Deno.build != null) { + return 'deno'; + } + if (typeof EdgeRuntime !== 'undefined') { + return 'edge'; + } + if ( + Object.prototype.toString.call( + typeof (globalThis as any).process !== 'undefined' ? (globalThis as any).process : 0, + ) === '[object process]' + ) { + return 'node'; + } + return 'unknown'; +} + +declare const Deno: any; +declare const EdgeRuntime: any; +type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown'; +type PlatformName = + | 'MacOS' + | 'Linux' + | 'Windows' + | 'FreeBSD' + | 'OpenBSD' + | 'iOS' + | 'Android' + | `Other:${string}` + | 'Unknown'; +type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari'; +type PlatformProperties = { + 'X-Scalar-Lang': 'js'; + 'X-Scalar-Package-Version': string; + 'X-Scalar-OS': PlatformName; + 'X-Scalar-Arch': Arch; + 'X-Scalar-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown'; + 'X-Scalar-Runtime-Version': string; +}; +const getPlatformProperties = (): PlatformProperties => { + const detectedPlatform = getDetectedPlatform(); + if (detectedPlatform === 'deno') { + return { + 'X-Scalar-Lang': 'js', + 'X-Scalar-Package-Version': VERSION, + 'X-Scalar-OS': normalizePlatform(Deno.build.os), + 'X-Scalar-Arch': normalizeArch(Deno.build.arch), + 'X-Scalar-Runtime': 'deno', + 'X-Scalar-Runtime-Version': + typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown', + }; + } + if (typeof EdgeRuntime !== 'undefined') { + return { + 'X-Scalar-Lang': 'js', + 'X-Scalar-Package-Version': VERSION, + 'X-Scalar-OS': 'Unknown', + 'X-Scalar-Arch': `other:${EdgeRuntime}`, + 'X-Scalar-Runtime': 'edge', + 'X-Scalar-Runtime-Version': (globalThis as any).process.version, + }; + } + // Check if Node.js + if (detectedPlatform === 'node') { + return { + 'X-Scalar-Lang': 'js', + 'X-Scalar-Package-Version': VERSION, + 'X-Scalar-OS': normalizePlatform((globalThis as any).process.platform ?? 'unknown'), + 'X-Scalar-Arch': normalizeArch((globalThis as any).process.arch ?? 'unknown'), + 'X-Scalar-Runtime': 'node', + 'X-Scalar-Runtime-Version': (globalThis as any).process.version ?? 'unknown', + }; + } + + const browserInfo = getBrowserInfo(); + if (browserInfo) { + return { + 'X-Scalar-Lang': 'js', + 'X-Scalar-Package-Version': VERSION, + 'X-Scalar-OS': 'Unknown', + 'X-Scalar-Arch': 'unknown', + 'X-Scalar-Runtime': `browser:${browserInfo.browser}`, + 'X-Scalar-Runtime-Version': browserInfo.version, + }; + } + + // TODO add support for Cloudflare workers, etc. + return { + 'X-Scalar-Lang': 'js', + 'X-Scalar-Package-Version': VERSION, + 'X-Scalar-OS': 'Unknown', + 'X-Scalar-Arch': 'unknown', + 'X-Scalar-Runtime': 'unknown', + 'X-Scalar-Runtime-Version': 'unknown', + }; +}; + +type BrowserInfo = { + browser: Browser; + version: string; +}; + +declare const navigator: { userAgent: string } | undefined; + +// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts +function getBrowserInfo(): BrowserInfo | null { + if (typeof navigator === 'undefined' || !navigator) { + return null; + } + + // NOTE: The order matters here! + const browserPatterns = [ + { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ }, + { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ }, + ]; + + // Find the FIRST matching browser + for (const { key, pattern } of browserPatterns) { + const match = pattern.exec(navigator.userAgent); + if (match) { + const major = match[1] || 0; + const minor = match[2] || 0; + const patch = match[3] || 0; + + return { browser: key, version: `${major}.${minor}.${patch}` }; + } + } + + return null; +} + +const normalizeArch = (arch: string): Arch => { + // Node docs: + // - https://nodejs.org/api/process.html#processarch + // Deno docs: + // - https://doc.deno.land/deno/stable/~/Deno.build + if (arch === 'x32') return 'x32'; + if (arch === 'x86_64' || arch === 'x64') return 'x64'; + if (arch === 'arm') return 'arm'; + if (arch === 'aarch64' || arch === 'arm64') return 'arm64'; + if (arch) return `other:${arch}`; + return 'unknown'; +}; + +const normalizePlatform = (platform: string): PlatformName => { + // Node platforms: + // - https://nodejs.org/api/process.html#processplatform + // Deno platforms: + // - https://doc.deno.land/deno/stable/~/Deno.build + // - https://github.com/denoland/deno/issues/14799 + + platform = platform.toLowerCase(); + + // NOTE: this iOS check is untested and may not work + // Node does not work natively on IOS, there is a fork at + // https://github.com/nodejs-mobile/nodejs-mobile + // however it is unknown at the time of writing how to detect if it is running + if (platform.includes('ios')) return 'iOS'; + if (platform === 'android') return 'Android'; + if (platform === 'darwin') return 'MacOS'; + if (platform === 'win32') return 'Windows'; + if (platform === 'freebsd') return 'FreeBSD'; + if (platform === 'openbsd') return 'OpenBSD'; + if (platform === 'linux') return 'Linux'; + if (platform) return `Other:${platform}`; + return 'Unknown'; +}; + +let _platformHeaders: PlatformProperties; +export const getPlatformHeaders = () => { + return (_platformHeaders ??= getPlatformProperties()); +}; diff --git a/src/sdk/internal/errors.ts b/src/sdk/internal/errors.ts new file mode 100644 index 0000000..fed0efd --- /dev/null +++ b/src/sdk/internal/errors.ts @@ -0,0 +1,33 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export function isAbortError(err: unknown) { + return ( + typeof err === 'object' && + err !== null && + // Spec-compliant fetch implementations + (('name' in err && (err as any).name === 'AbortError') || + // Expo fetch + ('message' in err && String((err as any).message).includes('FetchRequestCanceledException'))) + ); +} + +export const castToError = (err: any): Error => { + if (err instanceof Error) return err; + if (typeof err === 'object' && err !== null) { + try { + if (Object.prototype.toString.call(err) === '[object Error]') { + // @ts-ignore - not all envs have native support for cause yet + const error = new Error(err.message, err.cause ? { cause: err.cause } : {}); + if (err.stack) error.stack = err.stack; + // @ts-ignore - not all envs have native support for cause yet + if (err.cause && !error.cause) error.cause = err.cause; + if (err.name) error.name = err.name; + return error; + } + } catch {} + try { + return new Error(JSON.stringify(err)); + } catch {} + } + return new Error(err); +}; diff --git a/src/sdk/internal/headers.ts b/src/sdk/internal/headers.ts new file mode 100644 index 0000000..411434e --- /dev/null +++ b/src/sdk/internal/headers.ts @@ -0,0 +1,97 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { isReadonlyArray } from './utils/values'; + +type HeaderValue = string | undefined | null; +export type HeadersLike = + | Headers + | readonly HeaderValue[][] + | Record + | undefined + | null + | NullableHeaders; + +const brand_privateNullableHeaders = /* @__PURE__ */ Symbol('brand.privateNullableHeaders'); + +/** + * @internal + * Users can pass explicit nulls to unset default headers. When we parse them + * into a standard headers type we need to preserve that information. + */ +export type NullableHeaders = { + /** Brand check, prevent users from creating a NullableHeaders. */ + [brand_privateNullableHeaders]: true; + /** Parsed headers. */ + values: Headers; + /** Set of lowercase header names explicitly set to null. */ + nulls: Set; +}; + +function* iterateHeaders(headers: HeadersLike): IterableIterator { + if (!headers) return; + + if (brand_privateNullableHeaders in headers) { + const { values, nulls } = headers; + yield* values.entries(); + for (const name of nulls) { + yield [name, null]; + } + return; + } + + let shouldClear = false; + let iter: Iterable; + if (headers instanceof Headers) { + iter = headers.entries(); + } else if (isReadonlyArray(headers)) { + iter = headers; + } else { + shouldClear = true; + iter = Object.entries(headers ?? {}); + } + for (let row of iter) { + const name = row[0]; + if (typeof name !== 'string') throw new TypeError('expected header name to be a string'); + const values = isReadonlyArray(row[1]) ? row[1] : [row[1]]; + let didClear = false; + for (const value of values) { + if (value === undefined) continue; + + // Objects keys always overwrite older headers, they never append. + // Yield a null to clear the header before adding the new values. + if (shouldClear && !didClear) { + didClear = true; + yield [name, null]; + } + yield [name, value]; + } + } +} + +export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => { + const targetHeaders = new Headers(); + const nullHeaders = new Set(); + for (const headers of newHeaders) { + const seenHeaders = new Set(); + for (const [name, value] of iterateHeaders(headers)) { + const lowerName = name.toLowerCase(); + if (!seenHeaders.has(lowerName)) { + targetHeaders.delete(name); + seenHeaders.add(lowerName); + } + if (value === null) { + targetHeaders.delete(name); + nullHeaders.add(lowerName); + } else { + targetHeaders.append(name, value); + nullHeaders.delete(lowerName); + } + } + } + return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders }; +}; + +export const isEmptyHeaders = (headers: HeadersLike) => { + for (const _ of iterateHeaders(headers)) return false; + return true; +}; diff --git a/src/sdk/internal/parse.ts b/src/sdk/internal/parse.ts new file mode 100644 index 0000000..bf0f241 --- /dev/null +++ b/src/sdk/internal/parse.ts @@ -0,0 +1,76 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import type { FinalRequestOptions } from './request-options'; +import { Stream } from '../core/streaming'; +import { type Dedalus } from '../client'; +import { formatRequestDetails, loggerFor } from './utils/log'; + +export type APIResponseProps = { + response: Response; + options: FinalRequestOptions; + controller: AbortController; + requestLogID: string; + retryOfRequestLogID: string | undefined; + startTime: number; +}; + +export async function defaultParseResponse(client: Dedalus, props: APIResponseProps): Promise { + const { response, requestLogID, retryOfRequestLogID, startTime } = props; + const body = await (async () => { + if (props.options.stream) { + loggerFor(client).debug('response', response.status, response.url, response.headers, response.body); + + // Note: there is an invariant here that isn't represented in the type system + // that if you set `stream: true` the response type must also be `Stream` + + if (props.options.__streamClass) { + return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any; + } + + const contentType = response.headers.get('content-type'); + if (contentType?.includes('ndjson') || contentType?.includes('jsonl')) { + if (!response.body) throw new Error('Attempted to iterate over a response with no body'); + return Stream.fromReadableStream(response.body, props.controller, client) as any; + } + + return Stream.fromSSEResponse(response, props.controller, client) as any; + } + + // fetch refuses to read the body when the status code is 204. + if (response.status === 204) { + return null as T; + } + + if (props.options.__binaryResponse) { + return response as unknown as T; + } + + const contentType = response.headers.get('content-type'); + const mediaType = contentType?.split(';')[0]?.trim(); + const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); + if (isJSON) { + const contentLength = response.headers.get('content-length'); + if (contentLength === '0') { + // if there is no content we can't do anything + return undefined as T; + } + + const json = await response.json(); + return json as T; + } + + const text = await response.text(); + return text as unknown as T; + })(); + loggerFor(client).debug( + `[${requestLogID}] response parsed`, + formatRequestDetails({ + retryOfRequestLogID, + url: response.url, + status: response.status, + body, + durationMs: Date.now() - startTime, + }), + ); + return body; +} diff --git a/src/sdk/internal/qs/LICENSE.md b/src/sdk/internal/qs/LICENSE.md new file mode 100644 index 0000000..3fda157 --- /dev/null +++ b/src/sdk/internal/qs/LICENSE.md @@ -0,0 +1,13 @@ +BSD 3-Clause License + +Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/puruvj/neoqs/graphs/contributors) All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/sdk/internal/qs/README.md b/src/sdk/internal/qs/README.md new file mode 100644 index 0000000..67ae04e --- /dev/null +++ b/src/sdk/internal/qs/README.md @@ -0,0 +1,3 @@ +# qs + +This is a vendored version of [neoqs](https://github.com/PuruVJ/neoqs) which is a TypeScript rewrite of [qs](https://github.com/ljharb/qs), a query string library. diff --git a/src/sdk/internal/qs/formats.ts b/src/sdk/internal/qs/formats.ts new file mode 100644 index 0000000..e76a742 --- /dev/null +++ b/src/sdk/internal/qs/formats.ts @@ -0,0 +1,10 @@ +import type { Format } from './types'; + +export const default_format: Format = 'RFC3986'; +export const default_formatter = (v: PropertyKey) => String(v); +export const formatters: Record string> = { + RFC1738: (v: PropertyKey) => String(v).replace(/%20/g, '+'), + RFC3986: default_formatter, +}; +export const RFC1738 = 'RFC1738'; +export const RFC3986 = 'RFC3986'; diff --git a/src/sdk/internal/qs/index.ts b/src/sdk/internal/qs/index.ts new file mode 100644 index 0000000..c3a3620 --- /dev/null +++ b/src/sdk/internal/qs/index.ts @@ -0,0 +1,13 @@ +import { default_format, formatters, RFC1738, RFC3986 } from './formats'; + +const formats = { + formatters, + RFC1738, + RFC3986, + default: default_format, +}; + +export { stringify } from './stringify'; +export { formats }; + +export type { DefaultDecoder, DefaultEncoder, Format, ParseOptions, StringifyOptions } from './types'; diff --git a/src/sdk/internal/qs/stringify.ts b/src/sdk/internal/qs/stringify.ts new file mode 100644 index 0000000..7e71387 --- /dev/null +++ b/src/sdk/internal/qs/stringify.ts @@ -0,0 +1,385 @@ +import { encode, is_buffer, maybe_map, has } from './utils'; +import { default_format, default_formatter, formatters } from './formats'; +import type { NonNullableProperties, StringifyOptions } from './types'; +import { isArray } from '../utils/values'; + +const array_prefix_generators = { + brackets(prefix: PropertyKey) { + return String(prefix) + '[]'; + }, + comma: 'comma', + indices(prefix: PropertyKey, key: string) { + return String(prefix) + '[' + key + ']'; + }, + repeat(prefix: PropertyKey) { + return String(prefix); + }, +}; + +const push_to_array = function (arr: any[], value_or_array: any) { + Array.prototype.push.apply(arr, isArray(value_or_array) ? value_or_array : [value_or_array]); +}; + +let toISOString; + +const defaults = { + addQueryPrefix: false, + allowDots: false, + allowEmptyArrays: false, + arrayFormat: 'indices', + charset: 'utf-8', + charsetSentinel: false, + delimiter: '&', + encode: true, + encodeDotInKeys: false, + encoder: encode, + encodeValuesOnly: false, + format: default_format, + formatter: default_formatter, + /** @deprecated */ + indices: false, + serializeDate(date) { + return (toISOString ??= Function.prototype.call.bind(Date.prototype.toISOString))(date); + }, + skipNulls: false, + strictNullHandling: false, +} as NonNullableProperties; + +function is_non_nullish_primitive(v: unknown): v is string | number | boolean | symbol | bigint { + return ( + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' || + typeof v === 'symbol' || + typeof v === 'bigint' + ); +} + +const sentinel = {}; + +function inner_stringify( + object: any, + prefix: PropertyKey, + generateArrayPrefix: StringifyOptions['arrayFormat'] | ((prefix: string, key: string) => string), + commaRoundTrip: boolean, + allowEmptyArrays: boolean, + strictNullHandling: boolean, + skipNulls: boolean, + encodeDotInKeys: boolean, + encoder: StringifyOptions['encoder'], + filter: StringifyOptions['filter'], + sort: StringifyOptions['sort'], + allowDots: StringifyOptions['allowDots'], + serializeDate: StringifyOptions['serializeDate'], + format: StringifyOptions['format'], + formatter: StringifyOptions['formatter'], + encodeValuesOnly: boolean, + charset: StringifyOptions['charset'], + sideChannel: WeakMap, +) { + let obj = object; + + let tmp_sc = sideChannel; + let step = 0; + let find_flag = false; + while ((tmp_sc = tmp_sc.get(sentinel)) !== void undefined && !find_flag) { + // Where object last appeared in the ref tree + const pos = tmp_sc.get(object); + step += 1; + if (typeof pos !== 'undefined') { + if (pos === step) { + throw new RangeError('Cyclic object value'); + } else { + find_flag = true; // Break while + } + } + if (typeof tmp_sc.get(sentinel) === 'undefined') { + step = 0; + } + } + + if (typeof filter === 'function') { + obj = filter(prefix, obj); + } else if (obj instanceof Date) { + obj = serializeDate?.(obj); + } else if (generateArrayPrefix === 'comma' && isArray(obj)) { + obj = maybe_map(obj, function (value) { + if (value instanceof Date) { + return serializeDate?.(value); + } + return value; + }); + } + + if (obj === null) { + if (strictNullHandling) { + return encoder && !encodeValuesOnly ? + // @ts-expect-error + encoder(prefix, defaults.encoder, charset, 'key', format) + : prefix; + } + + obj = ''; + } + + if (is_non_nullish_primitive(obj) || is_buffer(obj)) { + if (encoder) { + const key_value = + encodeValuesOnly ? prefix + // @ts-expect-error + : encoder(prefix, defaults.encoder, charset, 'key', format); + return [ + formatter?.(key_value) + + '=' + + // @ts-expect-error + formatter?.(encoder(obj, defaults.encoder, charset, 'value', format)), + ]; + } + return [formatter?.(prefix) + '=' + formatter?.(String(obj))]; + } + + const values: string[] = []; + + if (typeof obj === 'undefined') { + return values; + } + + let obj_keys; + if (generateArrayPrefix === 'comma' && isArray(obj)) { + // we need to join elements in + if (encodeValuesOnly && encoder) { + // @ts-expect-error values only + obj = maybe_map(obj, encoder); + } + obj_keys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; + } else if (isArray(filter)) { + obj_keys = filter; + } else { + const keys = Object.keys(obj); + obj_keys = sort ? keys.sort(sort) : keys; + } + + const encoded_prefix = encodeDotInKeys ? String(prefix).replace(/\./g, '%2E') : String(prefix); + + const adjusted_prefix = + commaRoundTrip && isArray(obj) && obj.length === 1 ? encoded_prefix + '[]' : encoded_prefix; + + if (allowEmptyArrays && isArray(obj) && obj.length === 0) { + return adjusted_prefix + '[]'; + } + + for (let j = 0; j < obj_keys.length; ++j) { + const key = obj_keys[j]; + const value = + // @ts-ignore + typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key as any]; + + if (skipNulls && value === null) { + continue; + } + + // @ts-ignore + const encoded_key = allowDots && encodeDotInKeys ? (key as any).replace(/\./g, '%2E') : key; + const key_prefix = + isArray(obj) ? + typeof generateArrayPrefix === 'function' ? + generateArrayPrefix(adjusted_prefix, encoded_key) + : adjusted_prefix + : adjusted_prefix + (allowDots ? '.' + encoded_key : '[' + encoded_key + ']'); + + sideChannel.set(object, step); + const valueSideChannel = new WeakMap(); + valueSideChannel.set(sentinel, sideChannel); + push_to_array( + values, + inner_stringify( + value, + key_prefix, + generateArrayPrefix, + commaRoundTrip, + allowEmptyArrays, + strictNullHandling, + skipNulls, + encodeDotInKeys, + // @ts-ignore + generateArrayPrefix === 'comma' && encodeValuesOnly && isArray(obj) ? null : encoder, + filter, + sort, + allowDots, + serializeDate, + format, + formatter, + encodeValuesOnly, + charset, + valueSideChannel, + ), + ); + } + + return values; +} + +function normalize_stringify_options( + opts: StringifyOptions = defaults, +): NonNullableProperties> & { indices?: boolean } { + if (typeof opts.allowEmptyArrays !== 'undefined' && typeof opts.allowEmptyArrays !== 'boolean') { + throw new TypeError('`allowEmptyArrays` option can only be `true` or `false`, when provided'); + } + + if (typeof opts.encodeDotInKeys !== 'undefined' && typeof opts.encodeDotInKeys !== 'boolean') { + throw new TypeError('`encodeDotInKeys` option can only be `true` or `false`, when provided'); + } + + if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') { + throw new TypeError('Encoder has to be a function.'); + } + + const charset = opts.charset || defaults.charset; + if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') { + throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined'); + } + + let format = default_format; + if (typeof opts.format !== 'undefined') { + if (!has(formatters, opts.format)) { + throw new TypeError('Unknown format option provided.'); + } + format = opts.format; + } + const formatter = formatters[format]; + + let filter = defaults.filter; + if (typeof opts.filter === 'function' || isArray(opts.filter)) { + filter = opts.filter; + } + + let arrayFormat: StringifyOptions['arrayFormat']; + if (opts.arrayFormat && opts.arrayFormat in array_prefix_generators) { + arrayFormat = opts.arrayFormat; + } else if ('indices' in opts) { + arrayFormat = opts.indices ? 'indices' : 'repeat'; + } else { + arrayFormat = defaults.arrayFormat; + } + + if ('commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + + const allowDots = + typeof opts.allowDots === 'undefined' ? + !!opts.encodeDotInKeys === true ? + true + : defaults.allowDots + : !!opts.allowDots; + + return { + addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, + // @ts-ignore + allowDots: allowDots, + allowEmptyArrays: + typeof opts.allowEmptyArrays === 'boolean' ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, + arrayFormat: arrayFormat, + charset: charset, + charsetSentinel: + typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, + commaRoundTrip: !!opts.commaRoundTrip, + delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, + encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, + encodeDotInKeys: + typeof opts.encodeDotInKeys === 'boolean' ? opts.encodeDotInKeys : defaults.encodeDotInKeys, + encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder, + encodeValuesOnly: + typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly, + filter: filter, + format: format, + formatter: formatter, + serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate, + skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls, + // @ts-ignore + sort: typeof opts.sort === 'function' ? opts.sort : null, + strictNullHandling: + typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling, + }; +} + +export function stringify(object: any, opts: StringifyOptions = {}) { + let obj = object; + const options = normalize_stringify_options(opts); + + let obj_keys: PropertyKey[] | undefined; + let filter; + + if (typeof options.filter === 'function') { + filter = options.filter; + obj = filter('', obj); + } else if (isArray(options.filter)) { + filter = options.filter; + obj_keys = filter; + } + + const keys: string[] = []; + + if (typeof obj !== 'object' || obj === null) { + return ''; + } + + const generateArrayPrefix = array_prefix_generators[options.arrayFormat]; + const commaRoundTrip = generateArrayPrefix === 'comma' && options.commaRoundTrip; + + if (!obj_keys) { + obj_keys = Object.keys(obj); + } + + if (options.sort) { + obj_keys.sort(options.sort); + } + + const sideChannel = new WeakMap(); + for (let i = 0; i < obj_keys.length; ++i) { + const key = obj_keys[i]!; + + if (options.skipNulls && obj[key] === null) { + continue; + } + push_to_array( + keys, + inner_stringify( + obj[key], + key, + // @ts-expect-error + generateArrayPrefix, + commaRoundTrip, + options.allowEmptyArrays, + options.strictNullHandling, + options.skipNulls, + options.encodeDotInKeys, + options.encode ? options.encoder : null, + options.filter, + options.sort, + options.allowDots, + options.serializeDate, + options.format, + options.formatter, + options.encodeValuesOnly, + options.charset, + sideChannel, + ), + ); + } + + const joined = keys.join(options.delimiter); + let prefix = options.addQueryPrefix === true ? '?' : ''; + + if (options.charsetSentinel) { + if (options.charset === 'iso-8859-1') { + // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark + prefix += 'utf8=%26%2310003%3B&'; + } else { + // encodeURIComponent('✓') + prefix += 'utf8=%E2%9C%93&'; + } + } + + return joined.length > 0 ? prefix + joined : ''; +} diff --git a/src/sdk/internal/qs/types.ts b/src/sdk/internal/qs/types.ts new file mode 100644 index 0000000..7c28dbb --- /dev/null +++ b/src/sdk/internal/qs/types.ts @@ -0,0 +1,71 @@ +export type Format = 'RFC1738' | 'RFC3986'; + +export type DefaultEncoder = (str: any, defaultEncoder?: any, charset?: string) => string; +export type DefaultDecoder = (str: string, decoder?: any, charset?: string) => string; + +export type BooleanOptional = boolean | undefined; + +export type StringifyBaseOptions = { + delimiter?: string; + allowDots?: boolean; + encodeDotInKeys?: boolean; + strictNullHandling?: boolean; + skipNulls?: boolean; + encode?: boolean; + encoder?: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format?: Format, + ) => string; + filter?: Array | ((prefix: PropertyKey, value: any) => any); + arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma'; + indices?: boolean; + sort?: ((a: PropertyKey, b: PropertyKey) => number) | null; + serializeDate?: (d: Date) => string; + format?: 'RFC1738' | 'RFC3986'; + formatter?: (str: PropertyKey) => string; + encodeValuesOnly?: boolean; + addQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + allowEmptyArrays?: boolean; + commaRoundTrip?: boolean; +}; + +export type StringifyOptions = StringifyBaseOptions; + +export type ParseBaseOptions = { + comma?: boolean; + delimiter?: string | RegExp; + depth?: number | false; + decoder?: (str: string, defaultDecoder: DefaultDecoder, charset: string, type: 'key' | 'value') => any; + arrayLimit?: number; + parseArrays?: boolean; + plainObjects?: boolean; + allowPrototypes?: boolean; + allowSparse?: boolean; + parameterLimit?: number; + strictDepth?: boolean; + strictNullHandling?: boolean; + ignoreQueryPrefix?: boolean; + charset?: 'utf-8' | 'iso-8859-1'; + charsetSentinel?: boolean; + interpretNumericEntities?: boolean; + allowEmptyArrays?: boolean; + duplicates?: 'combine' | 'first' | 'last'; + allowDots?: boolean; + decodeDotInKeys?: boolean; +}; + +export type ParseOptions = ParseBaseOptions; + +export type ParsedQs = { + [key: string]: undefined | string | string[] | ParsedQs | ParsedQs[]; +}; + +// Type to remove null or undefined union from each property +export type NonNullableProperties = { + [K in keyof T]-?: Exclude; +}; diff --git a/src/sdk/internal/qs/utils.ts b/src/sdk/internal/qs/utils.ts new file mode 100644 index 0000000..4cd5657 --- /dev/null +++ b/src/sdk/internal/qs/utils.ts @@ -0,0 +1,265 @@ +import { RFC1738 } from './formats'; +import type { DefaultEncoder, Format } from './types'; +import { isArray } from '../utils/values'; + +export let has = (obj: object, key: PropertyKey): boolean => ( + (has = (Object as any).hasOwn ?? Function.prototype.call.bind(Object.prototype.hasOwnProperty)), + has(obj, key) +); + +const hex_table = /* @__PURE__ */ (() => { + const array = []; + for (let i = 0; i < 256; ++i) { + array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase()); + } + + return array; +})(); + +function compact_queue>(queue: Array<{ obj: T; prop: string }>) { + while (queue.length > 1) { + const item = queue.pop(); + if (!item) continue; + + const obj = item.obj[item.prop]; + + if (isArray(obj)) { + const compacted: unknown[] = []; + + for (let j = 0; j < obj.length; ++j) { + if (typeof obj[j] !== 'undefined') { + compacted.push(obj[j]); + } + } + + // @ts-ignore + item.obj[item.prop] = compacted; + } + } +} + +function array_to_object(source: any[], options: { plainObjects: boolean }) { + const obj = options && options.plainObjects ? Object.create(null) : {}; + for (let i = 0; i < source.length; ++i) { + if (typeof source[i] !== 'undefined') { + obj[i] = source[i]; + } + } + + return obj; +} + +export function merge( + target: any, + source: any, + options: { plainObjects?: boolean; allowPrototypes?: boolean } = {}, +) { + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (isArray(target)) { + target.push(source); + } else if (target && typeof target === 'object') { + if ((options && (options.plainObjects || options.allowPrototypes)) || !has(Object.prototype, source)) { + target[source] = true; + } + } else { + return [target, source]; + } + + return target; + } + + if (!target || typeof target !== 'object') { + return [target].concat(source); + } + + let mergeTarget = target; + if (isArray(target) && !isArray(source)) { + // @ts-ignore + mergeTarget = array_to_object(target, options); + } + + if (isArray(target) && isArray(source)) { + source.forEach(function (item, i) { + if (has(target, i)) { + const targetItem = target[i]; + if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') { + target[i] = merge(targetItem, item, options); + } else { + target.push(item); + } + } else { + target[i] = item; + } + }); + return target; + } + + return Object.keys(source).reduce(function (acc, key) { + const value = source[key]; + + if (has(acc, key)) { + acc[key] = merge(acc[key], value, options); + } else { + acc[key] = value; + } + return acc; + }, mergeTarget); +} + +export function assign_single_source(target: any, source: any) { + return Object.keys(source).reduce(function (acc, key) { + acc[key] = source[key]; + return acc; + }, target); +} + +export function decode(str: string, _: any, charset: string) { + const strWithoutPlus = str.replace(/\+/g, ' '); + if (charset === 'iso-8859-1') { + // unescape never throws, no try...catch needed: + return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape); + } + // utf-8 + try { + return decodeURIComponent(strWithoutPlus); + } catch (e) { + return strWithoutPlus; + } +} + +const limit = 1024; + +export const encode: ( + str: any, + defaultEncoder: DefaultEncoder, + charset: string, + type: 'key' | 'value', + format: Format, +) => string = (str, _defaultEncoder, charset, _kind, format: Format) => { + // This code was originally written by Brian White for the io.js core querystring library. + // It has been adapted here for stricter adherence to RFC 3986 + if (str.length === 0) { + return str; + } + + let string = str; + if (typeof str === 'symbol') { + string = Symbol.prototype.toString.call(str); + } else if (typeof str !== 'string') { + string = String(str); + } + + if (charset === 'iso-8859-1') { + return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) { + return '%26%23' + parseInt($0.slice(2), 16) + '%3B'; + }); + } + + let out = ''; + for (let j = 0; j < string.length; j += limit) { + const segment = string.length >= limit ? string.slice(j, j + limit) : string; + const arr = []; + + for (let i = 0; i < segment.length; ++i) { + let c = segment.charCodeAt(i); + if ( + c === 0x2d || // - + c === 0x2e || // . + c === 0x5f || // _ + c === 0x7e || // ~ + (c >= 0x30 && c <= 0x39) || // 0-9 + (c >= 0x41 && c <= 0x5a) || // a-z + (c >= 0x61 && c <= 0x7a) || // A-Z + (format === RFC1738 && (c === 0x28 || c === 0x29)) // ( ) + ) { + arr[arr.length] = segment.charAt(i); + continue; + } + + if (c < 0x80) { + arr[arr.length] = hex_table[c]; + continue; + } + + if (c < 0x800) { + arr[arr.length] = hex_table[0xc0 | (c >> 6)]! + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + if (c < 0xd800 || c >= 0xe000) { + arr[arr.length] = + hex_table[0xe0 | (c >> 12)]! + hex_table[0x80 | ((c >> 6) & 0x3f)] + hex_table[0x80 | (c & 0x3f)]; + continue; + } + + i += 1; + c = 0x10000 + (((c & 0x3ff) << 10) | (segment.charCodeAt(i) & 0x3ff)); + + arr[arr.length] = + hex_table[0xf0 | (c >> 18)]! + + hex_table[0x80 | ((c >> 12) & 0x3f)] + + hex_table[0x80 | ((c >> 6) & 0x3f)] + + hex_table[0x80 | (c & 0x3f)]; + } + + out += arr.join(''); + } + + return out; +}; + +export function compact(value: any) { + const queue = [{ obj: { o: value }, prop: 'o' }]; + const refs = []; + + for (let i = 0; i < queue.length; ++i) { + const item = queue[i]; + // @ts-ignore + const obj = item.obj[item.prop]; + + const keys = Object.keys(obj); + for (let j = 0; j < keys.length; ++j) { + const key = keys[j]!; + const val = obj[key]; + if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) { + queue.push({ obj: obj, prop: key }); + refs.push(val); + } + } + } + + compact_queue(queue); + + return value; +} + +export function is_regexp(obj: any) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +} + +export function is_buffer(obj: any) { + if (!obj || typeof obj !== 'object') { + return false; + } + + return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); +} + +export function combine(a: any, b: any) { + return [].concat(a, b); +} + +export function maybe_map(val: T[], fn: (v: T) => T) { + if (isArray(val)) { + const mapped = []; + for (let i = 0; i < val.length; i += 1) { + mapped.push(fn(val[i]!)); + } + return mapped; + } + return fn(val); +} diff --git a/src/sdk/internal/request-options.ts b/src/sdk/internal/request-options.ts new file mode 100644 index 0000000..55b1438 --- /dev/null +++ b/src/sdk/internal/request-options.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { NullableHeaders } from './headers'; + +import type { BodyInit } from './builtin-types'; +import { Stream } from '../core/streaming'; +import type { HTTPMethod, MergedRequestInit } from './types'; +import { type HeadersLike } from './headers'; + +export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; + +export type RequestOptions = { + /** + * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). + */ + method?: HTTPMethod; + + /** + * The URL path for the request. + * + * @example "/v1/foo" + */ + path?: string; + + /** + * Query parameters to include in the request URL. + */ + query?: object | undefined | null; + + /** + * The request body. Can be a string, JSON object, FormData, or other supported types. + */ + body?: unknown; + + /** + * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. + */ + headers?: HeadersLike; + + /** + * The maximum number of times that the client will retry a request in case of a + * temporary failure, like a network error or a 5XX error from the server. + * + * @default 2 + */ + maxRetries?: number; + + stream?: boolean | undefined; + + /** + * The maximum amount of time (in milliseconds) that the client should wait for a response + * from the server before timing out a single request. + * + * @unit milliseconds + */ + timeout?: number; + + /** + * Additional `RequestInit` options to be passed to the underlying `fetch` call. + * These options will be merged with the client's default fetch options. + */ + fetchOptions?: MergedRequestInit; + + /** + * An AbortSignal that can be used to cancel the request. + */ + signal?: AbortSignal | undefined | null; + + /** + * A unique key for this request to enable idempotency. + */ + idempotencyKey?: string; + + /** + * Override the default base URL for this specific request. + */ + defaultBaseURL?: string | undefined; + + __binaryResponse?: boolean | undefined; + __streamClass?: typeof Stream; +}; + +export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; +export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; + +export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { + return { + bodyHeaders: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), + }; +}; diff --git a/src/sdk/internal/shim-types.ts b/src/sdk/internal/shim-types.ts new file mode 100644 index 0000000..6d992a5 --- /dev/null +++ b/src/sdk/internal/shim-types.ts @@ -0,0 +1,26 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** + * Shims for types that we can't always rely on being available globally. + * + * Note: these only exist at the type-level, there is no corresponding runtime + * version for any of these symbols. + */ + +type NeverToAny = T extends never ? any : T; + +/** @ts-ignore */ +type _DOMReadableStream = globalThis.ReadableStream; + +/** @ts-ignore */ +type _NodeReadableStream = import('stream/web').ReadableStream; + +type _ConditionalNodeReadableStream = + typeof globalThis extends { ReadableStream: any } ? never : _NodeReadableStream; + +type _ReadableStream = NeverToAny< + | ([0] extends [1 & _DOMReadableStream] ? never : _DOMReadableStream) + | ([0] extends [1 & _ConditionalNodeReadableStream] ? never : _ConditionalNodeReadableStream) +>; + +export type { _ReadableStream as ReadableStream }; diff --git a/src/sdk/internal/shims.ts b/src/sdk/internal/shims.ts new file mode 100644 index 0000000..0774514 --- /dev/null +++ b/src/sdk/internal/shims.ts @@ -0,0 +1,107 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** + * This module provides internal shims and utility functions for environments where certain Node.js or global types may not be available. + * + * These are used to ensure we can provide a consistent behaviour between different JavaScript environments and good error + * messages in cases where an environment isn't fully supported. + */ + +import type { Fetch } from './builtin-types'; +import type { ReadableStream } from './shim-types'; + +export function getDefaultFetch(): Fetch { + if (typeof fetch !== 'undefined') { + return fetch as any; + } + + throw new Error( + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new Dedalus({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + ); +} + +type ReadableStreamArgs = ConstructorParameters; + +export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream { + const ReadableStream = (globalThis as any).ReadableStream; + if (typeof ReadableStream === 'undefined') { + // Note: All of the platforms / runtimes we officially support already define + // `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes. + throw new Error( + '`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`', + ); + } + + return new ReadableStream(...args); +} + +export function ReadableStreamFrom(iterable: Iterable | AsyncIterable): ReadableStream { + let iter: AsyncIterator | Iterator = + Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator](); + + return makeReadableStream({ + start() {}, + async pull(controller: any) { + const { done, value } = await iter.next(); + if (done) { + controller.close(); + } else { + controller.enqueue(value); + } + }, + async cancel() { + await iter.return?.(); + }, + }); +} + +/** + * Most browsers don't yet have async iterable support for ReadableStream, + * and Node has a very different way of reading bytes from its "ReadableStream". + * + * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 + */ +export function ReadableStreamToAsyncIterable(stream: any): AsyncIterableIterator { + if (stream[Symbol.asyncIterator]) return stream; + + const reader = stream.getReader(); + return { + async next() { + try { + const result = await reader.read(); + if (result?.done) reader.releaseLock(); // release lock when stream becomes closed + return result; + } catch (e) { + reader.releaseLock(); // release lock when stream becomes errored + throw e; + } + }, + async return() { + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; + return { done: true, value: undefined }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; +} + +/** + * Cancels a ReadableStream we don't need to consume. + * See https://undici.nodejs.org/#/?id=garbage-collection + */ +export async function CancelReadableStream(stream: any): Promise { + if (stream === null || typeof stream !== 'object') return; + + if (stream[Symbol.asyncIterator]) { + await stream[Symbol.asyncIterator]().return?.(); + return; + } + + const reader = stream.getReader(); + const cancelPromise = reader.cancel(); + reader.releaseLock(); + await cancelPromise; +} diff --git a/src/sdk/internal/to-file.ts b/src/sdk/internal/to-file.ts new file mode 100644 index 0000000..30eada3 --- /dev/null +++ b/src/sdk/internal/to-file.ts @@ -0,0 +1,154 @@ +import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads'; +import type { FilePropertyBag } from './builtin-types'; +import { checkFileSupport } from './uploads'; + +type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView; + +/** + * Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc. + * Don't add arrayBuffer here, node-fetch doesn't have it + */ +interface BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ + readonly size: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ + readonly type: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ + text(): Promise; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ + slice(start?: number, end?: number): BlobLike; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.size === 'number' && + typeof value.type === 'string' && + typeof value.text === 'function' && + typeof value.slice === 'function' && + typeof value.arrayBuffer === 'function'; + +/** + * Intended to match DOM File, node:buffer File, undici File, etc. + */ +interface FileLike extends BlobLike { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ + readonly lastModified: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ + readonly name?: string | undefined; +} + +/** + * This check adds the arrayBuffer() method type because it is available and used at runtime + */ +const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise } => + value != null && + typeof value === 'object' && + typeof value.name === 'string' && + typeof value.lastModified === 'number' && + isBlobLike(value); + +/** + * Intended to match DOM Response, node-fetch Response, undici Response, etc. + */ +export interface ResponseLike { + url: string; + blob(): Promise; +} + +const isResponseLike = (value: any): value is ResponseLike => + value != null && + typeof value === 'object' && + typeof value.url === 'string' && + typeof value.blob === 'function'; + +export type ToFileInput = + | FileLike + | ResponseLike + | Exclude + | AsyncIterable; + +/** + * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats + * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts + * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible + * @param {Object=} options additional properties + * @param {string=} options.type the MIME type of the content + * @param {number=} options.lastModified the last modified timestamp + * @returns a {@link File} with the given properties + */ +export async function toFile( + value: ToFileInput | PromiseLike, + name?: string | null | undefined, + options?: FilePropertyBag | undefined, +): Promise { + checkFileSupport(); + + // If it's a promise, resolve it. + value = await value; + + // If we've been given a `File` we don't need to do anything + if (isFileLike(value)) { + if (value instanceof File) { + return value; + } + return makeFile([await value.arrayBuffer()], value.name); + } + + if (isResponseLike(value)) { + const blob = await value.blob(); + name ||= new URL(value.url).pathname.split(/[\\/]/).pop(); + + return makeFile(await getBytes(blob), name, options); + } + + const parts = await getBytes(value); + + name ||= getName(value); + + if (!options?.type) { + const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type); + if (typeof type === 'string') { + options = { ...options, type }; + } + } + + return makeFile(parts, name, options); +} + +async function getBytes(value: BlobLikePart | AsyncIterable): Promise> { + let parts: Array = []; + if ( + typeof value === 'string' || + ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. + value instanceof ArrayBuffer + ) { + parts.push(value); + } else if (isBlobLike(value)) { + parts.push(value instanceof Blob ? value : await value.arrayBuffer()); + } else if ( + isAsyncIterable(value) // includes Readable, ReadableStream, etc. + ) { + for await (const chunk of value) { + parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating? + } + } else { + const constructor = value?.constructor?.name; + throw new Error( + `Unexpected data type: ${typeof value}${ + constructor ? `; constructor: ${constructor}` : '' + }${propsForError(value)}`, + ); + } + + return parts; +} + +function propsForError(value: unknown): string { + if (typeof value !== 'object' || value === null) return ''; + const props = Object.getOwnPropertyNames(value); + return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`; +} diff --git a/src/sdk/internal/types.ts b/src/sdk/internal/types.ts new file mode 100644 index 0000000..93a4f5a --- /dev/null +++ b/src/sdk/internal/types.ts @@ -0,0 +1,93 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export type PromiseOrValue = T | Promise; +export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'; + +export type KeysEnum = { [P in keyof Required]: true }; + +export type FinalizedRequestInit = RequestInit & { headers: Headers }; + +type NotAny = [0] extends [1 & T] ? never : T; + +/** + * Some environments overload the global fetch function, and Parameters only gets the last signature. + */ +type OverloadedParameters = + T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + (...args: infer D): unknown; + } + ) ? + A | B | C | D + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + (...args: infer C): unknown; + } + ) ? + A | B | C + : T extends ( + { + (...args: infer A): unknown; + (...args: infer B): unknown; + } + ) ? + A | B + : T extends (...args: infer A) => unknown ? A + : never; + +/** + * These imports attempt to get types from a parent package's dependencies. + * Unresolved bare specifiers can trigger [automatic type acquisition][1] in some projects, which + * would cause typescript to show types not present at runtime. To avoid this, we import + * directly from parent node_modules folders. + * + * We need to check multiple levels because we don't know what directory structure we'll be in. + * For example, pnpm generates directories like this: + * ``` + * node_modules + * ├── .pnpm + * │ └── pkg@1.0.0 + * │ └── node_modules + * │ └── pkg + * │ └── internal + * │ └── types.d.ts + * ├── pkg -> .pnpm/pkg@1.0.0/node_modules/pkg + * └── undici + * ``` + * + * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition + */ +/** @ts-ignore For users with \@types/node */ /* prettier-ignore */ +type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with undici */ /* prettier-ignore */ +type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with \@types/bun */ /* prettier-ignore */ +type BunRequestInit = globalThis.FetchRequestInit; +/** @ts-ignore For users with node-fetch@2 */ /* prettier-ignore */ +type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ /* prettier-ignore */ +type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +/** @ts-ignore For users who use Deno */ /* prettier-ignore */ +type FetchRequestInit = NonNullable[1]>; + +type RequestInits = + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny + | NotAny; + +/** + * This type contains `RequestInit` options that may be available on the current runtime, + * including per-platform extensions like `dispatcher`, `agent`, `client`, etc. + */ +export type MergedRequestInit = RequestInits & + /** We don't include these in the types as they'll be overridden for every request. */ + Partial>; diff --git a/src/sdk/internal/uploads.ts b/src/sdk/internal/uploads.ts new file mode 100644 index 0000000..bfa3673 --- /dev/null +++ b/src/sdk/internal/uploads.ts @@ -0,0 +1,201 @@ +import { type RequestOptions } from './request-options'; +import type { FilePropertyBag, Fetch } from './builtin-types'; +import type { Dedalus } from '../client'; +import { ReadableStreamFrom } from './shims'; + +export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; +type FsReadStream = AsyncIterable & { path: string | { toString(): string } }; + +// https://github.com/oven-sh/bun/issues/5980 +interface BunFile extends Blob { + readonly name?: string | undefined; +} + +export const checkFileSupport = () => { + if (typeof File === 'undefined') { + const { process } = globalThis as any; + const isOldNode = + typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20; + throw new Error( + '`File` is not defined as a global, which is required for file uploads.' + + (isOldNode ? + " Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`." + : ''), + ); + } +}; + +/** + * Typically, this is a native "File" class. + * + * We provide the {@link toFile} utility to convert a variety of objects + * into the File class. + * + * For convenience, you can also pass a fetch Response, or in Node, + * the result of fs.createReadStream(). + */ +export type Uploadable = Blob | File | Response | FsReadStream | BunFile; + +/** + * Construct a `File` instance. This is used to ensure a helpful error is thrown + * for environments that don't define a global `File` yet. + */ +export function makeFile( + fileBits: BlobPart[], + fileName: string | undefined, + options?: FilePropertyBag, +): File { + checkFileSupport(); + return new File(fileBits as any, fileName ?? 'unknown_file', options); +} + +export function getName(value: any): string | undefined { + return ( + ( + (typeof value === 'object' && + value !== null && + (('name' in value && value.name && String(value.name)) || + ('url' in value && value.url && String(value.url)) || + ('filename' in value && value.filename && String(value.filename)) || + ('path' in value && value.path && String(value.path)))) || + '' + ) + .split(/[\\/]/) + .pop() || undefined + ); +} + +export const isAsyncIterable = (value: any): value is AsyncIterable => + value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; + +/** + * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. + * Otherwise returns the request as is. + */ +export const maybeMultipartFormRequestOptions = async ( + opts: RequestOptions, + fetch: Dedalus | Fetch, +): Promise => { + if (!hasUploadableValue(opts.body)) return opts; + + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +type MultipartFormRequestOptions = Omit & { body: unknown }; + +export const multipartFormRequestOptions = async ( + opts: MultipartFormRequestOptions, + fetch: Dedalus | Fetch, +): Promise => { + return { ...opts, body: await createForm(opts.body, fetch) }; +}; + +const supportsFormDataMap = /* @__PURE__ */ new WeakMap>(); + +/** + * node-fetch doesn't support the global FormData object in recent node versions. Instead of sending + * properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]". + * This function detects if the fetch function provided supports the global FormData object to avoid + * confusing error messages later on. + */ +function supportsFormData(fetchObject: Dedalus | Fetch): Promise { + const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; + const cached = supportsFormDataMap.get(fetch); + if (cached) return cached; + const promise = (async () => { + try { + // Prefer a `Response` constructor we can reach without a network round-trip: the one attached to + // the fetch function, then the global `Response`. Only fall back to probing `data:,` when neither + // exists, so serializing an already-provided File/Blob never triggers an extra fetch (which would + // otherwise show up as a spurious request to `data:,` before the real API call). + const FetchResponse = ( + 'Response' in fetch ? fetch.Response + : typeof Response !== 'undefined' ? Response + : (await fetch('data:,')).constructor) as typeof Response; + const data = new FormData(); + if (data.toString() === (await new FetchResponse(data).text())) { + return false; + } + return true; + } catch { + // avoid false negatives + return true; + } + })(); + supportsFormDataMap.set(fetch, promise); + return promise; +} + +export const createForm = async >( + body: T | undefined, + fetch: Dedalus | Fetch, +): Promise => { + if (!(await supportsFormData(fetch))) { + throw new TypeError( + 'The provided fetch function does not support file uploads with the current global FormData class.', + ); + } + const form = new FormData(); + if (isUploadable(body)) { + // Multipart schemas can describe the whole request body as a single binary part. + await addFormValue(form, 'body', body); + return form; + } + await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); + return form; +}; + +// We check for Blob not File because Bun.File doesn't inherit from File, +// but they both inherit from Blob and have a `name` property at runtime. +const isBlob = (value: unknown): value is Blob => value instanceof Blob; + +const isUploadable = (value: unknown) => + typeof value === 'object' && + value !== null && + (value instanceof Response || isAsyncIterable(value) || isBlob(value)); + +const hasUploadableValue = (value: unknown): boolean => { + if (isUploadable(value)) return true; + if (Array.isArray(value)) return value.some(hasUploadableValue); + if (value && typeof value === 'object') { + for (const k in value) { + if (hasUploadableValue((value as any)[k])) return true; + } + } + return false; +}; + +const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { + if (value === undefined) return; + if (value == null) { + throw new TypeError( + `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, + ); + } + + // TODO: make nested formats configurable + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + form.append(key, String(value)); + } else if (value instanceof Response) { + form.append(key, makeFile([await value.blob()], getName(value))); + } else if (isAsyncIterable(value)) { + form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value))); + } else if (isBlob(value)) { + const name = getName(value); + if (name === undefined) { + form.append(key, value); + } else { + form.append(key, value, name); + } + } else if (Array.isArray(value)) { + await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); + } else if (typeof value === 'object') { + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), + ); + } else { + throw new TypeError( + `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, + ); + } +}; diff --git a/src/sdk/internal/utils.ts b/src/sdk/internal/utils.ts new file mode 100644 index 0000000..57f670f --- /dev/null +++ b/src/sdk/internal/utils.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export * from './utils/values'; +export * from './utils/base64'; +export * from './utils/env'; +export * from './utils/log'; +export * from './utils/uuid'; +export * from './utils/sleep'; diff --git a/src/sdk/internal/utils/base64.ts b/src/sdk/internal/utils/base64.ts new file mode 100644 index 0000000..44d5e8a --- /dev/null +++ b/src/sdk/internal/utils/base64.ts @@ -0,0 +1,40 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { DedalusError } from '../../core/error'; +import { encodeUTF8 } from './bytes'; + +export const toBase64 = (data: string | Uint8Array | null | undefined): string => { + if (!data) return ''; + + if (typeof (globalThis as any).Buffer !== 'undefined') { + return (globalThis as any).Buffer.from(data).toString('base64'); + } + + if (typeof data === 'string') { + data = encodeUTF8(data); + } + + if (typeof btoa !== 'undefined') { + return btoa(String.fromCharCode.apply(null, data as any)); + } + + throw new DedalusError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); +}; + +export const fromBase64 = (str: string): Uint8Array => { + if (typeof (globalThis as any).Buffer !== 'undefined') { + const buf = (globalThis as any).Buffer.from(str, 'base64'); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + if (typeof atob !== 'undefined') { + const bstr = atob(str); + const buf = new Uint8Array(bstr.length); + for (let i = 0; i < bstr.length; i++) { + buf[i] = bstr.charCodeAt(i); + } + return buf; + } + + throw new DedalusError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); +}; diff --git a/src/sdk/internal/utils/bytes.ts b/src/sdk/internal/utils/bytes.ts new file mode 100644 index 0000000..8da627a --- /dev/null +++ b/src/sdk/internal/utils/bytes.ts @@ -0,0 +1,32 @@ +export function concatBytes(buffers: Uint8Array[]): Uint8Array { + let length = 0; + for (const buffer of buffers) { + length += buffer.length; + } + const output = new Uint8Array(length); + let index = 0; + for (const buffer of buffers) { + output.set(buffer, index); + index += buffer.length; + } + + return output; +} + +let encodeUTF8_: (str: string) => Uint8Array; +export function encodeUTF8(str: string) { + let encoder; + return ( + encodeUTF8_ ?? + ((encoder = new (globalThis as any).TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))) + )(str); +} + +let decodeUTF8_: (bytes: Uint8Array) => string; +export function decodeUTF8(bytes: Uint8Array) { + let decoder; + return ( + decodeUTF8_ ?? + ((decoder = new (globalThis as any).TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))) + )(bytes); +} diff --git a/src/sdk/internal/utils/env.ts b/src/sdk/internal/utils/env.ts new file mode 100644 index 0000000..6212168 --- /dev/null +++ b/src/sdk/internal/utils/env.ts @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** + * Read an environment variable. + * + * Trims beginning and trailing whitespace. + * + * Will return undefined if the environment variable doesn't exist or cannot be accessed. + */ +export const readEnv = (env: string): string | undefined => { + if (typeof (globalThis as any).process !== 'undefined') { + return (globalThis as any).process.env?.[env]?.trim() || undefined; + } + if (typeof (globalThis as any).Deno !== 'undefined') { + return (globalThis as any).Deno.env?.get?.(env)?.trim() || undefined; + } + return undefined; +}; diff --git a/src/sdk/internal/utils/log.ts b/src/sdk/internal/utils/log.ts new file mode 100644 index 0000000..55da470 --- /dev/null +++ b/src/sdk/internal/utils/log.ts @@ -0,0 +1,128 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { hasOwn } from './values'; +import { type Dedalus } from '../../client'; +import { RequestOptions } from '../request-options'; + +type LogFn = (message: string, ...rest: unknown[]) => void; +export type Logger = { + error: LogFn; + warn: LogFn; + info: LogFn; + debug: LogFn; +}; +export type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'debug'; + +const levelNumbers = { + off: 0, + error: 200, + warn: 300, + info: 400, + debug: 500, +}; + +export const parseLogLevel = ( + maybeLevel: string | undefined, + sourceName: string, + client: Dedalus, +): LogLevel | undefined => { + if (!maybeLevel) { + return undefined; + } + if (hasOwn(levelNumbers, maybeLevel)) { + return maybeLevel; + } + loggerFor(client).warn( + `${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify( + Object.keys(levelNumbers), + )}`, + ); + return undefined; +}; + +function noop() {} + +function makeLogFn(fnLevel: keyof Logger, logger: Logger | undefined, logLevel: LogLevel) { + if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) { + return noop; + } else { + // Don't wrap logger functions, we want the stacktrace intact! + return logger[fnLevel].bind(logger); + } +} + +const noopLogger = { + error: noop, + warn: noop, + info: noop, + debug: noop, +}; + +let cachedLoggers = /* @__PURE__ */ new WeakMap(); + +export function loggerFor(client: Dedalus): Logger { + const logger = client.logger; + const logLevel = client.logLevel ?? 'off'; + if (!logger) { + return noopLogger; + } + + const cachedLogger = cachedLoggers.get(logger); + if (cachedLogger && cachedLogger[0] === logLevel) { + return cachedLogger[1]; + } + + const levelLogger = { + error: makeLogFn('error', logger, logLevel), + warn: makeLogFn('warn', logger, logLevel), + info: makeLogFn('info', logger, logLevel), + debug: makeLogFn('debug', logger, logLevel), + }; + + cachedLoggers.set(logger, [logLevel, levelLogger]); + + return levelLogger; +} + +export const formatRequestDetails = (details: { + options?: RequestOptions | undefined; + headers?: Headers | Record | undefined; + retryOfRequestLogID?: string | undefined; + retryOf?: string | undefined; + url?: string | undefined; + status?: number | undefined; + method?: string | undefined; + durationMs?: number | undefined; + message?: unknown; + body?: unknown; +}) => { + if (details.options) { + details.options = { ...details.options }; + delete details.options['headers']; // redundant + leaks internals + } + if (details.headers) { + details.headers = Object.fromEntries( + (details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map( + ([name, value]) => [ + name, + ( + name.toLowerCase() === 'authorization' || + name.toLowerCase() === 'api-key' || + name.toLowerCase() === 'x-api-key' || + name.toLowerCase() === 'cookie' || + name.toLowerCase() === 'set-cookie' + ) ? + '***' + : value, + ], + ), + ); + } + if ('retryOfRequestLogID' in details) { + if (details.retryOfRequestLogID) { + details.retryOf = details.retryOfRequestLogID; + } + delete details.retryOfRequestLogID; + } + return details; +}; diff --git a/src/sdk/internal/utils/path.ts b/src/sdk/internal/utils/path.ts new file mode 100644 index 0000000..e40e356 --- /dev/null +++ b/src/sdk/internal/utils/path.ts @@ -0,0 +1,122 @@ +import { DedalusError } from '../../core/error'; + +/** + * Percent-encode everything that isn't safe to have in a path without encoding safe chars. + * + * Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3: + * > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + * > pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ +export function encodeURIPath(str: string) { + return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent); +} + +/** + * Like {@link encodeURIPath} but leaves `/` unescaped, implementing RFC 6570 reserved expansion + * (`{+var}`) for path-like parameters such as a file path. The slash is part of the route shape for + * these params — `docs/example.txt` must stay nested under `.../files/docs/example.txt` rather than + * collapsing to a single `docs%2Fexample.txt` segment that points at a different backend path. Each + * `/`-separated segment is still run through {@link encodeURIPath}, so every other unsafe character is + * percent-encoded with the exact same rules, and the tag function below still rejects `.`/`..` segments, + * so a reserved value cannot smuggle in path traversal. + */ +export function encodeURIPathReserved(str: string) { + return str.split('/').map(encodeURIPath).join('/'); +} + +/** + * Wrapper marking a path-parameter value for reserved expansion. The {@link createPathTagFunction} tag + * detects this instance and encodes its value with {@link encodeURIPathReserved} (slash-preserving) + * instead of the default per-segment encoder, while leaving all other interpolated params untouched. + */ +class ReservedPathParam { + constructor(readonly value: string) {} + toString(): string { + return this.value; + } +} + +/** + * Marks a value so the `path` tag preserves `/` when encoding it (RFC 6570 reserved expansion). Used by + * generated request paths for parameters the spec flags with `allowReserved` (e.g. file paths). + */ +export const reserved = (value: unknown): ReservedPathParam => new ReservedPathParam('' + value); + +const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null)); + +export const createPathTagFunction = (pathEncoder = encodeURIPath) => + function path(statics: readonly string[], ...params: readonly unknown[]): string { + // If there are no params, no processing is needed. + if (statics.length === 1) return statics[0]!; + + let postPath = false; + const invalidSegments = []; + const path = statics.reduce((previousValue, currentValue, index) => { + if (/[?#]/.test(currentValue)) { + postPath = true; + } + const param = params[index]; + // Reserved params keep `/` (file-path-like values); everything else uses the default encoder. + const isReserved = param instanceof ReservedPathParam; + const value = isReserved ? param.value : param; + let encoded = (postPath ? encodeURIComponent : isReserved ? encodeURIPathReserved : pathEncoder)('' + value); + if ( + index !== params.length && + (value == null || + (typeof value === 'object' && + // handle values from other realms + value.toString === + Object.getPrototypeOf(Object.getPrototypeOf((value as any).hasOwnProperty ?? EMPTY) ?? EMPTY) + ?.toString)) + ) { + encoded = value + ''; + invalidSegments.push({ + start: previousValue.length + currentValue.length, + length: encoded.length, + error: `Value of type ${Object.prototype.toString + .call(value) + .slice(8, -1)} is not a valid path parameter`, + }); + } + return previousValue + currentValue + (index === params.length ? '' : encoded); + }, ''); + + const pathOnly = path.split(/[?#]/, 1)[0]!; + const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi; + let match; + + // Find all invalid segments + while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) { + invalidSegments.push({ + start: match.index, + length: match[0].length, + error: `Value "${match[0]}" can\'t be safely passed as a path parameter`, + }); + } + + invalidSegments.sort((a, b) => a.start - b.start); + + if (invalidSegments.length > 0) { + let lastEnd = 0; + const underline = invalidSegments.reduce((acc, segment) => { + const spaces = ' '.repeat(segment.start - lastEnd); + const arrows = '^'.repeat(segment.length); + lastEnd = segment.start + segment.length; + return acc + spaces + arrows; + }, ''); + + throw new DedalusError( + `Path parameters result in path with invalid segments:\n${invalidSegments + .map((e) => e.error) + .join('\n')}\n${path}\n${underline}`, + ); + } + + return path; + }; + +/** + * URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced. + */ +export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath); diff --git a/src/sdk/internal/utils/sleep.ts b/src/sdk/internal/utils/sleep.ts new file mode 100644 index 0000000..f8d0a12 --- /dev/null +++ b/src/sdk/internal/utils/sleep.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/sdk/internal/utils/uuid.ts b/src/sdk/internal/utils/uuid.ts new file mode 100644 index 0000000..94878fa --- /dev/null +++ b/src/sdk/internal/utils/uuid.ts @@ -0,0 +1,17 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** + * https://stackoverflow.com/a/2117523 + */ +export let uuid4 = function () { + const { crypto } = globalThis as any; + if (crypto?.randomUUID) { + uuid4 = crypto.randomUUID.bind(crypto); + return crypto.randomUUID(); + } + const u8 = new Uint8Array(1); + const randomByte = crypto ? () => crypto.getRandomValues(u8)[0]! : () => (Math.random() * 0xff) & 0xff; + return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => + (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16), + ); +}; diff --git a/src/sdk/internal/utils/values.ts b/src/sdk/internal/utils/values.ts new file mode 100644 index 0000000..6361499 --- /dev/null +++ b/src/sdk/internal/utils/values.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { DedalusError } from '../../core/error'; + +// https://url.spec.whatwg.org/#url-scheme-string +const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; + +export const isAbsoluteURL = (url: string): boolean => { + return startsWithSchemeRegexp.test(url); +}; + +export let isArray = (val: unknown): val is unknown[] => ((isArray = Array.isArray), isArray(val)); +export let isReadonlyArray = isArray as (val: unknown) => val is readonly unknown[]; + +/** Returns an object if the given value isn't an object, otherwise returns as-is */ +export function maybeObj(x: unknown): Record { + if (typeof x !== 'object') { + return {}; + } + + return (x ?? {}) as Record; +} + +// https://stackoverflow.com/a/34491287 +export function isEmptyObj(obj: Object | null | undefined): boolean { + if (!obj) return true; + for (const _k in obj) return false; + return true; +} + +// https://eslint.org/docs/latest/rules/no-prototype-builtins +export function hasOwn(obj: T, key: PropertyKey): key is keyof T { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +export function isObj(obj: unknown): obj is Record { + return obj != null && typeof obj === 'object' && !Array.isArray(obj); +} + +export const ensurePresent = (value: T | null | undefined): T => { + if (value == null) { + throw new DedalusError(`Expected a value to be given but received ${value} instead.`); + } + + return value; +}; + +export const validatePositiveInteger = (name: string, n: unknown): number => { + if (typeof n !== 'number' || !Number.isInteger(n)) { + throw new DedalusError(`${name} must be an integer`); + } + if (n < 0) { + throw new DedalusError(`${name} must be a positive integer`); + } + return n; +}; + +export const coerceInteger = (value: unknown): number => { + if (typeof value === 'number') return Math.round(value); + if (typeof value === 'string') return parseInt(value, 10); + + throw new DedalusError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceFloat = (value: unknown): number => { + if (typeof value === 'number') return value; + if (typeof value === 'string') return parseFloat(value); + + throw new DedalusError(`Could not coerce ${value} (type: ${typeof value}) into a number`); +}; + +export const coerceBoolean = (value: unknown): boolean => { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') return value === 'true'; + return Boolean(value); +}; + +export const maybeCoerceInteger = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceInteger(value); +}; + +export const maybeCoerceFloat = (value: unknown): number | undefined => { + if (value == null) { + return undefined; + } + return coerceFloat(value); +}; + +export const maybeCoerceBoolean = (value: unknown): boolean | undefined => { + if (value == null) { + return undefined; + } + return coerceBoolean(value); +}; + +export const safeJSON = (text: string) => { + try { + return JSON.parse(text); + } catch (err) { + return undefined; + } +}; diff --git a/src/sdk/internal/ws-adapter-browser.ts b/src/sdk/internal/ws-adapter-browser.ts new file mode 100644 index 0000000..1a1aa4c --- /dev/null +++ b/src/sdk/internal/ws-adapter-browser.ts @@ -0,0 +1,123 @@ +import type { WebSocketLike } from './ws-adapter'; + +/** A generic event listener callback. */ +type Listener = (...args: any[]) => void; + +/** A DOM-style event handler passed to addEventListener/removeEventListener. */ +type DOMEventHandler = (ev: any) => void; + +// Minimal browser API type declarations. +declare class WebSocket { + readonly readyState: number; + binaryType: string; + send(data: string | ArrayBufferLike | ArrayBufferView): void; + close(code?: number, reason?: string): void; + addEventListener(type: string, listener: DOMEventHandler): void; + removeEventListener(type: string, listener: DOMEventHandler): void; +} + +interface MessageEvent { + data: any; +} + +interface CloseEvent { + code: number; + reason: string; +} + +export class BrowserWebSocket implements WebSocketLike { + private _ws: WebSocket; + private _listenerMap = new Map>(); + + constructor(ws: WebSocket) { + this._ws = ws; + this._ws.binaryType = 'arraybuffer'; + } + + /** The underlying platform-specific socket. Code that accesses this will not be isomorphic across server and browser environments. */ + get platformSocket(): WebSocket { + return this._ws; + } + + get readyState(): number { + return this._ws.readyState; + } + + send(data: string | ArrayBufferLike | ArrayBufferView): void { + this._ws.send(data); + } + + close(code?: number, reason?: string): void { + this._ws.close(code, reason); + } + + on(event: string, listener: Listener): void { + const wrapped = this._wrapListener(event, listener); + this._listenersFor(event).set(listener, wrapped); + this._ws.addEventListener(event, wrapped); + } + + off(event: string, listener: Listener): void { + const byListener = this._listenerMap.get(event); + if (!byListener) return; + const wrapped = byListener.get(listener); + if (wrapped) { + byListener.delete(listener); + this._ws.removeEventListener(event, wrapped); + } + } + + once(event: string, listener: Listener): void { + const onceListener: Listener = (...args) => { + this.off(event, listener); + listener(...args); + }; + const wrapped = this._wrapListener(event, onceListener); + this._listenersFor(event).set(listener, wrapped); + this._ws.addEventListener(event, wrapped); + } + + private _listenersFor(event: string): Map { + let map = this._listenerMap.get(event); + if (!map) { + map = new Map(); + this._listenerMap.set(event, map); + } + return map; + } + + /** + * Converts browser event objects to positional arguments matching the + * {@link WebSocketLike} interface. + */ + private _wrapListener(event: string, listener: Listener): DOMEventHandler { + switch (event) { + case 'message': + return (ev: MessageEvent) => { + const isBinary = typeof ev.data !== 'string'; + listener(ev.data, isBinary); + }; + + case 'close': + return (ev: CloseEvent) => { + listener(ev.code, ev.reason); + }; + + case 'error': + return (ev: any) => { + // Some environments provide an ErrorEvent with a `.message`; + // fall back to a generic message when the event carries nothing. + const message = ev?.message || ev?.error?.message || 'WebSocket error'; + const err = new Error(message); + if (ev?.error) { + (err as any).cause = ev.error; + } + listener(err); + }; + + case 'open': + default: + return listener as DOMEventHandler; + } + } +} diff --git a/src/sdk/internal/ws-adapter-node.ts b/src/sdk/internal/ws-adapter-node.ts new file mode 100644 index 0000000..c8632aa --- /dev/null +++ b/src/sdk/internal/ws-adapter-node.ts @@ -0,0 +1,105 @@ +import type * as WS from 'ws'; +import type { WebSocketLike } from './ws-adapter'; + +/** A generic event listener callback. */ +type Listener = (...args: any[]) => void; + +export class NodeWebSocket implements WebSocketLike { + private _ws: WS.WebSocket; + + /** Maps `(event, originalListener)` -> wrapped listener for correct `off()` removal. */ + private _listenerMap = new Map>(); + + constructor(ws: WS.WebSocket) { + this._ws = ws; + } + + /** The underlying platform-specific socket. Code that accesses this will not be isomorphic across server and browser environments. */ + get platformSocket(): WS.WebSocket { + return this._ws; + } + + get readyState(): number { + return this._ws.readyState; + } + + send(data: string | ArrayBufferLike | ArrayBufferView): void { + this._ws.send(data); + } + + close(code?: number, reason?: string): void { + this._ws.close(code, reason); + } + + on(event: string, listener: Listener): void { + const wrapped = this._wrapListener(event, listener); + this._listenersFor(event).set(listener, wrapped); + this._ws.on(event, wrapped); + } + + off(event: string, listener: Listener): void { + const byListener = this._listenerMap.get(event); + if (!byListener) return; + const wrapped = byListener.get(listener); + if (wrapped) { + byListener.delete(listener); + this._ws.removeListener(event, wrapped); + } + } + + once(event: string, listener: Listener): void { + const onceListener: Listener = (...args) => { + this.off(event, listener); + listener(...args); + }; + const wrapped = this._wrapListener(event, onceListener); + this._listenersFor(event).set(listener, wrapped); + this._ws.on(event, wrapped); + } + + private _listenersFor(event: string): Map { + let map = this._listenerMap.get(event); + if (!map) { + map = new Map(); + this._listenerMap.set(event, map); + } + return map; + } + + /** + * Normalizes `ws` message payloads: text frames become strings, + * binary frames stay as `Buffer`, and fragmented frames are merged. + */ + private static _normalizeMessageData( + data: Buffer | ArrayBuffer | Buffer[], + isBinary: boolean, + ): string | Buffer { + if (!isBinary) { + if (Array.isArray(data)) return Buffer.concat(data).toString(); + if (data instanceof ArrayBuffer) return Buffer.from(data).toString(); + return data.toString(); + } + + if (Array.isArray(data)) return Buffer.concat(data); + if (data instanceof ArrayBuffer) return Buffer.from(data); + return data; + } + + private _wrapListener(event: string, listener: Listener): Listener { + switch (event) { + case 'message': + return (data: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) => { + listener(NodeWebSocket._normalizeMessageData(data, isBinary), isBinary); + }; + + case 'close': + return (code: number, reason: Buffer) => { + listener(code, reason.toString()); + }; + + // 'open' and 'error' pass through unchanged + default: + return listener; + } + } +} diff --git a/src/sdk/internal/ws-adapter.ts b/src/sdk/internal/ws-adapter.ts new file mode 100644 index 0000000..579d1f9 --- /dev/null +++ b/src/sdk/internal/ws-adapter.ts @@ -0,0 +1,30 @@ +/** + * Normalized WebSocket interface that abstracts over the `ws` package (Node.js) + * and the native WebSocket API (browser). + */ +export interface WebSocketLike { + readonly readyState: number; + + send(data: string | ArrayBufferLike | ArrayBufferView): void; + close(code?: number, reason?: string): void; + + on(event: 'open', listener: () => void): void; + on( + event: 'message', + listener: (data: string | ArrayBuffer | ArrayBufferView, isBinary: boolean) => void, + ): void; + on(event: 'close', listener: (code: number, reason: string) => void): void; + on(event: 'error', listener: (err: Error) => void): void; + on(event: string, listener: (...args: any[]) => void): void; + + off(event: string, listener: (...args: any[]) => void): void; + once(event: string, listener: (...args: any[]) => void): void; +} + +/** Standard WebSocket readyState values (RFC 6455). */ +export const ReadyState = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3, +} as const; diff --git a/src/sdk/internal/ws.ts b/src/sdk/internal/ws.ts new file mode 100644 index 0000000..7e17902 --- /dev/null +++ b/src/sdk/internal/ws.ts @@ -0,0 +1,193 @@ +import { concatBytes, encodeUTF8 } from './utils/bytes'; + +/** Reconnection event passed to the `onReconnecting` handler and event listeners. */ +export interface ReconnectingEvent> { + /** Which retry attempt this is (1-based). */ + readonly attempt: number; + /** Total attempts that will be made. */ + readonly maxAttempts: number; + /** Delay in ms before this attempt connects. */ + readonly delay: number; + /** The WebSocket close code that triggered reconnection. */ + readonly closeCode: number; + /** The current query parameters. */ + readonly parameters: (Parameters & Record) | undefined; +} + +/** + * Optional overrides returned from the `onReconnecting` handler + * to customize the next reconnection attempt. + */ +export type ReconnectingOverrides> = + | { + /** + * If provided, assigns the query parameters for the next connection. + * Set to `undefined` to clear all query parameters. + */ + parameters?: (Parameters & Record) | undefined; + } + | { + /** + * If set, will stop attempting to reconnect. + */ + abort: true; + }; + +/** + * Raw data types that can be sent over a WebSocket without serialization. + */ +export type RawWebSocketData = string | ArrayBufferLike | ArrayBufferView | ArrayBufferView[]; + +export type UnsentMessage = { type: 'message'; message: T } | { type: 'raw'; data: RawWebSocketData }; + +type QueueEntry = + | { kind: 'json'; data: string; byteLength: number } + | { kind: 'raw'; data: RawWebSocketData; byteLength: number }; + +function toUint8Array(view: ArrayBufferView): Uint8Array { + if (view instanceof Uint8Array) return view; + return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); +} + +/** + * Flatten `ArrayBufferView[]` fragments into a single `Uint8Array` so that + * `ws.send()` transmits the correct bytes. + */ +export function flattenRawData(data: RawWebSocketData): Exclude { + if (Array.isArray(data)) return concatBytes(data.map(toUint8Array)); + return data; +} + +function snapshotRawData(data: RawWebSocketData): Exclude { + if (typeof data === 'string') return data; + if (Array.isArray(data)) return concatBytes(data.map(toUint8Array)); + if (ArrayBuffer.isView(data)) { + const copy = new Uint8Array(data.byteLength); + copy.set(toUint8Array(data)); + return copy; + } + return data.slice(0); +} + +function rawByteLength(data: RawWebSocketData): number { + if (typeof data === 'string') return encodeUTF8(data).byteLength; + if (Array.isArray(data)) return data.reduce((sum, buf) => sum + buf.byteLength, 0); + if ('byteLength' in data) return data.byteLength; + return 0; +} + +/** + * A bounded queue for outgoing WebSocket messages. JSON messages are + * serialized on enqueue; raw messages are stored as-is. The queue enforces + * a configurable byte-size limit and can return the original messages via + * {@link drain} when the connection permanently closes. + */ +export class SendQueue { + private _queue: QueueEntry[] = []; + private _bytes: number = 0; + private _maxBytes: number; + + constructor(maxBytes: number = 1_048_576) { + this._maxBytes = maxBytes; + } + + /** + * Serialize and enqueue a JSON message. Returns `true` if the message was + * accepted, `false` if it would exceed the byte-size limit. + */ + enqueue(event: T): boolean { + const data = JSON.stringify(event); + const byteLength = encodeUTF8(data).byteLength; + if (this._bytes + byteLength > this._maxBytes && this._queue.length > 0) { + return false; + } + this._queue.push({ kind: 'json', data, byteLength }); + this._bytes += byteLength; + return true; + } + + /** + * Enqueue raw data without serialization. Returns `true` if the data was + * accepted, `false` if it would exceed the byte-size limit. + */ + enqueueRaw(data: RawWebSocketData): boolean { + const snapshot = snapshotRawData(data); + const byteLength = rawByteLength(snapshot); + if (this._bytes + byteLength > this._maxBytes && this._queue.length > 0) { + return false; + } + this._queue.push({ kind: 'raw', data: snapshot, byteLength }); + this._bytes += byteLength; + return true; + } + + /** + * Send every queued message via `send`. If `send` throws, the failing + * message and all subsequent messages are re-queued and the error is + * re-thrown so the caller can report it. + */ + flush(send: (data: RawWebSocketData) => void): void { + const pending = this._queue.splice(0); + this._bytes = 0; + for (let i = 0; i < pending.length; i++) { + try { + send(pending[i]!.data); + } catch (err) { + const remaining = pending.slice(i); + this._queue = remaining.concat(this._queue); + this._bytes = this._queue.reduce((sum, item) => sum + item.byteLength, 0); + throw err; + } + } + } + + /** + * Drain the queue and return the unsent messages. JSON messages are + * deserialized back to their original form. Resets byte tracking to zero. + */ + drain(): UnsentMessage[] { + const unsent = this._queue.map((entry): UnsentMessage => { + if (entry.kind === 'raw') return { type: 'raw', data: entry.data }; + return { type: 'message', message: JSON.parse(entry.data) as T }; + }); + this._queue = []; + this._bytes = 0; + return unsent; + } +} + +// RFC 6455 §7.4.1 +export function isRecoverableClose(code: number): boolean { + switch (code) { + case 1000: + return false; // Normal closure + case 1001: + return true; // Going away (server shutting down) + case 1002: + return false; // Protocol error + case 1003: + return false; // Unsupported data + case 1005: + return true; // No status code (abnormal) + case 1006: + return true; // Abnormal closure (network drop) + case 1007: + return false; // Invalid payload + case 1008: + return false; // Policy violation + case 1009: + return false; // Message too big + case 1010: + return false; // Missing extension + case 1011: + return true; // Internal server error + case 1012: + return true; // Service restart + case 1013: + return true; // Try again later + case 1015: + return true; // TLS handshake failure + default: + return false; + } +} diff --git a/src/sdk/resource.ts b/src/sdk/resource.ts new file mode 100644 index 0000000..93968f2 --- /dev/null +++ b/src/sdk/resource.ts @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import type { Dedalus } from './client'; + +export class APIResource { + protected _client: Dedalus; + + constructor(client: Dedalus) { + this._client = client; + } +} diff --git a/src/sdk/resources/index.ts b/src/sdk/resources/index.ts new file mode 100644 index 0000000..bbaaf18 --- /dev/null +++ b/src/sdk/resources/index.ts @@ -0,0 +1,8 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { MachineLifecycle } from "./machine-lifecycle/machine-lifecycle"; +export type { CreateMachineRequest, UpdateMachineRequest, CreateExecutionRequest, CreatePreviewRequest, CreateSSHSessionRequest, CreateTerminalRequest, MachineLifecycleListParams, MachineLifecycleListResponse, MachineLifecycleCreateParams, MachineLifecycleCreateResponse, MachineLifecycleDeleteParams, MachineLifecycleDeleteResponse, MachineLifecycleRetrieveParams, MachineLifecycleRetrieveResponse, MachineLifecyclePatchParams, MachineLifecyclePatchResponse, MachineLifecycleListArtifactsParams, MachineLifecycleListArtifactsResponse, MachineLifecycleDeleteArtifactParams, MachineLifecycleDeleteArtifactResponse, MachineLifecycleRetrieveArtifactParams, MachineLifecycleRetrieveArtifactResponse, MachineLifecycleListExecutionsParams, MachineLifecycleListExecutionsResponse, MachineLifecycleCreateExecutionParams, MachineLifecycleCreateExecutionResponse, MachineLifecycleDeleteExecutionParams, MachineLifecycleDeleteExecutionResponse, MachineLifecycleRetrieveExecutionParams, MachineLifecycleRetrieveExecutionResponse, MachineLifecycleListExecutionEventsParams, MachineLifecycleListExecutionEventsResponse, MachineLifecycleListExecutionOutputParams, MachineLifecycleListExecutionOutputResponse, MachineLifecycleListPreviewsParams, MachineLifecycleListPreviewsResponse, MachineLifecycleCreatePreviewParams, MachineLifecycleCreatePreviewResponse, MachineLifecycleDeletePreviewParams, MachineLifecycleDeletePreviewResponse, MachineLifecycleRetrievePreviewParams, MachineLifecycleRetrievePreviewResponse, MachineLifecycleSleepParams, MachineLifecycleSleepResponse, MachineLifecycleListSSHSessionsParams, MachineLifecycleListSSHSessionsResponse, MachineLifecycleCreateSSHSessionParams, MachineLifecycleCreateSSHSessionResponse, MachineLifecycleDeleteSSHSessionParams, MachineLifecycleDeleteSSHSessionResponse, MachineLifecycleRetrieveSSHSessionParams, MachineLifecycleRetrieveSSHSessionResponse, MachineLifecycleWatchStatusParams, MachineLifecycleWatchStatusResponse, MachineLifecycleListTerminalsParams, MachineLifecycleListTerminalsResponse, MachineLifecycleCreateTerminalParams, MachineLifecycleCreateTerminalResponse, MachineLifecycleDeleteTerminalParams, MachineLifecycleDeleteTerminalResponse, MachineLifecycleRetrieveTerminalParams, MachineLifecycleRetrieveTerminalResponse, MachineLifecycleConnectTerminalParams, MachineLifecycleWakeParams, MachineLifecycleWakeResponse } from "./machine-lifecycle/machine-lifecycle"; +export { MachineLifecycle as MachineLifecycleResource } from "./machine-lifecycle/machine-lifecycle"; +export { Usage } from "./usage/usage"; +export type { UsageListParams, UsageListResponse } from "./usage/usage"; +export { Usage as UsageResource } from "./usage/usage"; diff --git a/src/sdk/resources/machine-lifecycle.ts b/src/sdk/resources/machine-lifecycle.ts new file mode 100644 index 0000000..dd79961 --- /dev/null +++ b/src/sdk/resources/machine-lifecycle.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export * from "./machine-lifecycle/index"; diff --git a/src/sdk/resources/machine-lifecycle/index.ts b/src/sdk/resources/machine-lifecycle/index.ts new file mode 100644 index 0000000..f441339 --- /dev/null +++ b/src/sdk/resources/machine-lifecycle/index.ts @@ -0,0 +1,6 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { MachineLifecycle } from "./machine-lifecycle"; +export type { CreateMachineRequest, UpdateMachineRequest, CreateExecutionRequest, CreatePreviewRequest, CreateSSHSessionRequest, CreateTerminalRequest, MachineLifecycleListParams, MachineLifecycleListResponse, MachineLifecycleCreateParams, MachineLifecycleCreateResponse, MachineLifecycleDeleteParams, MachineLifecycleDeleteResponse, MachineLifecycleRetrieveParams, MachineLifecycleRetrieveResponse, MachineLifecyclePatchParams, MachineLifecyclePatchResponse, MachineLifecycleListArtifactsParams, MachineLifecycleListArtifactsResponse, MachineLifecycleDeleteArtifactParams, MachineLifecycleDeleteArtifactResponse, MachineLifecycleRetrieveArtifactParams, MachineLifecycleRetrieveArtifactResponse, MachineLifecycleListExecutionsParams, MachineLifecycleListExecutionsResponse, MachineLifecycleCreateExecutionParams, MachineLifecycleCreateExecutionResponse, MachineLifecycleDeleteExecutionParams, MachineLifecycleDeleteExecutionResponse, MachineLifecycleRetrieveExecutionParams, MachineLifecycleRetrieveExecutionResponse, MachineLifecycleListExecutionEventsParams, MachineLifecycleListExecutionEventsResponse, MachineLifecycleListExecutionOutputParams, MachineLifecycleListExecutionOutputResponse, MachineLifecycleListPreviewsParams, MachineLifecycleListPreviewsResponse, MachineLifecycleCreatePreviewParams, MachineLifecycleCreatePreviewResponse, MachineLifecycleDeletePreviewParams, MachineLifecycleDeletePreviewResponse, MachineLifecycleRetrievePreviewParams, MachineLifecycleRetrievePreviewResponse, MachineLifecycleSleepParams, MachineLifecycleSleepResponse, MachineLifecycleListSSHSessionsParams, MachineLifecycleListSSHSessionsResponse, MachineLifecycleCreateSSHSessionParams, MachineLifecycleCreateSSHSessionResponse, MachineLifecycleDeleteSSHSessionParams, MachineLifecycleDeleteSSHSessionResponse, MachineLifecycleRetrieveSSHSessionParams, MachineLifecycleRetrieveSSHSessionResponse, MachineLifecycleWatchStatusParams, MachineLifecycleWatchStatusResponse, MachineLifecycleListTerminalsParams, MachineLifecycleListTerminalsResponse, MachineLifecycleCreateTerminalParams, MachineLifecycleCreateTerminalResponse, MachineLifecycleDeleteTerminalParams, MachineLifecycleDeleteTerminalResponse, MachineLifecycleRetrieveTerminalParams, MachineLifecycleRetrieveTerminalResponse, MachineLifecycleConnectTerminalParams, MachineLifecycleWakeParams, MachineLifecycleWakeResponse } from "./machine-lifecycle"; +export { MachineLifecycleWS, type MachineLifecycleWSClientOptions } from './ws'; +export type { MachineLifecycleWSReconnectOptions, MachineLifecycleWSParameters } from './ws-base'; diff --git a/src/sdk/resources/machine-lifecycle/internal-base.ts b/src/sdk/resources/machine-lifecycle/internal-base.ts new file mode 100644 index 0000000..902230c --- /dev/null +++ b/src/sdk/resources/machine-lifecycle/internal-base.ts @@ -0,0 +1,105 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { path as __scalarPath } from "../../internal/utils/path"; +import * as MachineLifecycleAPI from "./machine-lifecycle"; +import { Dedalus } from "../../client"; +import { EventEmitter, type EventParameters } from "../../core/EventEmitter"; +import { DedalusError } from "../../error"; +import type { RawWebSocketData, ReconnectingEvent, UnsentMessage } from "../../internal/ws"; +import type { MachineLifecycleWSParameters } from "./ws-base"; + +type EventTypeOf = T extends { type?: infer EventType } ? EventType : never; +type MachineLifecycleWSErrorEvent = Extract; + +export type MachineLifecycleWSStreamMessage = + | { type: 'connecting' | 'open' | 'closing' } + | { type: 'close'; code: number; reason: string; unsent: UnsentMessage[] } + | { type: 'reconnecting'; reconnect: ReconnectingEvent } + | { type: 'reconnected' } + | { type: 'message'; message: unknown } + | { type: 'raw'; data: RawWebSocketData } + | { type: 'error'; error: WebSocketError }; + +export class WebSocketError extends DedalusError { + error?: MachineLifecycleWSErrorEvent | undefined; + + constructor(message: string, event: MachineLifecycleWSErrorEvent | null) { + super(message); + this.error = event ?? undefined; + } +} + +type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; + +type WebSocketEvents = Simplify< + { + event: (event: unknown) => void; + raw: (data: RawWebSocketData) => void; + error: (error: WebSocketError) => void; + close: (code: number, reason: string, unsent: UnsentMessage[]) => void; + reconnecting: (event: ReconnectingEvent) => void; + reconnected: () => void; + } & { + [EventType in Exclude>, 'error'> & string]: ( + event: Extract, + ) => unknown; + } +>; + +export abstract class MachineLifecycleWSEmitter extends EventEmitter { + /** Send an event to the API. */ + abstract send(event: unknown): void; + + /** Send raw data over the WebSocket without JSON serialization. */ + abstract sendRaw(data: RawWebSocketData): void; + + /** Close the WebSocket connection. */ + abstract close(props?: { code: number; reason: string }): void; + + protected _onError(event: null, message: string, cause: unknown): void; + protected _onError(event: MachineLifecycleWSErrorEvent, message?: string | undefined): void; + protected _onError(event: MachineLifecycleWSErrorEvent | null, message?: string | undefined, cause?: unknown): void { + message = message ?? safeJSONStringify(event) ?? 'unknown error'; + + if (!this._hasListener('error')) { + const error = new WebSocketError( + message + + "\n\nTo resolve these unhandled rejection errors you should bind an `error` callback, e.g. `ws.on('error', (error) => ...)` ", + event, + ); + (error as Error & { cause?: unknown }).cause = cause; + Promise.reject(error); + return; + } + + const error = new WebSocketError(message, event); + (error as Error & { cause?: unknown }).cause = cause; + this._emit('error', error); + } + + public _emit(event: Event, ...args: EventParameters): void { + super._emit(event, ...args); + } +} + +export function buildURL(client: Dedalus, parameters: Record): URL { + const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...query } = parameters; + const endpoint = __scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}/stream`; + const url = new URL(client.buildURL(endpoint, query, undefined)); + url.protocol = url.protocol === 'http:' || url.protocol === 'ws:' ? 'ws:' : 'wss:'; + return url; +} + +export function parameterHeaders(parameters: Record): Record { + const headers: Record = {}; + if (parameters["X-Dedalus-Org-Id"] !== undefined) headers["X-Dedalus-Org-Id"] = String(parameters["X-Dedalus-Org-Id"]); + return headers; +} + +function safeJSONStringify(value: unknown): string | null { + try { + return JSON.stringify(value); + } catch { + return null; + } +} diff --git a/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts b/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts new file mode 100644 index 0000000..4b26964 --- /dev/null +++ b/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts @@ -0,0 +1,2616 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { APIResource } from "../../resource"; +import { APIPromise } from "../../api-promise"; +import { Stream } from "../../core/streaming"; +import type { RequestOptions } from "../../internal/request-options"; +import { buildHeaders } from "../../internal/headers"; +import { path as __scalarPath } from "../../internal/utils/path"; +import { MachineLifecycleWS, type MachineLifecycleWSClientOptions } from "./ws"; + +export class MachineLifecycle extends APIResource { + /** + * List machines + * + * @param {MachineLifecycleListParams} [params] - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const list = await client.machineLifecycle.list(); + * ``` + */ + list(params: MachineLifecycleListParams | null | undefined = {}, options?: RequestOptions): APIPromise { + const { limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get("/v1/machines", { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Create machine + * + * @param {MachineLifecycleCreateParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} Create converged inline + * + * @example + * ```ts + * const create = await client.machineLifecycle.create({ + * memory_mib: 0, + * storage_gib: 0, + * vcpu: 0, + * }); + * ``` + */ + create(params: MachineLifecycleCreateParams, options?: RequestOptions): APIPromise { + const { "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.post("/v1/machines", { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Destroy machine + * + * @param {MachineLifecycleDeleteParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const delete_ = await client.machineLifecycle.delete({ + * machine_id: "machineID", + * }); + * ``` + */ + delete(params: MachineLifecycleDeleteParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get machine + * + * @param {MachineLifecycleRetrieveParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrieve = await client.machineLifecycle.retrieve({ + * machine_id: "machineID", + * }); + * ``` + */ + retrieve(params: MachineLifecycleRetrieveParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Update machine + * + * @param {MachineLifecyclePatchParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const patch = await client.machineLifecycle.patch({ + * machine_id: "machineID", + * }); + * ``` + */ + patch(params: MachineLifecyclePatchParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.patch(__scalarPath`/v1/machines/${machine_id}`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * List artifacts + * + * @param {MachineLifecycleListArtifactsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listArtifacts = await client.machineLifecycle.listArtifacts({ + * machine_id: "machineID", + * }); + * ``` + */ + listArtifacts(params: MachineLifecycleListArtifactsParams, options?: RequestOptions): APIPromise { + const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/artifacts`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Delete artifact + * + * @param {MachineLifecycleDeleteArtifactParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const deleteArtifact = await client.machineLifecycle.deleteArtifact({ + * machine_id: "machineID", + * artifact_id: "artifactID", + * }); + * ``` + */ + deleteArtifact(params: MachineLifecycleDeleteArtifactParams, options?: RequestOptions): APIPromise { + const { machine_id, artifact_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}/artifacts/${artifact_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get artifact + * + * @param {MachineLifecycleRetrieveArtifactParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrieveArtifact = await client.machineLifecycle.retrieveArtifact({ + * machine_id: "machineID", + * artifact_id: "artifactID", + * }); + * ``` + */ + retrieveArtifact(params: MachineLifecycleRetrieveArtifactParams, options?: RequestOptions): APIPromise { + const { machine_id, artifact_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/artifacts/${artifact_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * List executions + * + * @param {MachineLifecycleListExecutionsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listExecutions = await client.machineLifecycle.listExecutions({ + * machine_id: "machineID", + * }); + * ``` + */ + listExecutions(params: MachineLifecycleListExecutionsParams, options?: RequestOptions): APIPromise { + const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Create execution + * + * @param {MachineLifecycleCreateExecutionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const createExecution = await client.machineLifecycle.createExecution({ + * machine_id: "machineID", + * command: [], + * }); + * ``` + */ + createExecution(params: MachineLifecycleCreateExecutionParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/executions`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Delete execution + * + * @param {MachineLifecycleDeleteExecutionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const deleteExecution = await client.machineLifecycle.deleteExecution({ + * machine_id: "machineID", + * execution_id: "executionID", + * }); + * ``` + */ + deleteExecution(params: MachineLifecycleDeleteExecutionParams, options?: RequestOptions): APIPromise { + const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get execution + * + * @param {MachineLifecycleRetrieveExecutionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrieveExecution = await client.machineLifecycle.retrieveExecution({ + * machine_id: "machineID", + * execution_id: "executionID", + * }); + * ``` + */ + retrieveExecution(params: MachineLifecycleRetrieveExecutionParams, options?: RequestOptions): APIPromise { + const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * List execution events + * + * @param {MachineLifecycleListExecutionEventsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listExecutionEvents = await client.machineLifecycle.listExecutionEvents({ + * machine_id: "machineID", + * execution_id: "executionID", + * }); + * ``` + */ + listExecutionEvents(params: MachineLifecycleListExecutionEventsParams, options?: RequestOptions): APIPromise { + const { machine_id, execution_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}/events`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get execution output + * + * @param {MachineLifecycleListExecutionOutputParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listExecutionOutput = await client.machineLifecycle.listExecutionOutput({ + * machine_id: "machineID", + * execution_id: "executionID", + * }); + * ``` + */ + listExecutionOutput(params: MachineLifecycleListExecutionOutputParams, options?: RequestOptions): APIPromise { + const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}/output`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * List previews + * + * @param {MachineLifecycleListPreviewsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listPreviews = await client.machineLifecycle.listPreviews({ + * machine_id: "machineID", + * }); + * ``` + */ + listPreviews(params: MachineLifecycleListPreviewsParams, options?: RequestOptions): APIPromise { + const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/previews`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Create preview + * + * @param {MachineLifecycleCreatePreviewParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const createPreview = await client.machineLifecycle.createPreview({ + * machine_id: "machineID", + * port: 0, + * }); + * ``` + */ + createPreview(params: MachineLifecycleCreatePreviewParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/previews`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Delete preview + * + * @param {MachineLifecycleDeletePreviewParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const deletePreview = await client.machineLifecycle.deletePreview({ + * machine_id: "machineID", + * preview_id: "previewID", + * }); + * ``` + */ + deletePreview(params: MachineLifecycleDeletePreviewParams, options?: RequestOptions): APIPromise { + const { machine_id, preview_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}/previews/${preview_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get preview + * + * @param {MachineLifecycleRetrievePreviewParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrievePreview = await client.machineLifecycle.retrievePreview({ + * machine_id: "machineID", + * preview_id: "previewID", + * }); + * ``` + */ + retrievePreview(params: MachineLifecycleRetrievePreviewParams, options?: RequestOptions): APIPromise { + const { machine_id, preview_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/previews/${preview_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Sleep a running machine + * + * @param {MachineLifecycleSleepParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const sleep = await client.machineLifecycle.sleep({ + * machine_id: "machineID", + * }); + * ``` + */ + sleep(params: MachineLifecycleSleepParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/sleep`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * List SSH sessions + * + * @param {MachineLifecycleListSSHSessionsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listSSHSessions = await client.machineLifecycle.listSSHSessions({ + * machine_id: "machineID", + * }); + * ``` + */ + listSSHSessions(params: MachineLifecycleListSSHSessionsParams, options?: RequestOptions): APIPromise { + const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/ssh`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Create SSH session + * + * @param {MachineLifecycleCreateSSHSessionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const createSSHSession = await client.machineLifecycle.createSSHSession({ + * machine_id: "machineID", + * public_key: "", + * }); + * ``` + */ + createSSHSession(params: MachineLifecycleCreateSSHSessionParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/ssh`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Delete SSH session + * + * @param {MachineLifecycleDeleteSSHSessionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const deleteSSHSession = await client.machineLifecycle.deleteSSHSession({ + * machine_id: "machineID", + * session_id: "sessionID", + * }); + * ``` + */ + deleteSSHSession(params: MachineLifecycleDeleteSSHSessionParams, options?: RequestOptions): APIPromise { + const { machine_id, session_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}/ssh/${session_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get SSH session + * + * @param {MachineLifecycleRetrieveSSHSessionParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrieveSSHSession = await client.machineLifecycle.retrieveSSHSession({ + * machine_id: "machineID", + * session_id: "sessionID", + * }); + * ``` + */ + retrieveSSHSession(params: MachineLifecycleRetrieveSSHSessionParams, options?: RequestOptions): APIPromise { + const { machine_id, session_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/ssh/${session_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state. + * + * @param {MachineLifecycleWatchStatusParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise>} Server-Sent Event stream (`text/event-stream`) of machine lifecycle updates. + * + * @example + * ```ts + * const stream = await client.machineLifecycle.watchStatus({ + * machine_id: "machineID", + * }); + * for await (const event of stream) { + * console.log(event); + * } + * ``` + */ + watchStatus(params: MachineLifecycleWatchStatusParams, options?: RequestOptions): APIPromise> { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, "Last-Event-ID": lastEventID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/status/stream`, { ...options, headers: buildHeaders([{ Accept: "text/event-stream", ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}), ...(lastEventID !== undefined ? { "Last-Event-ID": lastEventID } : {}) }, options?.headers]), stream: true }); + } + + /** + * List terminals + * + * @param {MachineLifecycleListTerminalsParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listTerminals = await client.machineLifecycle.listTerminals({ + * machine_id: "machineID", + * }); + * ``` + */ + listTerminals(params: MachineLifecycleListTerminalsParams, options?: RequestOptions): APIPromise { + const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/terminals`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Create terminal + * + * @param {MachineLifecycleCreateTerminalParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const createTerminal = await client.machineLifecycle.createTerminal({ + * machine_id: "machineID", + * height: 0, + * width: 0, + * }); + * ``` + */ + createTerminal(params: MachineLifecycleCreateTerminalParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/terminals`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Delete terminal + * + * @param {MachineLifecycleDeleteTerminalParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const deleteTerminal = await client.machineLifecycle.deleteTerminal({ + * machine_id: "machineID", + * terminal_id: "terminalID", + * }); + * ``` + */ + deleteTerminal(params: MachineLifecycleDeleteTerminalParams, options?: RequestOptions): APIPromise { + const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.delete(__scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Get terminal + * + * @param {MachineLifecycleRetrieveTerminalParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const retrieveTerminal = await client.machineLifecycle.retrieveTerminal({ + * machine_id: "machineID", + * terminal_id: "terminalID", + * }); + * ``` + */ + retrieveTerminal(params: MachineLifecycleRetrieveTerminalParams, options?: RequestOptions): APIPromise { + const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.get(__scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } + + /** + * Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields. + * + * @param {MachineLifecycleConnectTerminalParams} params - The parameters to send with the request. + * @param {MachineLifecycleWSClientOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {MachineLifecycleWS} Switching Protocols to WebSocket + * + * @example + * ```ts + * const connection = client.machineLifecycle.connectTerminal({ + * machine_id: "machineID", + * terminal_id: "terminalID", + * }); + * try { + * for await (const message of connection) { + * console.log(message); + * } + * } finally { + * connection.close(); + * } + * ``` + */ + connectTerminal(params: MachineLifecycleConnectTerminalParams, options?: MachineLifecycleWSClientOptions): MachineLifecycleWS { + const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return new MachineLifecycleWS(this._client, { machine_id: machine_id, terminal_id: terminal_id, ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options); + } + + /** + * Wake a sleeping machine + * + * @param {MachineLifecycleWakeParams} params - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const wake = await client.machineLifecycle.wake({ + * machine_id: "machineID", + * }); + * ``` + */ + wake(params: MachineLifecycleWakeParams, options?: RequestOptions): APIPromise { + const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; + return this._client.post(__scalarPath`/v1/machines/${machine_id}/wake`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); + } +} + +export interface CreateMachineRequest { + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + /** + * Storage in GiB. + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; + /** + * Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. + */ + autosleep?: string; +} + +export interface UpdateMachineRequest { + /** + * Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. + */ + autosleep?: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib?: number; + /** + * Storage in GiB. + * @format int64 + */ + storage_gib?: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu?: number; +} + +export interface CreateExecutionRequest { + command: Array | null; + cwd?: string; + env?: Record; + stdin?: string; + /** + * @format int64 + */ + timeout_ms?: number; +} + +export interface CreatePreviewRequest { + /** + * @format int64 + */ + port: number; + protocol?: "http" | "https"; + visibility?: "public" | "private" | "org"; +} + +export interface CreateSSHSessionRequest { + public_key: string; +} + +export interface CreateTerminalRequest { + /** + * @format int64 + */ + height: number; + /** + * @format int64 + */ + width: number; + cwd?: string; + env?: Record; + shell?: string; +} + +export interface MachineLifecycleListParams { + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListResponse { + export interface Item { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + /** + * @format date-time + */ + created_at: string; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: Item.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; + } + + export namespace Item { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } + } +} + +export interface MachineLifecycleCreateParams { + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param: Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. + */ + autosleep?: string; + /** + * Body param: Memory in MiB. + * @format int64 + */ + memory_mib: number; + /** + * Body param: Storage in GiB. + * @format int64 + */ + storage_gib: number; + /** + * Body param: CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export interface MachineLifecycleCreateResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleCreateResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleCreateResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecycleDeleteParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeleteResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleDeleteResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleDeleteResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecycleRetrieveParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrieveResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleRetrieveResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleRetrieveResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecyclePatchParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param: Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. + */ + autosleep?: string; + /** + * Body param: Memory in MiB. + * @format int64 + */ + memory_mib?: number; + /** + * Body param: Storage in GiB. + * @format int64 + */ + storage_gib?: number; + /** + * Body param: CPU in vCPUs. + * @format double + */ + vcpu?: number; +} + +export interface MachineLifecyclePatchResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecyclePatchResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecyclePatchResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecycleListArtifactsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListArtifactsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListArtifactsResponse { + export interface Item { + artifact_id: string; + /** + * @format date-time + */ + created_at: string; + machine_id: string; + name: string; + /** + * @format int64 + */ + size_bytes: number; + download_url?: string; + execution_id?: string; + /** + * @format date-time + */ + expires_at?: string; + mime_type?: string; + sha256?: string; + } +} + +export interface MachineLifecycleDeleteArtifactParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + artifact_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeleteArtifactResponse { + artifact_id: string; + /** + * @format date-time + */ + created_at: string; + machine_id: string; + name: string; + /** + * @format int64 + */ + size_bytes: number; + download_url?: string; + execution_id?: string; + /** + * @format date-time + */ + expires_at?: string; + mime_type?: string; + sha256?: string; +} + +export interface MachineLifecycleRetrieveArtifactParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + artifact_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrieveArtifactResponse { + artifact_id: string; + /** + * @format date-time + */ + created_at: string; + machine_id: string; + name: string; + /** + * @format int64 + */ + size_bytes: number; + download_url?: string; + execution_id?: string; + /** + * @format date-time + */ + expires_at?: string; + mime_type?: string; + sha256?: string; +} + +export interface MachineLifecycleListExecutionsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListExecutionsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListExecutionsResponse { + export interface Item { + command: Array | null; + /** + * @format date-time + */ + created_at: string; + execution_id: string; + machine_id: string; + status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; + artifacts?: Array | null; + /** + * @format date-time + */ + completed_at?: string; + cwd?: string; + env_keys?: Array | null; + error_code?: string; + error_message?: string; + /** + * @format int64 + */ + exit_code?: number; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + /** + * @format int64 + */ + signal?: number; + /** + * @format date-time + */ + started_at?: string; + /** + * @format int64 + */ + stderr_bytes?: number; + stderr_truncated?: boolean; + /** + * @format int64 + */ + stdout_bytes?: number; + stdout_truncated?: boolean; + } + + export namespace Item { + export interface Artifact { + artifact_id: string; + name: string; + } + } +} + +export interface MachineLifecycleCreateExecutionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param + */ + command: Array | null; + /** + * Body param + */ + cwd?: string; + /** + * Body param + */ + env?: Record; + /** + * Body param + */ + stdin?: string; + /** + * Body param + * @format int64 + */ + timeout_ms?: number; +} + +export interface MachineLifecycleCreateExecutionResponse { + command: Array | null; + /** + * @format date-time + */ + created_at: string; + execution_id: string; + machine_id: string; + status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; + artifacts?: Array | null; + /** + * @format date-time + */ + completed_at?: string; + cwd?: string; + env_keys?: Array | null; + error_code?: string; + error_message?: string; + /** + * @format int64 + */ + exit_code?: number; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + /** + * @format int64 + */ + signal?: number; + /** + * @format date-time + */ + started_at?: string; + /** + * @format int64 + */ + stderr_bytes?: number; + stderr_truncated?: boolean; + /** + * @format int64 + */ + stdout_bytes?: number; + stdout_truncated?: boolean; +} + +export namespace MachineLifecycleCreateExecutionResponse { + export interface Artifact { + artifact_id: string; + name: string; + } +} + +export interface MachineLifecycleDeleteExecutionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + execution_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeleteExecutionResponse { + command: Array | null; + /** + * @format date-time + */ + created_at: string; + execution_id: string; + machine_id: string; + status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; + artifacts?: Array | null; + /** + * @format date-time + */ + completed_at?: string; + cwd?: string; + env_keys?: Array | null; + error_code?: string; + error_message?: string; + /** + * @format int64 + */ + exit_code?: number; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + /** + * @format int64 + */ + signal?: number; + /** + * @format date-time + */ + started_at?: string; + /** + * @format int64 + */ + stderr_bytes?: number; + stderr_truncated?: boolean; + /** + * @format int64 + */ + stdout_bytes?: number; + stdout_truncated?: boolean; +} + +export namespace MachineLifecycleDeleteExecutionResponse { + export interface Artifact { + artifact_id: string; + name: string; + } +} + +export interface MachineLifecycleRetrieveExecutionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + execution_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrieveExecutionResponse { + command: Array | null; + /** + * @format date-time + */ + created_at: string; + execution_id: string; + machine_id: string; + status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; + artifacts?: Array | null; + /** + * @format date-time + */ + completed_at?: string; + cwd?: string; + env_keys?: Array | null; + error_code?: string; + error_message?: string; + /** + * @format int64 + */ + exit_code?: number; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + /** + * @format int64 + */ + signal?: number; + /** + * @format date-time + */ + started_at?: string; + /** + * @format int64 + */ + stderr_bytes?: number; + stderr_truncated?: boolean; + /** + * @format int64 + */ + stdout_bytes?: number; + stdout_truncated?: boolean; +} + +export namespace MachineLifecycleRetrieveExecutionResponse { + export interface Artifact { + artifact_id: string; + name: string; + } +} + +export interface MachineLifecycleListExecutionEventsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + execution_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListExecutionEventsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListExecutionEventsResponse { + export interface Item { + /** + * @format date-time + */ + at: string; + /** + * @format int64 + */ + sequence: number; + type: "lifecycle" | "stdout" | "stderr"; + chunk?: string; + error_code?: string; + error_message?: string; + /** + * @format int64 + */ + exit_code?: number; + /** + * @format int64 + */ + signal?: number; + status?: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; + } +} + +export interface MachineLifecycleListExecutionOutputParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + execution_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListExecutionOutputResponse { + execution_id: string; + stderr?: string; + /** + * @format int64 + */ + stderr_bytes?: number; + stderr_truncated?: boolean; + stdout?: string; + /** + * @format int64 + */ + stdout_bytes?: number; + stdout_truncated?: boolean; +} + +export interface MachineLifecycleListPreviewsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListPreviewsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListPreviewsResponse { + export interface Item { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + /** + * @format int64 + */ + port: number; + preview_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + visibility: "public" | "private" | "org"; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "http" | "https"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + url?: string; + } +} + +export interface MachineLifecycleCreatePreviewParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param + * @format int64 + */ + port: number; + /** + * Body param + */ + protocol?: "http" | "https"; + /** + * Body param + */ + visibility?: "public" | "private" | "org"; +} + +export interface MachineLifecycleCreatePreviewResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + /** + * @format int64 + */ + port: number; + preview_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + visibility: "public" | "private" | "org"; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "http" | "https"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + url?: string; +} + +export interface MachineLifecycleDeletePreviewParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + preview_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeletePreviewResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + /** + * @format int64 + */ + port: number; + preview_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + visibility: "public" | "private" | "org"; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "http" | "https"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + url?: string; +} + +export interface MachineLifecycleRetrievePreviewParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + preview_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrievePreviewResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + /** + * @format int64 + */ + port: number; + preview_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + visibility: "public" | "private" | "org"; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "http" | "https"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + url?: string; +} + +export interface MachineLifecycleSleepParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleSleepResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleSleepResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleSleepResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecycleListSSHSessionsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListSSHSessionsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListSSHSessionsResponse { + export interface Item { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + session_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + connection?: Item.Connection; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + } + + export namespace Item { + export interface Connection { + endpoint: string; + /** + * @format int64 + */ + port: number; + ssh_username: string; + host_trust?: Connection.HostTrust; + user_certificate?: string; + } + + export namespace Connection { + export interface HostTrust { + host_pattern: string; + kind: "cert_authority"; + public_key: string; + } + } + } +} + +export interface MachineLifecycleCreateSSHSessionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param + */ + public_key: string; +} + +export interface MachineLifecycleCreateSSHSessionResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + session_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + connection?: MachineLifecycleCreateSSHSessionResponse.Connection; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; +} + +export namespace MachineLifecycleCreateSSHSessionResponse { + export interface Connection { + endpoint: string; + /** + * @format int64 + */ + port: number; + ssh_username: string; + host_trust?: Connection.HostTrust; + user_certificate?: string; + } + + export namespace Connection { + export interface HostTrust { + host_pattern: string; + kind: "cert_authority"; + public_key: string; + } + } +} + +export interface MachineLifecycleDeleteSSHSessionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + session_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeleteSSHSessionResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + session_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + connection?: MachineLifecycleDeleteSSHSessionResponse.Connection; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; +} + +export namespace MachineLifecycleDeleteSSHSessionResponse { + export interface Connection { + endpoint: string; + /** + * @format int64 + */ + port: number; + ssh_username: string; + host_trust?: Connection.HostTrust; + user_certificate?: string; + } + + export namespace Connection { + export interface HostTrust { + host_pattern: string; + kind: "cert_authority"; + public_key: string; + } + } +} + +export interface MachineLifecycleRetrieveSSHSessionParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + session_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrieveSSHSessionResponse { + /** + * @format date-time + */ + created_at: string; + machine_id: string; + session_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + connection?: MachineLifecycleRetrieveSSHSessionResponse.Connection; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; +} + +export namespace MachineLifecycleRetrieveSSHSessionResponse { + export interface Connection { + endpoint: string; + /** + * @format int64 + */ + port: number; + ssh_username: string; + host_trust?: Connection.HostTrust; + user_certificate?: string; + } + + export namespace Connection { + export interface HostTrust { + host_pattern: string; + kind: "cert_authority"; + public_key: string; + } + } +} + +export interface MachineLifecycleWatchStatusParams { + /** + * Path param: Machine identifier. + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param: Organization ID header applied to all DCS requests. + * @format uuid + */ + "X-Dedalus-Org-Id"?: string; + /** + * Header param: Optional resourceVersion bookmark used to resume a previous stream. + */ + "Last-Event-ID"?: string; +} + +export interface MachineLifecycleWatchStatusResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleWatchStatusResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleWatchStatusResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} + +export interface MachineLifecycleListTerminalsParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Query param + * @format int64 + */ + limit?: number; + /** + * Query param + */ + cursor?: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleListTerminalsResponse { + items: Array | null; + next_cursor?: string; +} + +export namespace MachineLifecycleListTerminalsResponse { + export interface Item { + /** + * @format date-time + */ + created_at: string; + /** + * @format int64 + */ + height: number; + machine_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + terminal_id: string; + /** + * @format int64 + */ + width: number; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "websocket"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + stream_url?: string; + } +} + +export interface MachineLifecycleCreateTerminalParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; + /** + * Body param + */ + cwd?: string; + /** + * Body param + */ + env?: Record; + /** + * Body param + * @format int64 + */ + height: number; + /** + * Body param + */ + shell?: string; + /** + * Body param + * @format int64 + */ + width: number; +} + +export interface MachineLifecycleCreateTerminalResponse { + /** + * @format date-time + */ + created_at: string; + /** + * @format int64 + */ + height: number; + machine_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + terminal_id: string; + /** + * @format int64 + */ + width: number; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "websocket"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + stream_url?: string; +} + +export interface MachineLifecycleDeleteTerminalParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + terminal_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleDeleteTerminalResponse { + /** + * @format date-time + */ + created_at: string; + /** + * @format int64 + */ + height: number; + machine_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + terminal_id: string; + /** + * @format int64 + */ + width: number; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "websocket"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + stream_url?: string; +} + +export interface MachineLifecycleRetrieveTerminalParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + terminal_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleRetrieveTerminalResponse { + /** + * @format date-time + */ + created_at: string; + /** + * @format int64 + */ + height: number; + machine_id: string; + status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; + terminal_id: string; + /** + * @format int64 + */ + width: number; + error_code?: string; + error_message?: string; + /** + * @format date-time + */ + expires_at?: string; + protocol?: "websocket"; + /** + * @format date-time + */ + ready_at?: string; + /** + * @format int64 + */ + retry_after_ms?: number; + stream_url?: string; +} + +export interface MachineLifecycleConnectTerminalParams { + /** + * Path param: Machine identifier. + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Path param: Terminal identifier. + * @minLength 1 + * @maxLength 253 + * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ + */ + terminal_id: string; + /** + * Header param: Organization ID header applied to all DCS requests. + * @format uuid + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleWakeParams { + /** + * Path param + * @minLength 4 + * @maxLength 253 + * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ + */ + machine_id: string; + /** + * Header param + */ + "X-Dedalus-Org-Id"?: string; +} + +export interface MachineLifecycleWakeResponse { + /** + * Seconds of inactivity before autosleep. 0 disables autosleep. + * @format int64 + * @minimum 0 + * @maximum 9223372036 + */ + autosleep_seconds: number; + desired_state: "running" | "sleeping" | "destroyed"; + machine_id: string; + /** + * Memory in MiB. + * @format int64 + */ + memory_mib: number; + status: MachineLifecycleWakeResponse.Status; + /** + * @format int64 + */ + storage_gib: number; + /** + * CPU in vCPUs. + * @format double + */ + vcpu: number; +} + +export namespace MachineLifecycleWakeResponse { + export interface Status { + /** + * @format date-time + */ + last_progress_at: string; + /** + * @format date-time + */ + last_transition_at: string; + phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; + reason: string; + retryable: boolean; + revision: string; + last_error?: string; + } +} +export declare namespace MachineLifecycle { + export { + type CreateMachineRequest as CreateMachineRequest, + type UpdateMachineRequest as UpdateMachineRequest, + type CreateExecutionRequest as CreateExecutionRequest, + type CreatePreviewRequest as CreatePreviewRequest, + type CreateSSHSessionRequest as CreateSSHSessionRequest, + type CreateTerminalRequest as CreateTerminalRequest, + type MachineLifecycleListResponse as MachineLifecycleListResponse, + type MachineLifecycleCreateResponse as MachineLifecycleCreateResponse, + type MachineLifecycleDeleteResponse as MachineLifecycleDeleteResponse, + type MachineLifecycleRetrieveResponse as MachineLifecycleRetrieveResponse, + type MachineLifecyclePatchResponse as MachineLifecyclePatchResponse, + type MachineLifecycleListArtifactsResponse as MachineLifecycleListArtifactsResponse, + type MachineLifecycleDeleteArtifactResponse as MachineLifecycleDeleteArtifactResponse, + type MachineLifecycleRetrieveArtifactResponse as MachineLifecycleRetrieveArtifactResponse, + type MachineLifecycleListExecutionsResponse as MachineLifecycleListExecutionsResponse, + type MachineLifecycleCreateExecutionResponse as MachineLifecycleCreateExecutionResponse, + type MachineLifecycleDeleteExecutionResponse as MachineLifecycleDeleteExecutionResponse, + type MachineLifecycleRetrieveExecutionResponse as MachineLifecycleRetrieveExecutionResponse, + type MachineLifecycleListExecutionEventsResponse as MachineLifecycleListExecutionEventsResponse, + type MachineLifecycleListExecutionOutputResponse as MachineLifecycleListExecutionOutputResponse, + type MachineLifecycleListPreviewsResponse as MachineLifecycleListPreviewsResponse, + type MachineLifecycleCreatePreviewResponse as MachineLifecycleCreatePreviewResponse, + type MachineLifecycleDeletePreviewResponse as MachineLifecycleDeletePreviewResponse, + type MachineLifecycleRetrievePreviewResponse as MachineLifecycleRetrievePreviewResponse, + type MachineLifecycleSleepResponse as MachineLifecycleSleepResponse, + type MachineLifecycleListSSHSessionsResponse as MachineLifecycleListSSHSessionsResponse, + type MachineLifecycleCreateSSHSessionResponse as MachineLifecycleCreateSSHSessionResponse, + type MachineLifecycleDeleteSSHSessionResponse as MachineLifecycleDeleteSSHSessionResponse, + type MachineLifecycleRetrieveSSHSessionResponse as MachineLifecycleRetrieveSSHSessionResponse, + type MachineLifecycleWatchStatusResponse as MachineLifecycleWatchStatusResponse, + type MachineLifecycleListTerminalsResponse as MachineLifecycleListTerminalsResponse, + type MachineLifecycleCreateTerminalResponse as MachineLifecycleCreateTerminalResponse, + type MachineLifecycleDeleteTerminalResponse as MachineLifecycleDeleteTerminalResponse, + type MachineLifecycleRetrieveTerminalResponse as MachineLifecycleRetrieveTerminalResponse, + type MachineLifecycleWakeResponse as MachineLifecycleWakeResponse, + type MachineLifecycleListParams as MachineLifecycleListParams, + type MachineLifecycleCreateParams as MachineLifecycleCreateParams, + type MachineLifecycleDeleteParams as MachineLifecycleDeleteParams, + type MachineLifecycleRetrieveParams as MachineLifecycleRetrieveParams, + type MachineLifecyclePatchParams as MachineLifecyclePatchParams, + type MachineLifecycleListArtifactsParams as MachineLifecycleListArtifactsParams, + type MachineLifecycleDeleteArtifactParams as MachineLifecycleDeleteArtifactParams, + type MachineLifecycleRetrieveArtifactParams as MachineLifecycleRetrieveArtifactParams, + type MachineLifecycleListExecutionsParams as MachineLifecycleListExecutionsParams, + type MachineLifecycleCreateExecutionParams as MachineLifecycleCreateExecutionParams, + type MachineLifecycleDeleteExecutionParams as MachineLifecycleDeleteExecutionParams, + type MachineLifecycleRetrieveExecutionParams as MachineLifecycleRetrieveExecutionParams, + type MachineLifecycleListExecutionEventsParams as MachineLifecycleListExecutionEventsParams, + type MachineLifecycleListExecutionOutputParams as MachineLifecycleListExecutionOutputParams, + type MachineLifecycleListPreviewsParams as MachineLifecycleListPreviewsParams, + type MachineLifecycleCreatePreviewParams as MachineLifecycleCreatePreviewParams, + type MachineLifecycleDeletePreviewParams as MachineLifecycleDeletePreviewParams, + type MachineLifecycleRetrievePreviewParams as MachineLifecycleRetrievePreviewParams, + type MachineLifecycleSleepParams as MachineLifecycleSleepParams, + type MachineLifecycleListSSHSessionsParams as MachineLifecycleListSSHSessionsParams, + type MachineLifecycleCreateSSHSessionParams as MachineLifecycleCreateSSHSessionParams, + type MachineLifecycleDeleteSSHSessionParams as MachineLifecycleDeleteSSHSessionParams, + type MachineLifecycleRetrieveSSHSessionParams as MachineLifecycleRetrieveSSHSessionParams, + type MachineLifecycleWatchStatusParams as MachineLifecycleWatchStatusParams, + type MachineLifecycleListTerminalsParams as MachineLifecycleListTerminalsParams, + type MachineLifecycleCreateTerminalParams as MachineLifecycleCreateTerminalParams, + type MachineLifecycleDeleteTerminalParams as MachineLifecycleDeleteTerminalParams, + type MachineLifecycleRetrieveTerminalParams as MachineLifecycleRetrieveTerminalParams, + type MachineLifecycleConnectTerminalParams as MachineLifecycleConnectTerminalParams, + type MachineLifecycleWakeParams as MachineLifecycleWakeParams, + }; +} +export { MachineLifecycle as MachineLifecycleResource }; diff --git a/src/sdk/resources/machine-lifecycle/ws-base.ts b/src/sdk/resources/machine-lifecycle/ws-base.ts new file mode 100644 index 0000000..794898d --- /dev/null +++ b/src/sdk/resources/machine-lifecycle/ws-base.ts @@ -0,0 +1,264 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { MachineLifecycleWSEmitter, MachineLifecycleWSStreamMessage, WebSocketError, buildURL, parameterHeaders } from "./internal-base"; +import { InternalEventEmitter } from "../../core/EventEmitter"; +import { sleep } from "../../internal/utils/sleep"; +import { type WebSocketLike, ReadyState } from "../../internal/ws-adapter"; +import { SendQueue, flattenRawData, isRecoverableClose, type RawWebSocketData, type ReconnectingEvent, type ReconnectingOverrides, type UnsentMessage } from "../../internal/ws"; +import * as MachineLifecycleAPI from "./machine-lifecycle"; +import { Dedalus } from "../../client"; +import { DedalusError } from "../../error"; + +export interface MachineLifecycleWSParameters extends Record { + machine_id: string; + + terminal_id: string; + + "X-Dedalus-Org-Id"?: string; + +} + +export interface MachineLifecycleWSReconnectOptions { + /** Called before each reconnect attempt. */ + onReconnecting(event: ReconnectingEvent): ReconnectingOverrides | void; + /** Maximum number of reconnection attempts. Default: 5. Set to 0 to disable reconnection. */ + maxRetries?: number; + /** Initial backoff delay in milliseconds. Default: 500. */ + initialDelay?: number; + /** Maximum backoff delay in milliseconds. Default: 8000. */ + maxDelay?: number; +} + +export interface MachineLifecycleWSBaseOptions { + /** Options for automatic reconnection on recoverable close codes. */ + reconnect?: MachineLifecycleWSReconnectOptions | null | undefined; + /** Maximum size of the outgoing message queue in bytes. Default: 1 MB. */ + maxQueueSize?: number | undefined; +} + +export abstract class MachineLifecycleWSBase extends MachineLifecycleWSEmitter { + url!: URL; + socket!: TSocket; + + protected _client: Dedalus; + protected _parameters: MachineLifecycleWSParameters | null | undefined; + private _reconnectOptions: MachineLifecycleWSReconnectOptions | null; + private _sendQueue: SendQueue; + private _isReconnecting = false; + private _intentionallyClosed = false; + private _closeCode = 1000; + private _closeReason = 'OK'; + private _lastCloseCode = 1006; + private _lastCloseReason = ''; + private _internalEvents = new InternalEventEmitter<{ socketSwap: (oldSocket: TSocket, newSocket: TSocket) => void; reconnecting: (event: ReconnectingEvent) => void; reconnected: () => void; close: (code: number, reason: string, unsent: UnsentMessage[]) => void; }>(); + + constructor(client: Dedalus, parameters: MachineLifecycleWSParameters, options?: MachineLifecycleWSBaseOptions | undefined) { + super(); + this._client = client; + this._parameters = parameters ?? undefined; + this._reconnectOptions = options?.reconnect ?? null; + this._sendQueue = new SendQueue(options?.maxQueueSize); + } + + protected _connectInitial(): void { + this.url = buildURL(this._client, this._parameters ?? {}); + this.socket = this._connect(); + } + + protected abstract _createSocket(url: URL, authHeaders: Record): TSocket; + + send(event: unknown): void { + if (this._isReconnecting || this.socket.readyState === ReadyState.CONNECTING) { + if (!this._sendQueue.enqueue(event)) this._onError(null, "send queue is full, message discarded", undefined); + return; + } + if (this.socket.readyState !== ReadyState.OPEN) { + this._onError(null, "cannot send on a closed WebSocket", undefined); + return; + } + try { + this.socket.send(JSON.stringify(event)); + } catch (err) { + this._onError(null, "could not send data", err); + } + } + + sendRaw(data: RawWebSocketData): void { + if (this._isReconnecting || this.socket.readyState === ReadyState.CONNECTING) { + if (!this._sendQueue.enqueueRaw(data)) this._onError(null, "send queue is full, message discarded", undefined); + return; + } + if (this.socket.readyState !== ReadyState.OPEN) { + this._onError(null, "cannot send on a closed WebSocket", undefined); + return; + } + try { + this.socket.send(flattenRawData(data)); + } catch (err) { + this._onError(null, "could not send data", err); + } + } + + close(props?: { code: number; reason: string }): void { + this._intentionallyClosed = true; + this._closeCode = props?.code ?? 1000; + this._closeReason = props?.reason ?? 'OK'; + try { this.socket.close(this._closeCode, this._closeReason); } catch (err) { this._onError(null, "could not close the connection", err); } + } + + stream(): AsyncIterableIterator { + return this[Symbol.asyncIterator](); + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + const queue: MachineLifecycleWSStreamMessage[] = []; + const resolvers: (() => void)[] = []; + let done = false; + let currentSocket = this.socket; + const push = (msg: MachineLifecycleWSStreamMessage) => { queue.push(msg); resolvers.shift()?.(); }; + const flushResolvers = () => { for (let resolver = resolvers.shift(); resolver; resolver = resolvers.shift()) resolver(); }; + const cleanup = () => { + this.off("event", onEvent); + this.off("raw", onRaw); + this.off("error", onEmitterError); + currentSocket.off("open", onOpen); + this._internalEvents.off("close", onClose); + this._internalEvents.off("socketSwap", onSocketSwap); + this._internalEvents.off("reconnecting", onReconnecting); + this._internalEvents.off("reconnected", onReconnected); + }; + const onEvent = (event: unknown) => { if (!isErrorEvent(event)) push({ type: "message", message: event as never }); }; + const onRaw = (data: RawWebSocketData) => push({ type: "raw", data }); + const onEmitterError = (error: WebSocketError) => push({ type: "error", error }); + const onOpen = () => push({ type: "open" }); + const onReconnecting = (event: ReconnectingEvent) => push({ type: "reconnecting", reconnect: event }); + const onReconnected = () => push({ type: "reconnected" }); + const onClose = (code: number, reason: string, unsent: UnsentMessage[]) => { push({ type: "close", code, reason, unsent }); done = true; flushResolvers(); cleanup(); }; + const onSocketSwap = (oldSocket: TSocket, newSocket: TSocket) => { oldSocket.off("open", onOpen); newSocket.on("open", onOpen); currentSocket = newSocket; }; + this.on("event", onEvent); + this.on("raw", onRaw); + this.on("error", onEmitterError); + this.socket.on("open", onOpen); + this._internalEvents.on("close", onClose); + this._internalEvents.on("socketSwap", onSocketSwap); + this._internalEvents.on("reconnecting", onReconnecting); + this._internalEvents.on("reconnected", onReconnected); + if (this._isReconnecting) push({ type: "reconnecting", reconnect: { attempt: 0, maxAttempts: 0, delay: 0, closeCode: 0, parameters: undefined } }); + else if (this.socket.readyState === ReadyState.CONNECTING) push({ type: "connecting" }); + else if (this.socket.readyState === ReadyState.OPEN) push({ type: "open" }); + else if (this.socket.readyState === ReadyState.CLOSING) push({ type: "closing" }); + else { push({ type: "close", code: this._lastCloseCode, reason: this._lastCloseReason, unsent: this._sendQueue.drain() }); done = true; cleanup(); } + const next = (): Promise> => new Promise((resolve) => { + if (queue.length > 0) resolve({ value: queue.shift()!, done: false }); + else if (done) resolve({ value: undefined, done: true }); + else resolvers.push(() => { + if (queue.length > 0) resolve({ value: queue.shift()!, done: false }); + else resolve({ value: undefined, done: true }); + }); + }); + return { + next, + return: () => { done = true; cleanup(); flushResolvers(); return Promise.resolve({ value: undefined, done: true }); }, + [Symbol.asyncIterator]() { return this; }, + }; + } + + private _connect(): TSocket { + this.url = buildURL(this._client, this._parameters ?? {}); + const socket = this._createSocket(this.url, this._authHeaders()); + socket.on("message", (data: string | ArrayBuffer | ArrayBufferView, isBinary: boolean) => { + if (isBinary) { this._emit("raw", data); return; } + const text = typeof data === "string" ? data : String(data); + let event: unknown; + try { event = JSON.parse(text); } catch { this._emit("raw", data); return; } + this._emit("event", event as never); + if (isErrorEvent(event)) this._onError(event as never); + else emitTypedEvent(this, event); + }); + socket.on("error", (err: Error) => { if (!this._isReconnecting) this._onError(null, err.message, err); }); + socket.on("open", () => this._flushSendQueue()); + socket.on("close", (code: number, reason: string) => { + if (socket !== this.socket) return; + if (!this._intentionallyClosed && this._canReconnect(code)) this._reconnect(code); + else if (!this._isReconnecting) this._emitPermanentClose(code, reason); + }); + return socket; + } + + private _canReconnect(code: number): boolean { + if (this._intentionallyClosed || !this._reconnectOptions || this._reconnectOptions.maxRetries === 0 || !this._reconnectOptions.onReconnecting) return false; + return isRecoverableClose(code); + } + + private async _reconnect(closeCode: number): Promise { + if (this._isReconnecting || !this._reconnectOptions) return; + this._isReconnecting = true; + const maxRetries = this._reconnectOptions.maxRetries ?? 5; + const initialDelay = this._reconnectOptions.initialDelay ?? 500; + const maxDelay = this._reconnectOptions.maxDelay ?? 8000; + for (let attempt = 1; attempt <= maxRetries; attempt++) { + if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } + const delay = Math.round(Math.min(initialDelay * 2 ** (attempt - 1), maxDelay) * (0.75 + Math.random() * 0.25)); + let reconnectingEvent: ReconnectingEvent = { attempt, maxAttempts: maxRetries, delay, closeCode, parameters: this._parameters ? { ...this._parameters } : undefined }; + let overrides: ReconnectingOverrides | void; + try { overrides = this._reconnectOptions.onReconnecting(reconnectingEvent); } catch (err) { this._isReconnecting = false; this._onError(null, "onReconnecting callback threw", err); this._emitPermanentClose(closeCode, "onReconnecting callback threw"); return; } + if (overrides && "abort" in overrides && overrides.abort) { this._isReconnecting = false; this._emitPermanentClose(closeCode, "reconnect aborted by handler"); return; } + if (overrides && "parameters" in overrides) { this._parameters = overrides.parameters; reconnectingEvent = { ...reconnectingEvent, parameters: this._parameters }; } + this._emit("reconnecting", reconnectingEvent); + this._internalEvents._emit("reconnecting", reconnectingEvent); + if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } + await sleep(delay); + if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } + let closeCodePromise: Promise | undefined; + try { + const oldSocket = this.socket; + this.socket = this._connect(); + closeCodePromise = new Promise((resolve) => { this.socket.once("close", resolve); }); + await this._awaitOpen(this.socket); + this._internalEvents._emit("socketSwap", oldSocket, this.socket); + this._isReconnecting = false; + this._flushSendQueue(); + this._emit("reconnected"); + this._internalEvents._emit("reconnected"); + return; + } catch { if (closeCodePromise) closeCode = await closeCodePromise; } + } + this._isReconnecting = false; + this._onError(null, `WebSocket reconnect failed after ${maxRetries} attempts (close code: ${closeCode})`, undefined); + this._emitPermanentClose(closeCode, `reconnect failed after ${maxRetries} attempts`); + } + + private _awaitOpen(socket: TSocket): Promise { + return new Promise((resolve, reject) => { + const cleanup = () => { socket.off("open", onOpen); socket.off("error", onError); socket.off("close", onFail); }; + const onOpen = () => { cleanup(); resolve(); }; + const onError = (err: Error) => { cleanup(); reject(err); }; + const onFail = () => { cleanup(); reject(new Error("socket closed before open")); }; + socket.once("open", onOpen); socket.once("error", onError); socket.once("close", onFail); + }); + } + + private _flushSendQueue(): void { + try { this._sendQueue.flush((data) => this.socket.send(flattenRawData(data))); } catch (err) { this._onError(null, "could not send queued data", err); } + } + + private _emitPermanentClose(code: number, reason: string): void { + this._lastCloseCode = code; + this._lastCloseReason = reason; + const unsent = this._sendQueue.drain(); + this._internalEvents._emit("close", code, reason, unsent); + this._emit("close", code, reason, unsent); + } + + protected _authHeaders(): Record { + return { ...this._client.webSocketAuthHeaders(), ...parameterHeaders(this._parameters ?? {}) }; + } +} + +const isErrorEvent = (event: unknown): boolean => typeof event === "object" && event !== null && "type" in event && event.type === "error"; + +const emitTypedEvent = (emitter: MachineLifecycleWSEmitter, event: unknown): void => { + if (typeof event === "object" && event !== null && "type" in event && typeof event.type === "string") { + (emitter._emit as (eventName: string, payload: unknown) => void)(event.type, event); + } +}; diff --git a/src/sdk/resources/machine-lifecycle/ws.ts b/src/sdk/resources/machine-lifecycle/ws.ts new file mode 100644 index 0000000..68299ea --- /dev/null +++ b/src/sdk/resources/machine-lifecycle/ws.ts @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { WebSocket, type ClientOptions } from 'ws'; +import { NodeWebSocket } from "../../internal/ws-adapter-node"; +import { MachineLifecycleWSBase, type MachineLifecycleWSBaseOptions, type MachineLifecycleWSParameters } from "./ws-base"; +import { Dedalus } from "../../client"; + +export type { MachineLifecycleWSParameters, MachineLifecycleWSReconnectOptions } from "./ws-base"; + +export interface MachineLifecycleWSClientOptions extends ClientOptions, MachineLifecycleWSBaseOptions {} + +export class MachineLifecycleWS extends MachineLifecycleWSBase { + private _wsOptions: ClientOptions | null | undefined; + + constructor( + client: Dedalus, + parameters: MachineLifecycleWSParameters, + options?: MachineLifecycleWSClientOptions | null | undefined, + ) { + if (!WebSocket) { + throw new Error( + "MachineLifecycleWS requires the \"ws\" package but it could not be loaded.", + ); + } + + const { reconnect, maxQueueSize, ...wsOptions } = options ?? {}; + super(client, parameters, { reconnect, maxQueueSize }); + this._wsOptions = wsOptions; + this._connectInitial(); + } + + protected _createSocket(url: URL, authHeaders: Record): NodeWebSocket { + const ws = new WebSocket(url, { + ...this._wsOptions, + headers: { + ...authHeaders, + ...this._wsOptions?.headers, + }, + }); + return new NodeWebSocket(ws); + } +} diff --git a/src/sdk/resources/usage.ts b/src/sdk/resources/usage.ts new file mode 100644 index 0000000..4c65efb --- /dev/null +++ b/src/sdk/resources/usage.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export * from "./usage/index"; diff --git a/src/sdk/resources/usage/index.ts b/src/sdk/resources/usage/index.ts new file mode 100644 index 0000000..64318c3 --- /dev/null +++ b/src/sdk/resources/usage/index.ts @@ -0,0 +1,6 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { Usage } from "./usage"; +export type { UsageListParams, UsageListResponse } from "./usage"; +export { Machines } from "./machines"; +export type { MachineListComputeUsageParams, MachineListComputeUsageResponse, MachineListStorageUsageParams, MachineListStorageUsageResponse } from "./machines"; diff --git a/src/sdk/resources/usage/machines.ts b/src/sdk/resources/usage/machines.ts new file mode 100644 index 0000000..988311c --- /dev/null +++ b/src/sdk/resources/usage/machines.ts @@ -0,0 +1,244 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { APIResource } from "../../resource"; +import { APIPromise } from "../../api-promise"; +import type { RequestOptions } from "../../internal/request-options"; + +export class Machines extends APIResource { + /** + * List machine compute usage breakdown + * + * @param {MachineListComputeUsageParams} [params] - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listComputeUsage = await client.usage.machines.listComputeUsage(); + * ``` + */ + listComputeUsage(params: MachineListComputeUsageParams | null | undefined = {}, options?: RequestOptions): APIPromise { + const { period_start, period_end, machine_id, granularity } = params ?? {}; + return this._client.get("/v1/usage/machines/compute", { query: { period_start: period_start, period_end: period_end, machine_id: machine_id, granularity: granularity }, ...options }); + } + + /** + * List machine storage usage breakdown + * + * @param {MachineListStorageUsageParams} [params] - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const listStorageUsage = await client.usage.machines.listStorageUsage(); + * ``` + */ + listStorageUsage(params: MachineListStorageUsageParams | null | undefined = {}, options?: RequestOptions): APIPromise { + const { period_start, period_end, machine_id } = params ?? {}; + return this._client.get("/v1/usage/machines/storage", { query: { period_start: period_start, period_end: period_end, machine_id: machine_id }, ...options }); + } +} + +export interface MachineListComputeUsageParams { + /** + * Usage period start (YYYY-MM-DD). Defaults to first of current month. + */ + period_start?: string; + /** + * Last UTC usage date to include (YYYY-MM-DD). Defaults to current time. + */ + period_end?: string; + /** + * Optional machine ID filter. + */ + machine_id?: string; + /** + * Usage breakdown granularity: hour or day. Defaults to hour. + */ + granularity?: string; +} + +export interface MachineListComputeUsageResponse { + /** + * Usage breakdown granularity used for rows: hour or day. + */ + granularity: string; + /** + * Exclusive usage period end. + * @format date-time + */ + period_end: string; + /** + * Inclusive usage period start. + * @format date-time + */ + period_start: string; + /** + * Machine-level compute usage breakdown rows. + */ + rows: Array | null; +} + +export namespace MachineListComputeUsageResponse { + export interface Row { + /** + * Machine-awake seconds in this bucket. + * @format int64 + */ + awake_seconds: number; + /** + * Exclusive usage bucket end. + * @format date-time + */ + bucket_end: string; + /** + * Inclusive usage bucket start. + * @format date-time + */ + bucket_start: string; + /** + * Requested vCPU millicores multiplied by guest-owned active CPU seconds. + * @format int64 + */ + cpu_millicore_seconds: number; + /** + * Latest raw window_end represented by this row. + * @format date-time + */ + last_window_end: string; + /** + * Machine identifier. + */ + machine_id: string; + /** + * Requested memory MiB multiplied by running allocation seconds. + * @format int64 + */ + memory_mib_seconds: number; + /** + * Org compute bucket IDs this row contributes to. + */ + org_metering_bucket_ids: Array | null; + /** + * Requested memory for this shape, in MiB. + * @format int32 + */ + requested_memory_mib: number; + /** + * Requested storage for this shape, in GiB. + * @format int32 + */ + requested_storage_gib: number; + /** + * Requested vCPU for this shape. + * @format double + */ + requested_vcpu: number; + /** + * Stable fingerprint for the requested machine shape. + */ + spec_fingerprint: string; + /** + * Stripe CPU meter event identifiers linked to those org buckets. + */ + stripe_cpu_identifiers: Array | null; + /** + * Stripe memory meter event identifiers linked to those org buckets. + */ + stripe_memory_identifiers: Array | null; + /** + * Raw usage windows compacted into this row. + * @format int64 + */ + window_count: number; + /** + * Latest Stripe emission timestamp for linked org buckets, when emitted. + * @format date-time + */ + latest_stripe_emitted_at?: string; + } +} + +export interface MachineListStorageUsageParams { + /** + * Usage period start (YYYY-MM-DD). Defaults to first of current month. + */ + period_start?: string; + /** + * Last UTC usage date to include (YYYY-MM-DD). Defaults to current time. + */ + period_end?: string; + /** + * Optional machine ID filter. + */ + machine_id?: string; +} + +export interface MachineListStorageUsageResponse { + /** + * Exclusive usage period end. + * @format date-time + */ + period_end: string; + /** + * Inclusive usage period start. + * @format date-time + */ + period_start: string; + /** + * Machine-level storage usage breakdown rows. + */ + rows: Array | null; +} + +export namespace MachineListStorageUsageResponse { + export interface Row { + /** + * Exclusive usage bucket end. + * @format date-time + */ + bucket_end: string; + /** + * Inclusive usage bucket start. + * @format date-time + */ + bucket_start: string; + /** + * Machine logical bytes observed for storage allocation. + * @format int64 + */ + logical_storage_bytes: number; + /** + * Machine identifier. + */ + machine_id: string; + /** + * Org storage bucket ID this row contributes to. + */ + org_metering_bucket_id: string; + /** + * Allocated logical MiB-seconds for this machine. + * @format int64 + */ + storage_mib_seconds: number; + /** + * Stripe storage meter event identifier linked to that org bucket. + */ + stripe_storage_identifier: string; + /** + * Latest Stripe emission timestamp for the linked org bucket, when emitted. + * @format date-time + */ + latest_stripe_emitted_at?: string; + } +} +export declare namespace Machines { + export { + type MachineListComputeUsageResponse as MachineListComputeUsageResponse, + type MachineListStorageUsageResponse as MachineListStorageUsageResponse, + type MachineListComputeUsageParams as MachineListComputeUsageParams, + type MachineListStorageUsageParams as MachineListStorageUsageParams, + }; +} +export { Machines as MachineResource }; diff --git a/src/sdk/resources/usage/usage.ts b/src/sdk/resources/usage/usage.ts new file mode 100644 index 0000000..320b4ef --- /dev/null +++ b/src/sdk/resources/usage/usage.ts @@ -0,0 +1,88 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import { APIResource } from "../../resource"; +import { APIPromise } from "../../api-promise"; +import type { RequestOptions } from "../../internal/request-options"; +import { Machines, type MachineListComputeUsageResponse, type MachineListStorageUsageResponse, type MachineListComputeUsageParams, type MachineListStorageUsageParams } from "./machines"; + +export class Usage extends APIResource { + machines: Machines = new Machines(this._client); + + /** + * Get usage summary + * + * @param {UsageListParams} [params] - The parameters to send with the request. + * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. + * @returns {APIPromise} OK + * + * @example + * ```ts + * const list = await client.usage.list(); + * ``` + */ + list(params: UsageListParams | null | undefined = {}, options?: RequestOptions): APIPromise { + const { period_start } = params ?? {}; + return this._client.get("/v1/usage", { query: { period_start: period_start }, ...options }); + } +} + +export interface UsageListParams { + /** + * Billing period start (YYYY-MM-DD). Defaults to first of current month. + */ + period_start?: string; +} + +export interface UsageListResponse { + /** + * Closed awake seconds in billed org buckets for the period. + * @format int64 + */ + billed_awake_seconds: number; + /** + * Closed requested vCPU millicores multiplied by guest-owned active CPU seconds for the period. + * @format int64 + */ + billed_cpu_millicore_seconds: number; + /** + * Closed billable logical MiB-seconds for the period, matching the Stripe storage meter. + * @format int64 + */ + billed_logical_storage_mib_seconds: number; + /** + * Closed requested memory MiB multiplied by running allocation seconds for the period. + * @format int64 + */ + billed_memory_mib_seconds: number; + /** + * Plan-included storage in GiB, used as a local guardrail only. + * @format int64 + */ + included_storage_gib: number; + /** + * Billing plan in effect for the organization. + */ + plan_slug: string; + /** + * Current provisioned storage summed across machines in GiB. + * @format int64 + */ + provisioned_storage_gib: number; +} +Usage.Machines = Machines; + +export declare namespace Usage { + export { + type UsageListResponse as UsageListResponse, + type UsageListParams as UsageListParams, + }; + + export { + Machines as Machines, + type MachineListComputeUsageResponse as MachineListComputeUsageResponse, + type MachineListStorageUsageResponse as MachineListStorageUsageResponse, + type MachineListComputeUsageParams as MachineListComputeUsageParams, + type MachineListStorageUsageParams as MachineListStorageUsageParams, + }; +} +export { Usage as UsageResource }; diff --git a/src/sdk/streaming.ts b/src/sdk/streaming.ts new file mode 100644 index 0000000..a497962 --- /dev/null +++ b/src/sdk/streaming.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +/** @deprecated Import from ./core/streaming instead */ +export * from './core/streaming'; diff --git a/src/sdk/uploads.ts b/src/sdk/uploads.ts new file mode 100644 index 0000000..86a228e --- /dev/null +++ b/src/sdk/uploads.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export { type Uploadable, toFile, type ToFileInput } from './core/uploads'; diff --git a/src/sdk/version.ts b/src/sdk/version.ts new file mode 100644 index 0000000..1bee298 --- /dev/null +++ b/src/sdk/version.ts @@ -0,0 +1,3 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +export const VERSION = "0.0.1"; diff --git a/tests/smoke-test.ts b/tests/smoke-test.ts new file mode 100644 index 0000000..0687e2c --- /dev/null +++ b/tests/smoke-test.ts @@ -0,0 +1,337 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +// Smoke test: invokes the generated CLI once per operation to confirm each command can reach +// its endpoint. Build the CLI first (so dist/esm/bin.js exists), then run this from the repo +// with `bun tests/smoke-test.ts`. Each case below holds the argv for one command, minus the +// base URL and credentials — the embedded SDK reads those from the environment, so set +// _BASE_URL and the auth variables before running. +// +// Two environment variables tune a run: +// - SCALAR_SMOKE_FILTER: comma-separated needles; only operations whose name or path contains +// one of them run, so you can smoke-test a subset without editing this file. +// - SCALAR_SMOKE_REPORT: a file path; when set, the run writes a JSON report there instead of +// printing a table. The generator uses this to collect per-operation results. +import { execFile } from 'node:child_process' +import { existsSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { promisify } from 'node:util' + +const execFileAsync = promisify(execFile) + +// The result of running one case, collected for the JSON report or the printed table. +type SmokeResult = { + operation: string + method: string + path: string + status: 'passed' | 'failed' + durationMs: number + error?: string +} + +// One entry per generated operation. `args` is the argv passed to the built CLI; the other fields +// are metadata used for filtering and reporting. This list is generated, so it stays in sync with +// the CLI command surface. +const cases: { operation: string; method: string; path: string; args: string[] }[] = [ + { + operation: "list", + method: "GET", + path: "/v1/machines", + args: ["machine-lifecycle","list"], + }, + + { + operation: "create", + method: "POST", + path: "/v1/machines", + args: ["machine-lifecycle","create","--memory-mib","1","--storage-gib","1","--vcpu","1"], + }, + + { + operation: "delete", + method: "DELETE", + path: "/v1/machines/{machine_id}", + args: ["machine-lifecycle","delete","--machine-id","machine_id"], + }, + + { + operation: "retrieve", + method: "GET", + path: "/v1/machines/{machine_id}", + args: ["machine-lifecycle","retrieve","--machine-id","machine_id"], + }, + + { + operation: "patch", + method: "PATCH", + path: "/v1/machines/{machine_id}", + args: ["machine-lifecycle","patch","--machine-id","machine_id"], + }, + + { + operation: "listArtifacts", + method: "GET", + path: "/v1/machines/{machine_id}/artifacts", + args: ["machine-lifecycle","list-artifacts","--machine-id","machine_id"], + }, + + { + operation: "deleteArtifact", + method: "DELETE", + path: "/v1/machines/{machine_id}/artifacts/{artifact_id}", + args: ["machine-lifecycle","delete-artifact","--machine-id","machine_id","--artifact-id","artifact_id"], + }, + + { + operation: "retrieveArtifact", + method: "GET", + path: "/v1/machines/{machine_id}/artifacts/{artifact_id}", + args: ["machine-lifecycle","retrieve-artifact","--machine-id","machine_id","--artifact-id","artifact_id"], + }, + + { + operation: "listExecutions", + method: "GET", + path: "/v1/machines/{machine_id}/executions", + args: ["machine-lifecycle","list-executions","--machine-id","machine_id"], + }, + + { + operation: "createExecution", + method: "POST", + path: "/v1/machines/{machine_id}/executions", + args: ["machine-lifecycle","create-execution","--machine-id","machine_id","--command","[\"command\"]"], + }, + + { + operation: "deleteExecution", + method: "DELETE", + path: "/v1/machines/{machine_id}/executions/{execution_id}", + args: ["machine-lifecycle","delete-execution","--machine-id","machine_id","--execution-id","execution_id"], + }, + + { + operation: "retrieveExecution", + method: "GET", + path: "/v1/machines/{machine_id}/executions/{execution_id}", + args: ["machine-lifecycle","retrieve-execution","--machine-id","machine_id","--execution-id","execution_id"], + }, + + { + operation: "listExecutionEvents", + method: "GET", + path: "/v1/machines/{machine_id}/executions/{execution_id}/events", + args: ["machine-lifecycle","list-execution-events","--machine-id","machine_id","--execution-id","execution_id"], + }, + + { + operation: "listExecutionOutput", + method: "GET", + path: "/v1/machines/{machine_id}/executions/{execution_id}/output", + args: ["machine-lifecycle","list-execution-output","--machine-id","machine_id","--execution-id","execution_id"], + }, + + { + operation: "listPreviews", + method: "GET", + path: "/v1/machines/{machine_id}/previews", + args: ["machine-lifecycle","list-previews","--machine-id","machine_id"], + }, + + { + operation: "createPreview", + method: "POST", + path: "/v1/machines/{machine_id}/previews", + args: ["machine-lifecycle","create-preview","--machine-id","machine_id","--port","1"], + }, + + { + operation: "deletePreview", + method: "DELETE", + path: "/v1/machines/{machine_id}/previews/{preview_id}", + args: ["machine-lifecycle","delete-preview","--machine-id","machine_id","--preview-id","preview_id"], + }, + + { + operation: "retrievePreview", + method: "GET", + path: "/v1/machines/{machine_id}/previews/{preview_id}", + args: ["machine-lifecycle","retrieve-preview","--machine-id","machine_id","--preview-id","preview_id"], + }, + + { + operation: "sleep", + method: "POST", + path: "/v1/machines/{machine_id}/sleep", + args: ["machine-lifecycle","sleep","--machine-id","machine_id"], + }, + + { + operation: "listSshSessions", + method: "GET", + path: "/v1/machines/{machine_id}/ssh", + args: ["machine-lifecycle","list-ssh-sessions","--machine-id","machine_id"], + }, + + { + operation: "createSshSession", + method: "POST", + path: "/v1/machines/{machine_id}/ssh", + args: ["machine-lifecycle","create-ssh-session","--machine-id","machine_id","--public-key","public_key"], + }, + + { + operation: "deleteSshSession", + method: "DELETE", + path: "/v1/machines/{machine_id}/ssh/{session_id}", + args: ["machine-lifecycle","delete-ssh-session","--machine-id","machine_id","--session-id","session_id"], + }, + + { + operation: "retrieveSshSession", + method: "GET", + path: "/v1/machines/{machine_id}/ssh/{session_id}", + args: ["machine-lifecycle","retrieve-ssh-session","--machine-id","machine_id","--session-id","session_id"], + }, + + { + operation: "watchStatus", + method: "GET", + path: "/v1/machines/{machine_id}/status/stream", + args: ["machine-lifecycle","watch-status","--machine-id","machine_id","--max-items","10"], + }, + + { + operation: "listTerminals", + method: "GET", + path: "/v1/machines/{machine_id}/terminals", + args: ["machine-lifecycle","list-terminals","--machine-id","machine_id"], + }, + + { + operation: "createTerminal", + method: "POST", + path: "/v1/machines/{machine_id}/terminals", + args: ["machine-lifecycle","create-terminal","--machine-id","machine_id","--height","1","--width","1"], + }, + + { + operation: "deleteTerminal", + method: "DELETE", + path: "/v1/machines/{machine_id}/terminals/{terminal_id}", + args: ["machine-lifecycle","delete-terminal","--machine-id","machine_id","--terminal-id","terminal_id"], + }, + + { + operation: "retrieveTerminal", + method: "GET", + path: "/v1/machines/{machine_id}/terminals/{terminal_id}", + args: ["machine-lifecycle","retrieve-terminal","--machine-id","machine_id","--terminal-id","terminal_id"], + }, + + { + operation: "wake", + method: "POST", + path: "/v1/machines/{machine_id}/wake", + args: ["machine-lifecycle","wake","--machine-id","machine_id"], + }, + + { + operation: "list", + method: "GET", + path: "/v1/usage", + args: ["usage","list"], + }, + + { + operation: "listComputeUsage", + method: "GET", + path: "/v1/usage/machines/compute", + args: ["usage:machines","list-compute-usage"], + }, + + { + operation: "listStorageUsage", + method: "GET", + path: "/v1/usage/machines/storage", + args: ["usage:machines","list-storage-usage"], + }, + +] + +// Each command gets its own budget so one hanging command fails on its own instead of stalling +// the whole run; the generator additionally bounds the overall run. +const COMMAND_TIMEOUT_MS = 60_000 + +// Locate the built executable from the nearest package.json `bin` entry. Walking up from this +// file (rather than assuming a fixed relative path) keeps it correct whether this harness runs +// from the repo's `tests/` directory or is staged flat into a runner by the smoke tester. +const resolveBinPath = (): string => { + let dir = dirname(fileURLToPath(import.meta.url)) + for (let depth = 0; depth < 6; depth += 1) { + const manifestPath = join(dir, 'package.json') + if (existsSync(manifestPath)) { + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { bin?: string | Record } + const bin = typeof manifest.bin === 'string' ? manifest.bin : Object.values(manifest.bin ?? {})[0] + if (bin) return join(dir, bin) + } + const parent = dirname(dir) + if (parent === dir) break + dir = parent + } + throw new Error('Could not locate the built CLI binary (run the package build first so dist/esm/bin.js exists).') +} + +const main = async (): Promise => { + const binPath = resolveBinPath() + + // SCALAR_SMOKE_FILTER (comma-separated) keeps only cases whose operation name or path matches + // one of the needles, so a caller can smoke-test a subset. With no filter, every case runs. + const filter = process.env['SCALAR_SMOKE_FILTER'] + const needles = filter ? filter.split(',').map((needle) => needle.trim()).filter(Boolean) : [] + const selected = needles.length > 0 ? cases.filter((testCase) => needles.some((needle) => testCase.operation.includes(needle) || testCase.path.includes(needle))) : cases + + // Run every selected command concurrently. Promise.allSettled means one failing command never + // blocks the others, so a single run reports the status of every endpoint. + const settled = await Promise.allSettled( + selected.map(async (testCase): Promise => { + const startedAt = Date.now() + try { + // Pass the current environment through so the embedded SDK picks up the base URL and + // credentials; node runs the built bin exactly as the published executable would. + await execFileAsync('node', [binPath, ...testCase.args], { env: process.env, timeout: COMMAND_TIMEOUT_MS, maxBuffer: 1024 * 1024 * 20 }) + return { operation: testCase.operation, method: testCase.method, path: testCase.path, status: 'passed', durationMs: Date.now() - startedAt } + } catch (error) { + // Surface stderr (commander/runtime error output) when present; fall back to the message. + const detail = error && typeof error === 'object' && 'stderr' in error ? String((error as { stderr?: unknown }).stderr ?? '') : '' + const message = detail.trim() || (error instanceof Error ? (error.stack ?? error.message) : String(error)) + return { operation: testCase.operation, method: testCase.method, path: testCase.path, status: 'failed', durationMs: Date.now() - startedAt, error: message } + } + }), + ) + + // allSettled never rejects, but defensively map any rejected slot to a failed result. + const results: SmokeResult[] = settled.map((result) => (result.status === 'fulfilled' ? result.value : { operation: 'unknown', method: '', path: '', status: 'failed', durationMs: 0, error: String(result.reason) })) + const failed = results.filter((result) => result.status === 'failed') + + // With SCALAR_SMOKE_REPORT set, write a machine-readable report; otherwise print a table. + const reportPath = process.env['SCALAR_SMOKE_REPORT'] + if (reportPath) { + writeFileSync(reportPath, JSON.stringify({ total: results.length, failed: failed.length, results })) + } else { + for (const result of results) { + if (result.status === 'passed') console.log(`\u2714 ${result.operation} (${result.method} ${result.path}) ${result.durationMs}ms`) + else console.error(`\u2718 ${result.operation} (${result.method} ${result.path})\n${result.error ?? ''}`) + } + if (results.length === 0) { + console.error('No commands ran (empty SDK or a SCALAR_SMOKE_FILTER that matched nothing).') + } else { + console.log(`\n${results.length - failed.length}/${results.length} commands passed`) + } + } + + // An empty run (no operations, or a filter that matched nothing) is a failure, not a vacuous pass. + if (failed.length > 0 || results.length === 0) process.exitCode = 1 +} + +void main() diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..a264fc9 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "CommonJS", + "lib": [ + "ES2023", + "DOM" + ], + "rootDir": "src", + "outDir": "./dist/cjs", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..471463c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2023", + "module": "ES2022", + "moduleResolution": "Bundler", + "lib": [ + "ES2023", + "DOM" + ], + "rootDir": "src", + "outDir": "./dist/esm", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "types": [ + "node" + ] + }, + "include": [ + "src/**/*.ts" + ] +} From fd5540f2220b861f6b36ebf653f954e36d5b069a Mon Sep 17 00:00:00 2001 From: scalarbot Date: Mon, 10 Aug 2026 21:25:52 +0000 Subject: [PATCH 2/3] chore(api): regenerate SDK The previous scalar-sdk.manifest.json could not be read as a valid manifest, so changes were not classified. Build: y1BU1bMwkQyrx8hjAgtOM --- .claude/skills/dedalus-cli-sdk/SKILL.md | 44 + .github/workflows/release-please.yml | 57 + .github/workflows/release-title-edit.yml | 143 + LICENSE | 2 +- README.md | 52 +- SKILL.md | 44 + VERSIONING.md | 79 + api.md | 247 +- man/dedalus-completion.1 | 32 + man/dedalus.1 | 97 + openapi.augmented.json | 1567 +-- package.json | 22 +- release-please-config.json | 47 + scalar-sdk.manifest.json | 8377 +---------------- src/cli/completions.ts | 12 + src/cli/runtime.ts | 69 +- src/commands/index.ts | 1562 +-- src/sdk/api-promise.ts | 81 +- src/sdk/client.ts | 190 +- src/sdk/core/EventEmitter.ts | 50 - src/sdk/core/api-promise.ts | 92 +- .../machine-lifecycle.ts => core/resource.ts} | 2 +- src/sdk/core/streaming.ts | 333 - src/sdk/index.ts | 3 +- src/sdk/internal/decoders/line.ts | 135 - src/sdk/internal/parse.ts | 76 +- src/sdk/internal/request-options.ts | 84 +- src/sdk/internal/types.ts | 6 + src/sdk/internal/utils.ts | 1 + src/sdk/internal/utils/query.ts | 10 + src/sdk/internal/ws-adapter-browser.ts | 123 - src/sdk/internal/ws-adapter-node.ts | 105 - src/sdk/internal/ws-adapter.ts | 30 - src/sdk/internal/ws.ts | 193 - src/sdk/resource.ts | 2 +- src/sdk/{resources/usage.ts => resources.ts} | 2 +- src/sdk/resources/index.ts | 6 - src/sdk/resources/machine-lifecycle/index.ts | 6 - .../machine-lifecycle/internal-base.ts | 105 - .../machine-lifecycle/machine-lifecycle.ts | 2616 ----- .../resources/machine-lifecycle/ws-base.ts | 264 - src/sdk/resources/machine-lifecycle/ws.ts | 42 - src/sdk/resources/usage/index.ts | 6 - src/sdk/resources/usage/machines.ts | 244 - src/sdk/resources/usage/usage.ts | 88 - src/sdk/streaming.ts | 4 - src/sdk/version.ts | 2 +- tests/smoke-test.ts | 224 - 48 files changed, 1678 insertions(+), 15900 deletions(-) create mode 100644 .claude/skills/dedalus-cli-sdk/SKILL.md create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release-title-edit.yml create mode 100644 SKILL.md create mode 100644 man/dedalus-completion.1 create mode 100644 man/dedalus.1 create mode 100644 release-please-config.json create mode 100644 src/cli/completions.ts delete mode 100644 src/sdk/core/EventEmitter.ts rename src/sdk/{resources/machine-lifecycle.ts => core/resource.ts} (64%) delete mode 100644 src/sdk/core/streaming.ts delete mode 100644 src/sdk/internal/decoders/line.ts create mode 100644 src/sdk/internal/utils/query.ts delete mode 100644 src/sdk/internal/ws-adapter-browser.ts delete mode 100644 src/sdk/internal/ws-adapter-node.ts delete mode 100644 src/sdk/internal/ws-adapter.ts delete mode 100644 src/sdk/internal/ws.ts rename src/sdk/{resources/usage.ts => resources.ts} (71%) delete mode 100644 src/sdk/resources/machine-lifecycle/index.ts delete mode 100644 src/sdk/resources/machine-lifecycle/internal-base.ts delete mode 100644 src/sdk/resources/machine-lifecycle/machine-lifecycle.ts delete mode 100644 src/sdk/resources/machine-lifecycle/ws-base.ts delete mode 100644 src/sdk/resources/machine-lifecycle/ws.ts delete mode 100644 src/sdk/resources/usage/index.ts delete mode 100644 src/sdk/resources/usage/machines.ts delete mode 100644 src/sdk/resources/usage/usage.ts delete mode 100644 src/sdk/streaming.ts diff --git a/.claude/skills/dedalus-cli-sdk/SKILL.md b/.claude/skills/dedalus-cli-sdk/SKILL.md new file mode 100644 index 0000000..d18c0fe --- /dev/null +++ b/.claude/skills/dedalus-cli-sdk/SKILL.md @@ -0,0 +1,44 @@ +--- +name: dedalus-cli-sdk +description: "CLI SDK for Dedalus API. Use when writing CLI code that calls Dedalus API with the dedalus-cli package: installing it, constructing and authenticating the client, and calling API operations." +--- + +# Dedalus CLI SDK + +Generated CLI client for Dedalus API, published as `dedalus-cli`. Use the generated client instead of hand-writing HTTP requests. + +## Install + +```sh +# npm (requires Node.js) +npm install -g dedalus-cli +``` + +## Client setup and authentication + +Provide credentials using the options below. Environment variables are read automatically when the target runtime supports them: + +- `--api-key` (env: `DEDALUS_API_KEY`) — API key authentication using Bearer token +- `--x-api-key` (env: `DEDALUS_X_API_KEY`) — API key authentication using X-API-Key header +- `--bearer-auth` (env: `DEDALUS_BEARER_AUTH`) — Dedalus API key in Authorization: Bearer . + +## Calling operations + +```sh +dedalus [resource] [command] [flags] +``` + +Method names, parameter shapes, and response types are generated from the API description — do not guess them. Look up the exact call signature in [api.md](../../../api.md) before writing a call. + +## Error handling + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +## Requirements + +- Node.js 20 or newer + +## Reference files + +- [README.md](../../../README.md) — full feature tour: client options, retries and timeouts, logging. +- [api.md](../../../api.md) — complete catalogue of every operation with request and response types. diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..e08c57a --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,57 @@ +name: Release Please + +on: + # Fires when a human merges the platform-managed release PR into main + # (the PR's base, so its diff shows the full pending release). Regenerations and + # custom-code pushes only touch scalar-next and never run this workflow. + push: + branches: + - main + # Manual fallback only; nothing in the automated chain depends on dispatch. + workflow_dispatch: + +permissions: + contents: write + # Required to update the merged release PR's autorelease labels after tagging. + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + # Consumed by the publish job's checkout: without this mapping the checkout ref would be + # empty and the publish would build the triggering branch head instead of the released + # tag's exact commit. + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + id: release + with: + target-branch: main + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + # Release PRs are opened and updated by the Scalar platform with its own + # credential (so their CI runs without manual approval); this workflow only + # cuts the tag + GitHub Release once a release PR is merged. + skip-github-pull-request: true + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + if: ${{ steps.release.outputs.release_created == 'true' }} + with: + fetch-depth: 0 + # Merging the release PR into main is itself the promotion, but the + # version bump and changelog must also land back on scalar-next, or the next + # release PR would propose this release again. A real merge (never a plain + # commit push) so a squash- or rebase-merged release PR syncs just as well; the + # non-conventional message keeps the sync commit out of future changelogs. Plain + # (non-force) push on purpose: losing a race against a concurrent regeneration + # push fails loudly here, and the platform's next sync re-converges. + - name: Sync release back to scalar-next + if: ${{ steps.release.outputs.release_created == 'true' }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git fetch origin scalar-next + git checkout -B scalar-next origin/scalar-next + git merge --no-edit -m "Sync release ${{ steps.release.outputs.tag_name }} back to scalar-next" "${{ steps.release.outputs.sha }}" + git push origin scalar-next diff --git a/.github/workflows/release-title-edit.yml b/.github/workflows/release-title-edit.yml new file mode 100644 index 0000000..d1827b1 --- /dev/null +++ b/.github/workflows/release-title-edit.yml @@ -0,0 +1,143 @@ +name: Release PR Version + +on: + # `edited` is not one of the default pull_request types, but it is the point of this + # workflow: retitling the release PR is how a maintainer picks an exact version. The + # other types keep the consistency check attached to the PR as it evolves. + pull_request: + types: [opened, reopened, edited, synchronize] + +# Read-only by default; only the job that pushes the Release-As commit widens this. +permissions: + contents: read + +jobs: + version-consistency: + # Also the check name the release PR's footer tells maintainers to wait for. + name: Release PR version + # A release-shaped title against the release branch is the whole test here, with no clause + # on who opened the pull request or what branch it is rendered onto. This job only reads, + # and a pull request claiming to be a release is worth checking against the committed + # version whoever opened it. Every other pull request skips this job, which GitHub reports + # as neutral. + # + # Loose on purpose: the prefix, not the full semver pattern the script matches. A version + # typo'd into the title still reaches the script and fails there, rather than falling out + # of the guard and leaving no check at all — a missing check is not a failing one, and this + # is the check the release PR's footer tells maintainers to wait for before merging. + # + # It is also what keeps the workflow from failing silently if the platform ever opens + # release PRs from a different account: the bridge below stops firing, but this check still + # runs, the retitled version and the committed one disagree, and it turns red. The merge is + # blocked loudly instead of releasing the old version. + if: >- + ${{ github.event.pull_request.base.ref == 'main' + && startsWith(github.event.pull_request.title, 'release: ') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + # The version committed on the PR head is what merging would actually release. + ref: ${{ github.event.pull_request.head.sha }} + # Fails while the title version and the committed version disagree — the window between + # a maintainer's retitle and the platform re-rendering the PR from it. Without this a + # merge in that window would tag a release whose own files self-report the old version, + # and a title release-please cannot parse would silently cut no release at all. + - name: Compare the title version with the committed version + env: + # The title is human input, so it is bound through the environment (never + # interpolated into the script) and matched against a strict semver pattern + # before it is read. + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$' + if [[ ! "$PR_TITLE" =~ $pattern ]]; then + echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE" + exit 1 + fi + title_version="${BASH_REMATCH[1]}" + manifest_version="$(jq -r '.["."]' .release-please-manifest.json)" + if [[ "$title_version" != "$manifest_version" ]]; then + echo "::error::Release PR title says $title_version but the pull request is versioned $manifest_version. Wait for the release PR to be re-rendered at $title_version before merging." + exit 1 + fi + echo "Release PR title and committed version agree on $title_version." + + apply-title-version: + name: Apply edited release version + # A human retitle of the open release PR is bridged into the canonical explicit-version + # mechanism (a Release-As commit on scalar-next, which the platform re-renders the + # release PR from). `changes.title` is only set when the title itself changed, and bot + # senders are ignored so the platform's own retitles cannot bounce back into another + # commit. + # + # Both signals are required here, unlike the read-only check above, and each covers what the + # other cannot. The author, because this job pushes with `contents: write` and a title is + # free text — anyone able to open a pull request against main could otherwise + # name a version and have it committed to scalar-next. `user.login` is set by GitHub when + # the pull request is opened and cannot be forged by whoever edits the title afterwards. + # The title, because those accounts open pull requests other than release PRs, and retitling + # one of those must not push a release. + # + # One login per platform deployment, since a repo generated by staging carries staging's + # app. Parenthesised because the group is ANDed with the title clause below: without the + # parens that `&&` would bind to the last login alone, letting the other accounts push a + # release off any title. Listed rather than matched on the shared `scalar-docs` stem — a + # prefix test would also admit any future `scalar-docs-*[bot]`, including someone else's. + if: >- + ${{ github.event.action == 'edited' + && github.event.changes.title != null + && github.event.sender.type != 'Bot' + && github.event.pull_request.state == 'open' + && github.event.pull_request.base.ref == 'main' + && (github.event.pull_request.user.login == 'scalar-docs[bot]' + || github.event.pull_request.user.login == 'scalar-docs-staging[bot]' + || github.event.pull_request.user.login == 'scalar-docs-development[bot]') + && startsWith(github.event.pull_request.title, 'release: ') }} + # One bridge run at a time per pull request, newest retitle wins. Two retitles in quick + # succession (a version typo corrected seconds later) would otherwise start two runs that + # both read the version committed on the PR head, both get past the no-op guard, and both + # push — and since each fetches scalar-next immediately before committing, the second + # push fast-forwards instead of failing. release-please honours the newest Release-As + # footer, so the release would land on whichever run happened to push last, not on the + # title the maintainer actually left behind. + concurrency: + group: release-title-edit-${{ github.event.pull_request.number }} + cancel-in-progress: true + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + # The PR head, so the version the pull request currently carries can be read before + # deciding whether anything needs to change. + ref: ${{ github.event.pull_request.head.sha }} + - name: Push a Release-As commit for the edited version + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + set -euo pipefail + pattern='^release: ((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?)$' + if [[ ! "$PR_TITLE" =~ $pattern ]]; then + echo "::error::Release PR title must be \"release: X.Y.Z\" with a full semver version, got: $PR_TITLE" + exit 1 + fi + # Only ever a validated semver from here on, so it is safe in a commit message. + version="${BASH_REMATCH[1]}" + manifest_version="$(jq -r '.["."]' .release-please-manifest.json)" + # The re-rendered PR is retitled to the version it now carries; stopping here keeps + # that from producing an endless chain of Release-As commits. + if [[ "$version" == "$manifest_version" ]]; then + echo "This release PR already carries $version; nothing to do." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git fetch origin scalar-next + git checkout -B scalar-next origin/scalar-next + git commit --allow-empty -m "chore: release $version" -m "Release-As: $version" + # Plain (non-force) push: losing a race against a regeneration push fails loudly + # rather than discarding it, and the retitle can simply be repeated. + git push origin scalar-next + echo "Pushed Release-As: $version to scalar-next. The release PR will be re-rendered at that version." diff --git a/LICENSE b/LICENSE index 261eeb9..d6e38b3 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Dedalus Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 4a4937b..3469800 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Dedalus -Generated CLI SDK for Dedalus API. -Controlplane API for Dedalus Cloud Services (DCS). +This library provides convenient access to the Dedalus REST API from the command line. + +The full API of this library can be found in [api.md](./api.md).
@@ -10,8 +11,8 @@ Controlplane API for Dedalus Cloud Services (DCS). - [Installation](#installation) - [Usage](#usage) - [API Reference](./api.md) -- [Streaming](#streaming) -- [WebSockets](#websockets) +- [Shell Completion](#shell-completion) +- [Manual Pages](#manual-pages) - [Authentication](#authentication) - [Errors](#errors) - [Client Options](#client-options) @@ -25,6 +26,7 @@ Controlplane API for Dedalus Cloud Services (DCS). ## Installation ```sh +# npm (requires Node.js) npm install -g dedalus-cli ``` @@ -34,8 +36,6 @@ npm install -g dedalus-cli ```sh dedalus [resource] [command] [flags] - -dedalus machine-lifecycle list --bearer "$BEARER" ``` The examples in the following sections assume a `client` configured as shown above. @@ -44,15 +44,31 @@ See the [API reference](./api.md) for every available operation.
-## Streaming +## Shell Completion + +`dedalus completion ` prints a completion script for bash, zsh, and fish. Add the matching line to your shell startup file to complete commands, subcommands, and flags with Tab. + +```sh +# bash (~/.bashrc) +eval "$(dedalus completion bash)" + +# zsh (~/.zshrc) +eval "$(dedalus completion zsh)" -Streaming commands emit one result per line as the server sends it. Use `--max-items ` to stop after N items. +# fish (~/.config/fish/config.fish) +dedalus completion fish | source +```
-## WebSockets +## Manual Pages -WebSocket commands stay connected and stream messages. Use `--send ` to send a message (or pipe JSON/YAML on stdin) and `--max-items ` to bound output. +Installing the package globally also installs man pages. `man dedalus` lists every command, and each command has its own page named after the command with spaces and `:` replaced by `-`. + +```sh +man dedalus +man dedalus-- +```
@@ -62,9 +78,9 @@ Pass credentials to the generated client constructor. Environment variables are | Option | Type | Default | Description | | --- | --- | --- | --- | -| `--api-key-auth` | `string \| provider` | - | API key authentication using X-API-Key header Defaults to API_KEY_AUTH. | -| `--bearer-auth` | `string \| provider` | - | Dedalus API key in Authorization: Bearer . Defaults to BEARER_AUTH. | -| `--bearer` | `string \| provider` | - | API key authentication using Bearer token Defaults to BEARER. | +| `--api-key` | `string \| provider` | - | API key authentication using Bearer token Defaults to DEDALUS_API_KEY. | +| `--x-api-key` | `string \| provider` | - | API key authentication using X-API-Key header Defaults to DEDALUS_X_API_KEY. | +| `--bearer-auth` | `string \| provider` | - | Dedalus API key in Authorization: Bearer . Defaults to DEDALUS_BEARER_AUTH. | Declared schemes: @@ -78,8 +94,6 @@ Declared schemes: Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. -Documented error statuses: `400`, `401`, `403`, `409`, `429`, `500`, `502`, `503`, `default`. -
## Client Options @@ -122,11 +136,3 @@ Generated clients support request timeouts and retry temporary failures such as - Node.js 20 or newer Powered by Scalar. - - -## Contributions - -This SDK is generated programmatically. Manual edits to generated files will be -overwritten on the next build. - -### SDK created by [Scalar](https://www.scalar.com/?utm_source=dedalus-cloud-services-api-cli&utm_campaign=sdk) diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..896dc31 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,44 @@ +--- +name: dedalus-cli-sdk +description: "CLI SDK for Dedalus API. Use when writing CLI code that calls Dedalus API with the dedalus-cli package: installing it, constructing and authenticating the client, and calling API operations." +--- + +# Dedalus CLI SDK + +Generated CLI client for Dedalus API, published as `dedalus-cli`. Use the generated client instead of hand-writing HTTP requests. + +## Install + +```sh +# npm (requires Node.js) +npm install -g dedalus-cli +``` + +## Client setup and authentication + +Provide credentials using the options below. Environment variables are read automatically when the target runtime supports them: + +- `--api-key` (env: `DEDALUS_API_KEY`) — API key authentication using Bearer token +- `--x-api-key` (env: `DEDALUS_X_API_KEY`) — API key authentication using X-API-Key header +- `--bearer-auth` (env: `DEDALUS_BEARER_AUTH`) — Dedalus API key in Authorization: Bearer . + +## Calling operations + +```sh +dedalus [resource] [command] [flags] +``` + +Method names, parameter shapes, and response types are generated from the API description — do not guess them. Look up the exact call signature in [api.md](./api.md) before writing a call. + +## Error handling + +Non-success responses throw generated API errors. Error objects expose status, headers, response body, and request metadata where the target runtime supports it. + +## Requirements + +- Node.js 20 or newer + +## Reference files + +- [README.md](./README.md) — full feature tour: client options, retries and timeouts, logging. +- [api.md](./api.md) — complete catalogue of every operation with request and response types. diff --git a/VERSIONING.md b/VERSIONING.md index 8f16c2c..662f2b6 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -5,3 +5,82 @@ This SDK is configured with the `manual` versioning policy. - `manual`: package versions are set explicitly before release. - `semver`: releases should follow semantic versioning based on API and SDK surface changes. - `calendar`: releases should use a calendar-derived version chosen by the release workflow or maintainer. + +## Branches and releases + +This repository follows a three-branch flow, managed by the Scalar platform together +with the generated workflows (which need only the default `GITHUB_TOKEN` — no extra +token required): + +- **`scalar-generated`** — pristine generator output. Pushed by the Scalar platform; do + not commit here. +- **`scalar-next`** — generated output merged with this repository's custom code. Commit + your customizations here (directly or via PRs). The Scalar platform merges each + regeneration into this branch and keeps the release PR up to date; merge conflicts + arrive as a PR from `scalar-merge-conflict` for you to resolve. +- **Default branch** — seeded from the first generated snapshot, then only ever receives + released states, each one the merge of a release PR. + +Release PRs are opened by the Scalar platform from `scalar-next` against the default +branch — so the PR diff shows the full pending release — and are versioned from +[Conventional Commits](https://www.conventionalcommits.org). Merging a release PR tags the +release, publishes it, and syncs the version bump and changelog back to `scalar-next`. +Pre-1.0, breaking changes bump the minor version. + +### Choosing an exact version + +To release a specific version — `1.0.0`, a hotfix number, anything the commit history would +not have picked — **edit the release PR title** to the version you want: + +```text +release: 1.0.0 +``` + +The `Release PR version` check turns red as soon as you save, because the version in the +title no longer matches the version committed in the PR. The Scalar platform then re-renders +the release PR at your version (changelog, manifest, and every version-bearing file), the +title comes back as `release: 1.0.0`, and the check turns green. **Wait for it to be green +before merging** — merging in between would tag a release whose own files still carry the +old version. + +The git-native equivalent, if you would rather not touch the PR: push an empty commit with a +`Release-As` footer to `scalar-next`. This is exactly what the title edit does for you. + +```sh +git commit --allow-empty -m "chore: release 1.0.0" -m "Release-As: 1.0.0" +``` + +### When a release PR does not merge cleanly + +Nothing is ever force-pushed automatically: a release PR that conflicts with the default +branch simply cannot be merged, and GitHub disables its merge button. The two causes have +different fixes: + +- **The default branch received direct commits** (for example a hotfix) that are not in + `scalar-next`. Land those commits on `scalar-next` (merge the default branch into it, or + cherry-pick), and the refreshed release PR merges cleanly again. Do not force-push — + that would discard the direct commits. +- **The repository was adopted with pre-existing content**, so the default branch shares + no history with `scalar-next`. The Scalar platform adds a checkbox to the release PR + description offering to replace the default branch with this release; checking it + authorizes the platform to force-push the released state over the old content. This is + destructive for anything on the default branch that never reached `scalar-next`, which + is why it requires that explicit opt-in. + +### Repository prerequisites + +- Branch protection on `scalar-next` and the default branch must allow the Scalar + platform and the `github-actions` bot to push (or be left unprotected); the default + branch only ever advances by merging release PRs, and `scalar-next` receives each + released state back from the release workflow. +- No Actions settings changes are required: the generated workflows declare their own + permissions and never create pull requests. +- If this package publishes through OIDC trusted publishing (for example PyPI or npm), + register the trusted publisher on the registry against the **`release-please.yml`** + workflow filename. Merging a release PR publishes from the `publish` job inside that + same workflow run (checked out at the released tag), so the automated path's OIDC + claims name that file — and, because nothing is dispatched, releasing works from any + release branch, not only the repository default branch. `sdk-release.yml` exists for + manual re-publishes at an existing tag; register it as an additional trusted publisher + only if you use it. If the publish job is configured with a deployment environment, + include that environment in the registration too. diff --git a/api.md b/api.md index 8810bf3..8aafa53 100644 --- a/api.md +++ b/api.md @@ -2,249 +2,4 @@ Complete reference of every operation, grouped by resource. See [the README](./README.md) for usage and configuration. -## Contents - -- [`MachineLifecycle`](#machinelifecycle) - - [List machines](#list-machines) - - [Create machine](#create-machine) - - [Destroy machine](#destroy-machine) - - [Get machine](#get-machine) - - [Update machine](#update-machine) - - [List artifacts](#list-artifacts) - - [Delete artifact](#delete-artifact) - - [Get artifact](#get-artifact) - - [List executions](#list-executions) - - [Create execution](#create-execution) - - [Delete execution](#delete-execution) - - [Get execution](#get-execution) - - [List execution events](#list-execution-events) - - [Get execution output](#get-execution-output) - - [List previews](#list-previews) - - [Create preview](#create-preview) - - [Delete preview](#delete-preview) - - [Get preview](#get-preview) - - [Sleep a running machine](#sleep-a-running-machine) - - [List SSH sessions](#list-ssh-sessions) - - [Create SSH session](#create-ssh-session) - - [Delete SSH session](#delete-ssh-session) - - [Get SSH session](#get-ssh-session) - - [Watch machine lifecycle status](#watch-machine-lifecycle-status) - - [List terminals](#list-terminals) - - [Create terminal](#create-terminal) - - [Delete terminal](#delete-terminal) - - [Get terminal](#get-terminal) - - [Connect to terminal WebSocket stream](#connect-to-terminal-websocket-stream) - - [Wake a sleeping machine](#wake-a-sleeping-machine) -- [`Usage`](#usage) - - [Get usage summary](#get-usage-summary) - - [`Usage Machines`](#usage-machines) - - [List machine compute usage breakdown](#list-machine-compute-usage-breakdown) - - [List machine storage usage breakdown](#list-machine-storage-usage-breakdown) - -## `MachineLifecycle` - -### List machines - -```sh -dedalus machine-lifecycle list --bearer "$BEARER" -``` - -### Create machine - -```sh -dedalus machine-lifecycle create --bearer "$BEARER" --memory-mib '1' --storage-gib '1' --vcpu '1' -``` - -### Destroy machine - -```sh -dedalus machine-lifecycle delete --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Get machine - -```sh -dedalus machine-lifecycle retrieve --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Update machine - -```sh -dedalus machine-lifecycle patch --bearer "$BEARER" --machine-id 'machine_id' -``` - -### List artifacts - -```sh -dedalus machine-lifecycle list-artifacts --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Delete artifact - -```sh -dedalus machine-lifecycle delete-artifact --bearer "$BEARER" --machine-id 'machine_id' --artifact-id 'artifact_id' -``` - -### Get artifact - -```sh -dedalus machine-lifecycle retrieve-artifact --bearer "$BEARER" --machine-id 'machine_id' --artifact-id 'artifact_id' -``` - -### List executions - -```sh -dedalus machine-lifecycle list-executions --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Create execution - -```sh -dedalus machine-lifecycle create-execution --bearer "$BEARER" --machine-id 'machine_id' --command '["command"]' -``` - -### Delete execution - -```sh -dedalus machine-lifecycle delete-execution --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' -``` - -### Get execution - -```sh -dedalus machine-lifecycle retrieve-execution --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' -``` - -### List execution events - -```sh -dedalus machine-lifecycle list-execution-events --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' -``` - -### Get execution output - -```sh -dedalus machine-lifecycle list-execution-output --bearer "$BEARER" --machine-id 'machine_id' --execution-id 'execution_id' -``` - -### List previews - -```sh -dedalus machine-lifecycle list-previews --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Create preview - -```sh -dedalus machine-lifecycle create-preview --bearer "$BEARER" --machine-id 'machine_id' --port '1' -``` - -### Delete preview - -```sh -dedalus machine-lifecycle delete-preview --bearer "$BEARER" --machine-id 'machine_id' --preview-id 'preview_id' -``` - -### Get preview - -```sh -dedalus machine-lifecycle retrieve-preview --bearer "$BEARER" --machine-id 'machine_id' --preview-id 'preview_id' -``` - -### Sleep a running machine - -```sh -dedalus machine-lifecycle sleep --bearer "$BEARER" --machine-id 'machine_id' -``` - -### List SSH sessions - -```sh -dedalus machine-lifecycle list-ssh-sessions --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Create SSH session - -```sh -dedalus machine-lifecycle create-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --public-key 'public_key' -``` - -### Delete SSH session - -```sh -dedalus machine-lifecycle delete-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --session-id 'session_id' -``` - -### Get SSH session - -```sh -dedalus machine-lifecycle retrieve-ssh-session --bearer "$BEARER" --machine-id 'machine_id' --session-id 'session_id' -``` - -### Watch machine lifecycle status - -Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state. - -```sh -dedalus machine-lifecycle watch-status --bearer "$BEARER" --machine-id 'machine_id' --max-items 10 -``` - -### List terminals - -```sh -dedalus machine-lifecycle list-terminals --bearer "$BEARER" --machine-id 'machine_id' -``` - -### Create terminal - -```sh -dedalus machine-lifecycle create-terminal --bearer "$BEARER" --machine-id 'machine_id' --height '1' --width '1' -``` - -### Delete terminal - -```sh -dedalus machine-lifecycle delete-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' -``` - -### Get terminal - -```sh -dedalus machine-lifecycle retrieve-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' -``` - -### Connect to terminal WebSocket stream - -Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields. - -```sh -dedalus machine-lifecycle connect-terminal --bearer "$BEARER" --machine-id 'machine_id' --terminal-id 'terminal_id' --max-items 10 -``` - -### Wake a sleeping machine - -```sh -dedalus machine-lifecycle wake --bearer "$BEARER" --machine-id 'machine_id' -``` - -## `Usage` - -### Get usage summary - -```sh -dedalus usage list --bearer "$BEARER" -``` - -### `Usage Machines` - -#### List machine compute usage breakdown - -```sh -dedalus usage:machines list-compute-usage --bearer "$BEARER" -``` - -#### List machine storage usage breakdown - -```sh -dedalus usage:machines list-storage-usage --bearer "$BEARER" -``` +No operations were discovered in the spec. diff --git a/man/dedalus-completion.1 b/man/dedalus-completion.1 new file mode 100644 index 0000000..6785513 --- /dev/null +++ b/man/dedalus-completion.1 @@ -0,0 +1,32 @@ +.\" File generated from our OpenAPI spec by Scalar. Do not edit. +.TH "DEDALUS\-COMPLETION" "1" "" "dedalus" "Dedalus Manual" +.SH "NAME" +dedalus\-completion \- print a shell completion script +.SH "SYNOPSIS" +.B dedalus completion +\fIshell\fR +.SH "DESCRIPTION" +Prints a completion script for the named shell to standard output. The script is generated from the same command table dedalus itself is built from, so it always describes this version's commands and flags. Supported shells are bash, zsh, and fish. +.SH "ARGUMENTS" +.TP +\fIshell\fR +The shell to print a script for: bash, zsh, and fish. Required. +.SH "EXAMPLES" +Load completions for the current shell by adding one of these to its startup file: +.PP +.RS 4 +.nf +eval "$(dedalus completion bash)" +eval "$(dedalus completion zsh)" +dedalus completion fish | source +.fi +.RE +.SH "EXIT STATUS" +.TP +\fB0\fR +The command completed successfully. +.TP +\fB1\fR +A usage error, or the request failed. The error is printed to standard error. +.SH "SEE ALSO" +.BR dedalus (1) diff --git a/man/dedalus.1 b/man/dedalus.1 new file mode 100644 index 0000000..6858410 --- /dev/null +++ b/man/dedalus.1 @@ -0,0 +1,97 @@ +.\" File generated from our OpenAPI spec by Scalar. Do not edit. +.TH "DEDALUS" "1" "" "dedalus" "Dedalus Manual" +.SH "NAME" +dedalus \- command line interface for the Dedalus REST API +.SH "SYNOPSIS" +.B dedalus +[\fIglobal options\fR] \fIcommand\fR [\fIsubcommand\fR] [\fIarguments\fR] [\fIoptions\fR] +.SH "DESCRIPTION" +dedalus calls the Dedalus REST API from the command line. Commands are grouped by resource, and every command prints its result to standard output in the configured format. +.PP +Controlplane API for Dedalus Cloud Services (DCS). +.SH "GLOBAL OPTIONS" +.TP +\fB\-\-base\-url\fR \fI\fR +Override the base URL for API requests. +.TP +\fB\-\-timeout\fR \fI\fR +Request timeout in milliseconds. +.TP +\fB\-\-max\-retries\fR \fI\fR +Number of retries for retryable failures. +.TP +\fB\-\-format\fR \fI\fR +Output format: auto, json, jsonl, pretty, raw, yaml. +.TP +\fB\-\-format\-error\fR \fI\fR +Error output format: auto, json, jsonl, pretty, raw, yaml. +.TP +\fB\-\-transform\fR \fI\fR +Dot\-path transform for data output. +.TP +\fB\-\-transform\-error\fR \fI\fR +Dot\-path transform for error output. +.TP +\fB\-r\fR, \fB\-\-raw\-output\fR +Print transformed string values without JSON quotes. +.TP +\fB\-\-debug\fR +Enable SDK debug logging on standard error. +.TP +\fB\-\-api\-key\fR \fI\fR +API key authentication using Bearer token. Can also be set with the DEDALUS_API_KEY environment variable. +.TP +\fB\-\-x\-api\-key\fR \fI\fR +API key authentication using X\-API\-Key header. Can also be set with the DEDALUS_X_API_KEY environment variable. +.TP +\fB\-\-bearer\-auth\fR \fI\fR +Dedalus API key in Authorization: Bearer . Can also be set with the DEDALUS_BEARER_AUTH environment variable. +.TP +\fB\-\-provider\fR \fI\fR +Provider name for BYOK mode. Can also be set with the DEDALUS_PROVIDER environment variable. +.TP +\fB\-\-provider\-key\fR \fI\fR +Provider API key for BYOK mode. Can also be set with the DEDALUS_PROVIDER_KEY environment variable. +.TP +\fB\-\-provider\-model\fR \fI\fR +Model identifier for BYOK provider. Can also be set with the DEDALUS_PROVIDER_MODEL environment variable. +.SH "COMMANDS" +.TP +\fBdedalus completion\fR +Print a shell completion script. See dedalus\-completion(1). +.SH "SHELL COMPLETION" +dedalus completion prints a completion script for bash, zsh, and fish. Load it from your shell startup file: +.PP +.RS 4 +.nf +eval "$(dedalus completion bash)" +eval "$(dedalus completion zsh)" +dedalus completion fish | source +.fi +.RE +.SH "ENVIRONMENT" +.TP +\fBDEDALUS_API_KEY\fR +Fallback for \-\-api\-key when the flag is not passed. +.TP +\fBDEDALUS_X_API_KEY\fR +Fallback for \-\-x\-api\-key when the flag is not passed. +.TP +\fBDEDALUS_BEARER_AUTH\fR +Fallback for \-\-bearer\-auth when the flag is not passed. +.TP +\fBDEDALUS_PROVIDER\fR +Fallback for \-\-provider when the flag is not passed. +.TP +\fBDEDALUS_PROVIDER_KEY\fR +Fallback for \-\-provider\-key when the flag is not passed. +.TP +\fBDEDALUS_PROVIDER_MODEL\fR +Fallback for \-\-provider\-model when the flag is not passed. +.SH "EXIT STATUS" +.TP +\fB0\fR +The command completed successfully. +.TP +\fB1\fR +A usage error, or the request failed. The error is printed to standard error. diff --git a/openapi.augmented.json b/openapi.augmented.json index bf27b3f..e9e584c 100644 --- a/openapi.augmented.json +++ b/openapi.augmented.json @@ -160,7 +160,7 @@ ], "type": "object" }, - "CreatePreviewRequest": { + "CreatePortRequest": { "additionalProperties": false, "properties": { "port": { @@ -173,14 +173,6 @@ "https" ], "type": "string" - }, - "visibility": { - "enum": [ - "public", - "private", - "org" - ], - "type": "string" } }, "required": [ @@ -601,8 +593,19 @@ "format": "int64", "type": "integer" }, - "status": { - "$ref": "#/components/schemas/LifecycleStatus" + "phase": { + "enum": [ + "accepted", + "placement_pending", + "starting", + "running", + "stopping", + "sleeping", + "destroying", + "destroyed", + "failed" + ], + "type": "string" }, "storage_gib": { "format": "int64", @@ -621,7 +624,7 @@ "storage_gib", "autosleep_seconds", "desired_state", - "status" + "phase" ], "type": "object" }, @@ -737,8 +740,8 @@ "format": "date-time", "type": "string" }, - "latest_stripe_emitted_at": { - "description": "Latest Stripe emission timestamp for linked org buckets, when emitted.", + "latest_meter_emitted_at": { + "description": "Latest meter emission timestamp for linked org buckets, when emitted.", "format": "date-time", "type": "string" }, @@ -780,28 +783,8 @@ "description": "Stable fingerprint for the requested machine shape.", "type": "string" }, - "stripe_cpu_identifiers": { - "description": "Stripe CPU meter event identifiers linked to those org buckets.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, - "stripe_memory_identifiers": { - "description": "Stripe memory meter event identifiers linked to those org buckets.", - "items": { - "type": "string" - }, - "type": [ - "array", - "null" - ] - }, "window_count": { - "description": "Raw usage windows compacted into this row.", + "description": "Raw metering events compacted into this row.", "format": "int64", "type": "integer" } @@ -819,16 +802,64 @@ "memory_mib_seconds", "window_count", "last_window_end", - "org_metering_bucket_ids", - "stripe_cpu_identifiers", - "stripe_memory_identifiers" + "org_metering_bucket_ids" + ], + "type": "object" + }, + "MachineDetailResponse": { + "additionalProperties": false, + "properties": { + "autosleep_seconds": { + "description": "Seconds of inactivity before autosleep. 0 disables autosleep.", + "format": "int64", + "maximum": 9223372036, + "minimum": 0, + "type": "integer" + }, + "desired_state": { + "enum": [ + "running", + "sleeping", + "destroyed" + ], + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "memory_mib": { + "description": "Memory in MiB.", + "format": "int64", + "type": "integer" + }, + "status": { + "$ref": "#/components/schemas/LifecycleStatus" + }, + "storage_gib": { + "format": "int64", + "type": "integer" + }, + "vcpu": { + "description": "CPU in vCPUs.", + "format": "double", + "type": "number" + } + }, + "required": [ + "machine_id", + "vcpu", + "memory_mib", + "storage_gib", + "autosleep_seconds", + "desired_state", + "status" ], "type": "object" }, "MachineIDPathSegment": { "maxLength": 253, "minLength": 4, - "pattern": "^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "pattern": "^dm-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "type": "string" }, "MachineListItem": { @@ -861,8 +892,19 @@ "format": "int64", "type": "integer" }, - "status": { - "$ref": "#/components/schemas/LifecycleStatus" + "phase": { + "enum": [ + "accepted", + "placement_pending", + "starting", + "running", + "stopping", + "sleeping", + "destroying", + "destroyed", + "failed" + ], + "type": "string" }, "storage_gib": { "format": "int64", @@ -881,7 +923,7 @@ "storage_gib", "autosleep_seconds", "desired_state", - "status", + "phase", "created_at" ], "type": "object" @@ -907,6 +949,38 @@ ], "type": "object" }, + "MachineNetworkResponse": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "machine_id": { + "type": "string" + }, + "network_id": { + "type": "string" + }, + "network_name": { + "type": "string" + }, + "private_ipv4": { + "type": "string" + }, + "private_ipv6": { + "type": "string" + } + }, + "required": [ + "machine_id", + "network_id", + "network_name", + "private_ipv4", + "private_ipv6", + "hostname" + ], + "type": "object" + }, "MachineStorageUsageBody": { "additionalProperties": false, "properties": { @@ -951,8 +1025,8 @@ "format": "date-time", "type": "string" }, - "latest_stripe_emitted_at": { - "description": "Latest Stripe emission timestamp for the linked org bucket, when emitted.", + "latest_meter_emitted_at": { + "description": "Latest meter emission timestamp for the linked org bucket, when emitted.", "format": "date-time", "type": "string" }, @@ -973,10 +1047,6 @@ "description": "Allocated logical MiB-seconds for this machine.", "format": "int64", "type": "integer" - }, - "stripe_storage_identifier": { - "description": "Stripe storage meter event identifier linked to that org bucket.", - "type": "string" } }, "required": [ @@ -985,8 +1055,7 @@ "bucket_end", "logical_storage_bytes", "storage_mib_seconds", - "org_metering_bucket_id", - "stripe_storage_identifier" + "org_metering_bucket_id" ], "type": "object" }, @@ -1038,6 +1107,16 @@ "format": "date-time", "type": "string" }, + "meter_outbox": { + "description": "Pending meter outbox buckets grouped by resource.", + "items": { + "$ref": "#/components/schemas/MeteringOutboxBody" + }, + "type": [ + "array", + "null" + ] + }, "oldest_logical_storage_observed_at": { "description": "Oldest logical storage observation timestamp across current machine specs.", "format": "date-time", @@ -1047,32 +1126,27 @@ "description": "Machine storage gauges missing or older than the freshness window.", "format": "int64", "type": "integer" - }, - "stripe_outbox": { - "description": "Pending Stripe outbox buckets grouped by resource.", - "items": { - "$ref": "#/components/schemas/MeteringStripeOutboxBody" - }, - "type": [ - "array", - "null" - ] } }, "required": [ "generated_at", "clickhouse_raw_rows_near_ttl", "closed_through", - "stripe_outbox", + "meter_outbox", "stale_logical_storage_gauge_count" ], "type": "object" }, - "MeteringStripeOutboxBody": { + "MeteringOutboxBody": { "additionalProperties": false, "properties": { "claimed_not_submitted_buckets": { - "description": "Pending org buckets claimed by a sweeper but not marked submitted to Stripe.", + "description": "Pending org buckets claimed by a sweeper but not marked submitted to the provider.", + "format": "int64", + "type": "integer" + }, + "meter_emission_lag_seconds": { + "description": "Seconds since the oldest pending bucket ended.", "format": "int64", "type": "integer" }, @@ -1095,13 +1169,8 @@ "description": "Org metering bucket resource.", "type": "string" }, - "stripe_emission_lag_seconds": { - "description": "Seconds since the oldest pending bucket ended.", - "format": "int64", - "type": "integer" - }, "submitted_not_emitted_buckets": { - "description": "Pending org buckets marked submitted to Stripe but not marked emitted.", + "description": "Pending org buckets marked submitted to the provider but not marked emitted.", "format": "int64", "type": "integer" } @@ -1112,16 +1181,76 @@ "pending_unclaimed_buckets", "claimed_not_submitted_buckets", "submitted_not_emitted_buckets", - "stripe_emission_lag_seconds" + "meter_emission_lag_seconds" + ], + "type": "object" + }, + "NetworkGateway": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "kind": { + "enum": [ + "ssh", + "port" + ], + "type": "string" + }, + "port": { + "format": "int64", + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "protocol": { + "enum": [ + "ssh", + "https" + ], + "type": "string" + } + }, + "required": [ + "kind", + "protocol", + "hostname" + ], + "type": "object" + }, + "NetworkResponse": { + "additionalProperties": false, + "properties": { + "gateways": { + "items": { + "$ref": "#/components/schemas/NetworkGateway" + }, + "type": [ + "array", + "null" + ] + }, + "name": { + "type": "string" + }, + "network_id": { + "type": "string" + } + }, + "required": [ + "network_id", + "name", + "gateways" ], "type": "object" }, - "PreviewListResponse": { + "PortListResponse": { "additionalProperties": false, "properties": { "items": { "items": { - "$ref": "#/components/schemas/PreviewResponse" + "$ref": "#/components/schemas/PortResponse" }, "type": [ "array", @@ -1137,7 +1266,7 @@ ], "type": "object" }, - "PreviewResponse": { + "PortResponse": { "additionalProperties": false, "properties": { "created_at": { @@ -1161,7 +1290,7 @@ "format": "int64", "type": "integer" }, - "preview_id": { + "port_id": { "type": "string" }, "protocol": { @@ -1191,22 +1320,13 @@ }, "url": { "type": "string" - }, - "visibility": { - "enum": [ - "public", - "private", - "org" - ], - "type": "string" } }, "required": [ - "preview_id", + "port_id", "machine_id", "status", "port", - "visibility", "created_at" ], "type": "object" @@ -1652,7 +1772,7 @@ "type": "integer" }, "billed_logical_storage_mib_seconds": { - "description": "Closed billable logical MiB-seconds for the period, matching the Stripe storage meter.", + "description": "Closed billable logical MiB-seconds for the period.", "format": "int64", "type": "integer" }, @@ -1867,6 +1987,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -1877,7 +2004,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -1981,10 +2108,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -2045,23 +2171,6 @@ "summary": "List machines", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list()\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst list = await client.machineLifecycle.list();\nconsole.log(list);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.List(context.Background(), sdk.MachineLifecycleListParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "post": { @@ -2201,6 +2310,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -2211,7 +2327,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -2365,10 +2481,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -2429,23 +2544,6 @@ "summary": "Create machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create(\n memory_mib=0,\n storage_gib=0,\n vcpu=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst create = await client.machineLifecycle.create({\n memory_mib: 0,\n storage_gib: 0,\n vcpu: 0,\n});\nconsole.log(create);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.New(context.Background(), sdk.MachineLifecycleNewParams{\n\t\tCreateMachineRequest: sdk.CreateMachineRequest{\n\t\tMemoryMib: sdk.F[int64](0),\n\t\tStorageGib: sdk.F[int64](0),\n\t\tVcpu: sdk.F[float64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -2585,6 +2683,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -2595,7 +2700,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -2749,10 +2854,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -2813,23 +2917,6 @@ "summary": "Destroy machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst delete_ = await client.machineLifecycle.delete({\n machine_id: \"machineID\",\n});\nconsole.log(delete_);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Delete(context.Background(), sdk.MachineLifecycleDeleteParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "get": { @@ -2856,7 +2943,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LifecycleResponse" + "$ref": "#/components/schemas/MachineDetailResponse" } } }, @@ -2926,6 +3013,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -2936,7 +3030,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -3040,10 +3134,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -3104,23 +3197,6 @@ "summary": "Get machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieve = await client.machineLifecycle.retrieve({\n machine_id: \"machineID\",\n});\nconsole.log(retrieve);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Get(context.Background(), sdk.MachineLifecycleGetParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "patch": { @@ -3268,6 +3344,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -3278,7 +3361,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -3456,10 +3539,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -3474,10 +3556,9 @@ "sleep_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "sleep_lifecycle_routes" } } @@ -3531,23 +3612,6 @@ "summary": "Update machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.patch(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst patch = await client.machineLifecycle.patch({\n machine_id: \"machineID\",\n});\nconsole.log(patch);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Patch(context.Background(), sdk.MachineLifecyclePatchParams{\n\t\tMachineID: \"machineID\",\n\t\tUpdateMachineRequest: sdk.UpdateMachineRequest{},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -3656,6 +3720,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -3666,7 +3737,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -3770,10 +3841,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -3835,23 +3905,6 @@ "tags": [ "Machine Lifecycle", "Machine Artifacts" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_artifacts(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listArtifacts = await client.machineLifecycle.listArtifacts({\n machine_id: \"machineID\",\n});\nconsole.log(listArtifacts);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListArtifacts(context.Background(), sdk.MachineLifecycleListArtifactsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -3951,6 +4004,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -3961,7 +4021,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -4065,10 +4125,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -4130,23 +4189,6 @@ "tags": [ "Machine Lifecycle", "Machine Artifacts" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_artifact(\n machine_id=\"machineID\",\n artifact_id=\"artifactID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteArtifact = await client.machineLifecycle.deleteArtifact({\n machine_id: \"machineID\",\n artifact_id: \"artifactID\",\n});\nconsole.log(deleteArtifact);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteArtifact(context.Background(), sdk.MachineLifecycleDeleteArtifactParams{\n\t\tArtifactID: \"artifactID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "get": { @@ -4244,6 +4286,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -4254,7 +4303,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -4358,10 +4407,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -4423,23 +4471,6 @@ "tags": [ "Machine Lifecycle", "Machine Artifacts" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_artifact(\n machine_id=\"machineID\",\n artifact_id=\"artifactID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveArtifact = await client.machineLifecycle.retrieveArtifact({\n machine_id: \"machineID\",\n artifact_id: \"artifactID\",\n});\nconsole.log(retrieveArtifact);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetArtifact(context.Background(), sdk.MachineLifecycleGetArtifactParams{\n\t\tArtifactID: \"artifactID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -4548,6 +4579,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -4558,7 +4596,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -4662,10 +4700,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -4727,23 +4764,6 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_executions(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutions = await client.machineLifecycle.listExecutions({\n machine_id: \"machineID\",\n});\nconsole.log(listExecutions);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutions(context.Background(), sdk.MachineLifecycleListExecutionsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "post": { @@ -4851,6 +4871,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -4861,7 +4888,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -5015,10 +5042,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -5080,23 +5106,6 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_execution(\n machine_id=\"machineID\",\n command=[],\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createExecution = await client.machineLifecycle.createExecution({\n machine_id: \"machineID\",\n command: [],\n});\nconsole.log(createExecution);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewExecution(context.Background(), sdk.MachineLifecycleNewExecutionParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateExecutionRequest: sdk.CreateExecutionRequest{\n\t\tCommand: sdk.F[[]string]([]string{\"\"}),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -5196,6 +5205,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -5206,7 +5222,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -5310,10 +5326,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -5375,23 +5390,6 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_execution(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteExecution = await client.machineLifecycle.deleteExecution({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(deleteExecution);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteExecution(context.Background(), sdk.MachineLifecycleDeleteExecutionParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "get": { @@ -5489,6 +5487,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -5499,7 +5504,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -5603,10 +5608,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -5668,23 +5672,6 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_execution(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveExecution = await client.machineLifecycle.retrieveExecution({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(retrieveExecution);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetExecution(context.Background(), sdk.MachineLifecycleGetExecutionParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -5801,6 +5788,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -5811,7 +5805,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -5915,10 +5909,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -5980,23 +5973,6 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_execution_events(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutionEvents = await client.machineLifecycle.listExecutionEvents({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(listExecutionEvents);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutionEvents(context.Background(), sdk.MachineLifecycleListExecutionEventsParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -6096,6 +6072,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -6106,7 +6089,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -6210,10 +6193,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -6275,29 +6257,173 @@ "tags": [ "Machine Lifecycle", "Machine Executions" - ], - "x-scalar-examples": [ + ] + } + }, + "/v1/machines/{machine_id}/network": { + "get": { + "operationId": "getMachineNetwork", + "parameters": [ { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_execution_output(\n machine_id=\"machineID\",\n execution_id=\"executionID\",\n)\nprint(machine_lifecycle)" + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } }, { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listExecutionOutput = await client.machineLifecycle.listExecutionOutput({\n machine_id: \"machineID\",\n execution_id: \"executionID\",\n});\nconsole.log(listExecutionOutput);" + "in": "path", + "name": "machine_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/MachineIDPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MachineNetworkResponse" + } + } + }, + "description": "OK" }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListExecutionOutput(context.Background(), sdk.MachineLifecycleListExecutionOutputParams{\n\t\tExecutionID: \"executionID\",\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "required authorization scope is missing", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" } + }, + "summary": "Get machine network identity", + "tags": [ + "Machine Lifecycle" ] } }, - "/v1/machines/{machine_id}/previews": { + "/v1/machines/{machine_id}/ports": { "get": { - "operationId": "listMachinePreviews", + "operationId": "listMachinePorts", "parameters": [ { "in": "header", @@ -6337,7 +6463,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PreviewListResponse" + "$ref": "#/components/schemas/PortListResponse" } } }, @@ -6400,6 +6526,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -6410,7 +6543,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -6514,10 +6647,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -6575,31 +6707,14 @@ "description": "Error" } }, - "summary": "List previews", + "summary": "List ports", "tags": [ "Machine Lifecycle", - "Machine Previews" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_previews(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listPreviews = await client.machineLifecycle.listPreviews({\n machine_id: \"machineID\",\n});\nconsole.log(listPreviews);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListPreviews(context.Background(), sdk.MachineLifecycleListPreviewsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } + "Machine Ports" ] }, "post": { - "operationId": "createMachinePreview", + "operationId": "createMachinePort", "parameters": [ { "in": "header", @@ -6629,7 +6744,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreatePreviewRequest" + "$ref": "#/components/schemas/CreatePortRequest" } } }, @@ -6640,7 +6755,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PreviewResponse" + "$ref": "#/components/schemas/PortResponse" } } }, @@ -6703,6 +6818,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -6713,7 +6835,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -6867,10 +6989,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -6928,33 +7049,16 @@ "description": "Error" } }, - "summary": "Create preview", + "summary": "Create port", "tags": [ "Machine Lifecycle", - "Machine Previews" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_preview(\n machine_id=\"machineID\",\n port=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createPreview = await client.machineLifecycle.createPreview({\n machine_id: \"machineID\",\n port: 0,\n});\nconsole.log(createPreview);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewPreview(context.Background(), sdk.MachineLifecycleNewPreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tCreatePreviewRequest: sdk.CreatePreviewRequest{\n\t\tPort: sdk.F[int64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } + "Machine Ports" ] } }, - "/v1/machines/{machine_id}/previews/{preview_id}": { + "/v1/machines/{machine_id}/ports/{port_id}": { "delete": { - "operationId": "deleteMachinePreview", + "operationId": "deleteMachinePort", "parameters": [ { "in": "header", @@ -6973,7 +7077,7 @@ }, { "in": "path", - "name": "preview_id", + "name": "port_id", "required": true, "schema": { "$ref": "#/components/schemas/PublicPathSegment" @@ -6985,7 +7089,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PreviewResponse" + "$ref": "#/components/schemas/PortResponse" } } }, @@ -7048,6 +7152,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -7058,7 +7169,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -7162,10 +7273,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -7223,31 +7333,14 @@ "description": "Error" } }, - "summary": "Delete preview", + "summary": "Delete port", "tags": [ "Machine Lifecycle", - "Machine Previews" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_preview(\n machine_id=\"machineID\",\n preview_id=\"previewID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deletePreview = await client.machineLifecycle.deletePreview({\n machine_id: \"machineID\",\n preview_id: \"previewID\",\n});\nconsole.log(deletePreview);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeletePreview(context.Background(), sdk.MachineLifecycleDeletePreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tPreviewID: \"previewID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } + "Machine Ports" ] }, "get": { - "operationId": "getMachinePreview", + "operationId": "getMachinePort", "parameters": [ { "in": "header", @@ -7266,7 +7359,7 @@ }, { "in": "path", - "name": "preview_id", + "name": "port_id", "required": true, "schema": { "$ref": "#/components/schemas/PublicPathSegment" @@ -7278,7 +7371,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PreviewResponse" + "$ref": "#/components/schemas/PortResponse" } } }, @@ -7341,6 +7434,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -7351,7 +7451,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -7455,10 +7555,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -7516,27 +7615,10 @@ "description": "Error" } }, - "summary": "Get preview", + "summary": "Get port", "tags": [ "Machine Lifecycle", - "Machine Previews" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_preview(\n machine_id=\"machineID\",\n preview_id=\"previewID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrievePreview = await client.machineLifecycle.retrievePreview({\n machine_id: \"machineID\",\n preview_id: \"previewID\",\n});\nconsole.log(retrievePreview);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetPreview(context.Background(), sdk.MachineLifecycleGetPreviewParams{\n\t\tMachineID: \"machineID\",\n\t\tPreviewID: \"previewID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } + "Machine Ports" ] } }, @@ -7676,6 +7758,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -7686,7 +7775,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -7860,10 +7949,9 @@ "sleep_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "sleep_lifecycle_routes" } } @@ -7917,23 +8005,6 @@ "summary": "Sleep a running machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.sleep(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst sleep = await client.machineLifecycle.sleep({\n machine_id: \"machineID\",\n});\nconsole.log(sleep);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Sleep(context.Background(), sdk.MachineLifecycleSleepParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -8042,6 +8113,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -8052,7 +8130,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -8156,10 +8234,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -8221,23 +8298,6 @@ "tags": [ "Machine Lifecycle", "Machine SSH" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_ssh_sessions(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listSSHSessions = await client.machineLifecycle.listSSHSessions({\n machine_id: \"machineID\",\n});\nconsole.log(listSSHSessions);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListSSHSessions(context.Background(), sdk.MachineLifecycleListSSHSessionsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "post": { @@ -8345,6 +8405,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -8355,7 +8422,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -8509,10 +8576,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -8574,23 +8640,6 @@ "tags": [ "Machine Lifecycle", "Machine SSH" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_ssh_session(\n machine_id=\"machineID\",\n public_key=\"\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createSSHSession = await client.machineLifecycle.createSSHSession({\n machine_id: \"machineID\",\n public_key: \"\",\n});\nconsole.log(createSSHSession);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewSSHSession(context.Background(), sdk.MachineLifecycleNewSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateSSHSessionRequest: sdk.CreateSSHSessionRequest{\n\t\tPublicKey: sdk.F[string](\"\"),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -8690,6 +8739,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -8700,7 +8756,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -8804,10 +8860,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -8869,23 +8924,6 @@ "tags": [ "Machine Lifecycle", "Machine SSH" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_ssh_session(\n machine_id=\"machineID\",\n session_id=\"sessionID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteSSHSession = await client.machineLifecycle.deleteSSHSession({\n machine_id: \"machineID\",\n session_id: \"sessionID\",\n});\nconsole.log(deleteSSHSession);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteSSHSession(context.Background(), sdk.MachineLifecycleDeleteSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tSessionID: \"sessionID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "get": { @@ -8983,6 +9021,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -8993,7 +9038,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -9097,10 +9142,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -9162,23 +9206,6 @@ "tags": [ "Machine Lifecycle", "Machine SSH" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_ssh_session(\n machine_id=\"machineID\",\n session_id=\"sessionID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveSSHSession = await client.machineLifecycle.retrieveSSHSession({\n machine_id: \"machineID\",\n session_id: \"sessionID\",\n});\nconsole.log(retrieveSSHSession);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetSSHSession(context.Background(), sdk.MachineLifecycleGetSSHSessionParams{\n\t\tMachineID: \"machineID\",\n\t\tSessionID: \"sessionID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -9282,6 +9309,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -9292,7 +9326,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -9335,23 +9369,6 @@ "summary": "Watch machine lifecycle status", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nstream = client.machine_lifecycle.watch_status(\n machine_id=\"machineID\",\n)\nfor event in stream:\n print(event)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst stream = await client.machineLifecycle.watchStatus({\n machine_id: \"machineID\",\n});\nfor await (const event of stream) {\n console.log(event);\n}" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tstream := client.MachineLifecycle.WatchStatusStreaming(context.Background(), sdk.MachineLifecycleWatchStatusParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tdefer stream.Close()\n\tfor stream.Next() {\n\t\tevent := stream.Current()\n\t\tfmt.Println(event)\n\t}\n\tif err := stream.Err(); err != nil {\n\t\tpanic(err)\n\t}\n}" - } ] } }, @@ -9460,6 +9477,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -9470,7 +9494,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -9574,10 +9598,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -9639,23 +9662,6 @@ "tags": [ "Machine Lifecycle", "Machine Terminals" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.list_terminals(\n machine_id=\"machineID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listTerminals = await client.machineLifecycle.listTerminals({\n machine_id: \"machineID\",\n});\nconsole.log(listTerminals);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.ListTerminals(context.Background(), sdk.MachineLifecycleListTerminalsParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "post": { @@ -9763,6 +9769,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -9773,7 +9786,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -9927,10 +9940,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -9992,23 +10004,6 @@ "tags": [ "Machine Lifecycle", "Machine Terminals" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.create_terminal(\n machine_id=\"machineID\",\n height=0,\n width=0,\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst createTerminal = await client.machineLifecycle.createTerminal({\n machine_id: \"machineID\",\n height: 0,\n width: 0,\n});\nconsole.log(createTerminal);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.NewTerminal(context.Background(), sdk.MachineLifecycleNewTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tCreateTerminalRequest: sdk.CreateTerminalRequest{\n\t\tHeight: sdk.F[int64](0),\n\t\tWidth: sdk.F[int64](0),\n\t},\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -10108,6 +10103,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -10118,7 +10120,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -10222,10 +10224,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -10287,23 +10288,6 @@ "tags": [ "Machine Lifecycle", "Machine Terminals" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.delete_terminal(\n machine_id=\"machineID\",\n terminal_id=\"terminalID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst deleteTerminal = await client.machineLifecycle.deleteTerminal({\n machine_id: \"machineID\",\n terminal_id: \"terminalID\",\n});\nconsole.log(deleteTerminal);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.DeleteTerminal(context.Background(), sdk.MachineLifecycleDeleteTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tTerminalID: \"terminalID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] }, "get": { @@ -10401,6 +10385,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -10411,7 +10402,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -10515,10 +10506,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -10580,23 +10570,6 @@ "tags": [ "Machine Lifecycle", "Machine Terminals" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.retrieve_terminal(\n machine_id=\"machineID\",\n terminal_id=\"terminalID\",\n)\nprint(machine_lifecycle)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst retrieveTerminal = await client.machineLifecycle.retrieveTerminal({\n machine_id: \"machineID\",\n terminal_id: \"terminalID\",\n});\nconsole.log(retrieveTerminal);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.GetTerminal(context.Background(), sdk.MachineLifecycleGetTerminalParams{\n\t\tMachineID: \"machineID\",\n\t\tTerminalID: \"terminalID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" - } ] } }, @@ -10694,6 +10667,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -10704,7 +10684,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -10870,10 +10850,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "read_lifecycle_routes" } } @@ -11064,6 +11043,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -11074,7 +11060,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -11241,10 +11227,9 @@ "mutation_limiter_unavailable": { "value": { "error_code": "DEPENDENCY_UNAVAILABLE", - "message": "mutation rate limiter unavailable", + "message": "mutation rate limit check is unavailable", "retryable": true, "details": { - "rate_limit_backend": "redis", "rate_limit_scope": "mutating_lifecycle_routes" } } @@ -11305,23 +11290,167 @@ "summary": "Wake a sleeping machine", "tags": [ "Machine Lifecycle" - ], - "x-scalar-examples": [ + ] + } + }, + "/v1/networks/{network_id}": { + "get": { + "operationId": "getNetwork", + "parameters": [ { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine_lifecycle = client.machine_lifecycle.wake(\n machine_id=\"machineID\",\n idempotency_key=\"\",\n)\nprint(machine_lifecycle)" + "in": "header", + "name": "X-Dedalus-Org-Id", + "schema": { + "type": "string" + } }, { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst wake = await client.machineLifecycle.wake({\n machine_id: \"machineID\",\n});\nconsole.log(wake);" + "in": "path", + "name": "network_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/PublicPathSegment" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkResponse" + } + } + }, + "description": "OK" }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachineLifecycle, err := client.MachineLifecycle.Wake(context.Background(), sdk.MachineLifecycleWakeParams{\n\t\tMachineID: \"machineID\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machineLifecycle)\n}" + "401": { + "content": { + "application/json": { + "examples": { + "invalid_key": { + "value": { + "error_code": "AUTH_INVALID", + "message": "invalid Dedalus API key", + "retryable": false + } + }, + "missing_key": { + "value": { + "error_code": "AUTH_REQUIRED", + "message": "missing Dedalus API key", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Authentication required" + }, + "403": { + "content": { + "application/json": { + "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, + "org_mismatch": { + "value": { + "error_code": "AUTH_ORG_MISMATCH", + "message": "org scope does not match API key ownership", + "retryable": false + } + }, + "scope_forbidden": { + "value": { + "error_code": "AUTH_SCOPE_FORBIDDEN", + "message": "required authorization scope is missing", + "retryable": false + } + } + }, + "schema": { + "additionalProperties": false, + "properties": { + "details": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retry_after_ms": { + "format": "int64", + "type": "integer" + }, + "retryable": { + "type": "boolean" + } + }, + "required": [ + "error_code", + "message", + "retryable" + ], + "type": "object" + } + } + }, + "description": "Forbidden" + }, + "default": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + }, + "description": "Error" } + }, + "summary": "Get network details", + "tags": [ + "Machine Lifecycle" ] } }, @@ -11445,6 +11574,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -11455,7 +11591,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -11583,23 +11719,6 @@ "tags": [ "Usage", "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nusage = client.usage.list()\nprint(usage)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst list = await client.usage.list();\nconsole.log(list);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tusage, err := client.Usage.List(context.Background(), sdk.UsageListParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(usage)\n}" - } ] } }, @@ -11753,6 +11872,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -11763,7 +11889,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -11891,23 +12017,6 @@ "tags": [ "Usage", "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine = client.usage.machines.list_compute_usage()\nprint(machine)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listComputeUsage = await client.usage.machines.listComputeUsage();\nconsole.log(listComputeUsage);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachine, err := client.Usage.Machines.ListComputeUsage(context.Background(), sdk.UsageMachineListComputeUsageParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machine)\n}" - } ] } }, @@ -12051,6 +12160,13 @@ "content": { "application/json": { "examples": { + "beta_access_required": { + "value": { + "error_code": "BETA_ACCESS_REQUIRED", + "message": "DCS is in private beta; this org has not been admitted", + "retryable": false + } + }, "org_mismatch": { "value": { "error_code": "AUTH_ORG_MISMATCH", @@ -12061,7 +12177,7 @@ "scope_forbidden": { "value": { "error_code": "AUTH_SCOPE_FORBIDDEN", - "message": "internal service authorization is required", + "message": "required authorization scope is missing", "retryable": false } } @@ -12189,23 +12305,6 @@ "tags": [ "Usage", "Machine Lifecycle" - ], - "x-scalar-examples": [ - { - "lang": "Python", - "label": "Python", - "source": "import os\n\nfrom dedalus import Dedalus\n\nclient = Dedalus(\n bearer=os.environ.get(\"BEARER\"),\n)\n\nmachine = client.usage.machines.list_storage_usage()\nprint(machine)" - }, - { - "lang": "TypeScript", - "label": "TypeScript", - "source": "import Dedalus from \"dedalus\";\n\nconst client = new Dedalus({\n bearer: process.env[\"BEARER\"], // defaults to the BEARER env var\n environment: \"production\",\n});\n\nconst listStorageUsage = await client.usage.machines.listStorageUsage();\nconsole.log(listStorageUsage);" - }, - { - "lang": "Go", - "label": "Go", - "source": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\tsdk \"dedalus\"\n\t\"dedalus/option\"\n)\n\nfunc main() {\n\tclient := sdk.NewClient(\n\t\toption.WithBearer(os.Getenv(\"BEARER\")),\n\t\toption.WithAPIKey(os.Getenv(\"DEDALUS_API_KEY\")),\n\t)\n\n\tmachine, err := client.Usage.Machines.ListStorageUsage(context.Background(), sdk.UsageMachineListStorageUsageParams{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(machine)\n}" - } ] } } diff --git a/package.json b/package.json index fff5a56..8baf425 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dedalus-cli", - "version": "0.1.3", + "version": "0.1.0", "description": "Controlplane API for Dedalus Cloud Services (DCS).", "type": "module", "bin": { @@ -20,7 +20,13 @@ }, "files": [ "dist", - "api.md" + "api.md", + "man", + "SKILL.md" + ], + "man": [ + "./man/dedalus.1", + "./man/dedalus-completion.1" ], "scripts": { "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node scripts/finalize-build.mjs", @@ -29,21 +35,11 @@ "dependencies": { "ansis": "^4.3.0", "commander": "^14.0.3", - "yaml": "^2.9.0", - "ws": "^8.18.0" + "yaml": "^2.9.0" }, "devDependencies": { "@types/node": "^20.17.0", - "@types/ws": "^8.5.13", "typescript": "^6.0.0" }, - "peerDependencies": { - "ws": "^8.18.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - } - }, "license": "Apache-2.0" } diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..0b4124c --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,47 @@ +{ + "packages": { + ".": { + "release-type": "node", + "extra-files": [ + "src/commands/index.ts", + "src/sdk/version.ts" + ] + } + }, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "include-component-in-tag": false, + "pull-request-title-pattern": "release: ${version}", + "pull-request-header": "Automated Release PR", + "pull-request-footer": "The semver version number is based on included commit messages. To release a specific version,\nedit this pull request title to `release: 1.2.3` — Scalar re-creates this pull request at that\nversion, so wait for the `Release PR version` check to pass before merging.\n\nBrought to you by [Scalar](https://scalar.com)", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "refactor", + "section": "Refactors" + } + ] +} diff --git a/scalar-sdk.manifest.json b/scalar-sdk.manifest.json index 7e51fcf..928709f 100644 --- a/scalar-sdk.manifest.json +++ b/scalar-sdk.manifest.json @@ -1,17 +1,15 @@ { "name": "Dedalus", "slug": "dedalus", - "version": "0.1.4", + "generatorVersion": "0.23.6", "servers": [ "https://dcs.dedaluslabs.ai" ], "environments": { - "official_dcs_api": "https://dcs.dedaluslabs.ai", "production": "https://api.dedaluslabs.ai" }, "environmentOrder": [ - "production", - "official_dcs_api" + "production" ], "auth": [ "apiKey", @@ -36,8372 +34,9 @@ } ], "clientHeaderParams": [], - "schemas": [ - { - "name": "ArtifactListResponse", - "source": "ArtifactListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "ArtifactResponse" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ArtifactRef", - "source": "ArtifactRef", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "artifact_id", - "publicName": "artifact_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "name", - "publicName": "name", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ArtifactResponse", - "source": "ArtifactResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "artifact_id", - "publicName": "artifact_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "download_url", - "publicName": "download_url", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "execution_id", - "publicName": "execution_id", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "expires_at", - "publicName": "expires_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "mime_type", - "publicName": "mime_type", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "name", - "publicName": "name", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "sha256", - "publicName": "sha256", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "size_bytes", - "publicName": "size_bytes", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateExecutionRequest", - "source": "CreateExecutionRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "command", - "publicName": "command", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "cwd", - "publicName": "cwd", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "env", - "publicName": "env", - "required": false, - "deprecated": false, - "type": { - "kind": "record", - "value": { - "kind": "primitive", - "type": "string" - }, - "propertyNames": { - "kind": "primitive", - "type": "string" - } - } - }, - { - "name": "stdin", - "publicName": "stdin", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "timeout_ms", - "publicName": "timeout_ms", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateMachineRequest", - "source": "CreateMachineRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "autosleep", - "publicName": "autosleep", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "memory_mib", - "publicName": "memory_mib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "storage_gib", - "publicName": "storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "vcpu", - "publicName": "vcpu", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "number", - "format": "double" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreatePreviewRequest", - "source": "CreatePreviewRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "port", - "publicName": "port", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "protocol", - "publicName": "protocol", - "required": false, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "http", - "https" - ], - "names": [ - "HTTP", - "Https" - ], - "deprecations": [ - false, - false - ] - } - }, - { - "name": "visibility", - "publicName": "visibility", - "required": false, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "public", - "private", - "org" - ], - "names": [ - "Public", - "Private", - "Org" - ], - "deprecations": [ - false, - false, - false - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateSshSessionRequest", - "source": "CreateSshSessionRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "public_key", - "publicName": "public_key", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateTerminalRequest", - "source": "CreateTerminalRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "cwd", - "publicName": "cwd", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "env", - "publicName": "env", - "required": false, - "deprecated": false, - "type": { - "kind": "record", - "value": { - "kind": "primitive", - "type": "string" - }, - "propertyNames": { - "kind": "primitive", - "type": "string" - } - } - }, - { - "name": "height", - "publicName": "height", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "shell", - "publicName": "shell", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "width", - "publicName": "width", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ErrorDetail", - "source": "ErrorDetail", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "location", - "publicName": "location", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "message", - "publicName": "message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "value", - "publicName": "value", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ErrorModel", - "source": "ErrorModel", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "detail", - "publicName": "detail", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "errors", - "publicName": "errors", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "ErrorDetail" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "instance", - "publicName": "instance", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "uri" - } - }, - { - "name": "status", - "publicName": "status", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "title", - "publicName": "title", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "type", - "publicName": "type", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "uri" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ExecutionEvent", - "source": "ExecutionEvent", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "at", - "publicName": "at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "chunk", - "publicName": "chunk", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_code", - "publicName": "error_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_message", - "publicName": "error_message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "exit_code", - "publicName": "exit_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "sequence", - "publicName": "sequence", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "signal", - "publicName": "signal", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "status", - "publicName": "status", - "required": false, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "wake_in_progress", - "queued", - "running", - "succeeded", - "failed", - "cancelled", - "expired" - ], - "names": [ - "WakeInProgress", - "Queued", - "Running", - "Succeeded", - "Failed", - "Cancelled", - "Expired" - ], - "deprecations": [ - false, - false, - false, - false, - false, - false, - false - ] - } - }, - { - "name": "type", - "publicName": "type", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "lifecycle", - "stdout", - "stderr" - ], - "names": [ - "Lifecycle", - "Stdout", - "Stderr" - ], - "deprecations": [ - false, - false, - false - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ExecutionEventsResponse", - "source": "ExecutionEventsResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "ExecutionEvent" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ExecutionListResponse", - "source": "ExecutionListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "ExecutionResponse" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ExecutionOutputResponse", - "source": "ExecutionOutputResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "execution_id", - "publicName": "execution_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "stderr", - "publicName": "stderr", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "stderr_bytes", - "publicName": "stderr_bytes", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "stderr_truncated", - "publicName": "stderr_truncated", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "boolean" - } - }, - { - "name": "stdout", - "publicName": "stdout", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "stdout_bytes", - "publicName": "stdout_bytes", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "stdout_truncated", - "publicName": "stdout_truncated", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "boolean" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "ExecutionResponse", - "source": "ExecutionResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "artifacts", - "publicName": "artifacts", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "ArtifactRef" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "command", - "publicName": "command", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "completed_at", - "publicName": "completed_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "cwd", - "publicName": "cwd", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "env_keys", - "publicName": "env_keys", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "error_code", - "publicName": "error_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_message", - "publicName": "error_message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "execution_id", - "publicName": "execution_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "exit_code", - "publicName": "exit_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "expires_at", - "publicName": "expires_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "retry_after_ms", - "publicName": "retry_after_ms", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "signal", - "publicName": "signal", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "started_at", - "publicName": "started_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "wake_in_progress", - "queued", - "running", - "succeeded", - "failed", - "cancelled", - "expired" - ], - "names": [ - "WakeInProgress", - "Queued", - "Running", - "Succeeded", - "Failed", - "Cancelled", - "Expired" - ], - "deprecations": [ - false, - false, - false, - false, - false, - false, - false - ] - } - }, - { - "name": "stderr_bytes", - "publicName": "stderr_bytes", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "stderr_truncated", - "publicName": "stderr_truncated", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "boolean" - } - }, - { - "name": "stdout_bytes", - "publicName": "stdout_bytes", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "stdout_truncated", - "publicName": "stdout_truncated", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "boolean" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "LifecycleResponse", - "source": "LifecycleResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "autosleep_seconds", - "publicName": "autosleep_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64", - "validation": {} - } - }, - { - "name": "desired_state", - "publicName": "desired_state", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "running", - "sleeping", - "destroyed" - ], - "names": [ - "Running", - "Sleeping", - "Destroyed" - ], - "deprecations": [ - false, - false, - false - ] - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "memory_mib", - "publicName": "memory_mib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "ref", - "name": "LifecycleStatus" - } - }, - { - "name": "storage_gib", - "publicName": "storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "vcpu", - "publicName": "vcpu", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "number", - "format": "double" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "LifecycleStatus", - "source": "LifecycleStatus", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "last_error", - "publicName": "last_error", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "last_progress_at", - "publicName": "last_progress_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "last_transition_at", - "publicName": "last_transition_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "phase", - "publicName": "phase", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "accepted", - "placement_pending", - "starting", - "running", - "stopping", - "sleeping", - "destroying", - "destroyed", - "failed" - ], - "names": [ - "Accepted", - "PlacementPending", - "Starting", - "Running", - "Stopping", - "Sleeping", - "Destroying", - "Destroyed", - "Failed" - ], - "deprecations": [ - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - } - }, - { - "name": "reason", - "publicName": "reason", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "retryable", - "publicName": "retryable", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "boolean" - } - }, - { - "name": "revision", - "publicName": "revision", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineComputeUsageBody", - "source": "MachineComputeUsageBody", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "granularity", - "publicName": "granularity", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "period_end", - "publicName": "period_end", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "period_start", - "publicName": "period_start", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "rows", - "publicName": "rows", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "MachineComputeUsageRowBody" - } - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineComputeUsageRowBody", - "source": "MachineComputeUsageRowBody", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "awake_seconds", - "publicName": "awake_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "bucket_end", - "publicName": "bucket_end", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "bucket_start", - "publicName": "bucket_start", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "cpu_millicore_seconds", - "publicName": "cpu_millicore_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "last_window_end", - "publicName": "last_window_end", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "latest_stripe_emitted_at", - "publicName": "latest_stripe_emitted_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "memory_mib_seconds", - "publicName": "memory_mib_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "org_metering_bucket_ids", - "publicName": "org_metering_bucket_ids", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "requested_memory_mib", - "publicName": "requested_memory_mib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int32" - } - }, - { - "name": "requested_storage_gib", - "publicName": "requested_storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int32" - } - }, - { - "name": "requested_vcpu", - "publicName": "requested_vcpu", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "number", - "format": "double" - } - }, - { - "name": "spec_fingerprint", - "publicName": "spec_fingerprint", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "stripe_cpu_identifiers", - "publicName": "stripe_cpu_identifiers", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "stripe_memory_identifiers", - "publicName": "stripe_memory_identifiers", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "primitive", - "type": "string" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "window_count", - "publicName": "window_count", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineIDPathSegment", - "source": "MachineIDPathSegment", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "validation": { - "pattern": "^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$", - "minLength": 4, - "maxLength": 253 - } - } - }, - { - "name": "MachineListItem", - "source": "MachineListItem", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "autosleep_seconds", - "publicName": "autosleep_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64", - "validation": {} - } - }, - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "desired_state", - "publicName": "desired_state", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "running", - "sleeping", - "destroyed" - ], - "names": [ - "Running", - "Sleeping", - "Destroyed" - ], - "deprecations": [ - false, - false, - false - ] - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "memory_mib", - "publicName": "memory_mib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "ref", - "name": "LifecycleStatus" - } - }, - { - "name": "storage_gib", - "publicName": "storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "vcpu", - "publicName": "vcpu", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "number", - "format": "double" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineListResponse", - "source": "MachineListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "MachineListItem" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineStorageUsageBody", - "source": "MachineStorageUsageBody", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "period_end", - "publicName": "period_end", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "period_start", - "publicName": "period_start", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "rows", - "publicName": "rows", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "MachineStorageUsageRowBody" - } - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "MachineStorageUsageRowBody", - "source": "MachineStorageUsageRowBody", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "bucket_end", - "publicName": "bucket_end", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "bucket_start", - "publicName": "bucket_start", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "latest_stripe_emitted_at", - "publicName": "latest_stripe_emitted_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "logical_storage_bytes", - "publicName": "logical_storage_bytes", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "org_metering_bucket_id", - "publicName": "org_metering_bucket_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "storage_mib_seconds", - "publicName": "storage_mib_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "stripe_storage_identifier", - "publicName": "stripe_storage_identifier", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "PreviewListResponse", - "source": "PreviewListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "PreviewResponse" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "PreviewResponse", - "source": "PreviewResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "error_code", - "publicName": "error_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_message", - "publicName": "error_message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "expires_at", - "publicName": "expires_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "port", - "publicName": "port", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "preview_id", - "publicName": "preview_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "protocol", - "publicName": "protocol", - "required": false, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "http", - "https" - ], - "names": [ - "HTTP", - "Https" - ], - "deprecations": [ - false, - false - ] - } - }, - { - "name": "ready_at", - "publicName": "ready_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "retry_after_ms", - "publicName": "retry_after_ms", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "wake_in_progress", - "ready", - "closed", - "expired", - "failed" - ], - "names": [ - "WakeInProgress", - "Ready", - "Closed", - "Expired", - "Failed" - ], - "deprecations": [ - false, - false, - false, - false, - false - ] - } - }, - { - "name": "url", - "publicName": "url", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "visibility", - "publicName": "visibility", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "public", - "private", - "org" - ], - "names": [ - "Public", - "Private", - "Org" - ], - "deprecations": [ - false, - false, - false - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "PublicPathSegment", - "source": "PublicPathSegment", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "validation": { - "pattern": "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$", - "minLength": 1, - "maxLength": 253 - } - } - }, - { - "name": "SshConnection", - "source": "SshConnection", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "endpoint", - "publicName": "endpoint", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "host_trust", - "publicName": "host_trust", - "required": false, - "deprecated": false, - "type": { - "kind": "ref", - "name": "SshHostTrust" - } - }, - { - "name": "port", - "publicName": "port", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "ssh_username", - "publicName": "ssh_username", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "user_certificate", - "publicName": "user_certificate", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "SshHostTrust", - "source": "SshHostTrust", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "host_pattern", - "publicName": "host_pattern", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "kind", - "publicName": "kind", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "cert_authority" - ], - "names": [ - "CertAuthority" - ], - "deprecations": [ - false - ] - } - }, - { - "name": "public_key", - "publicName": "public_key", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "SshSessionListResponse", - "source": "SshSessionListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "SshSessionResponse" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "SshSessionResponse", - "source": "SshSessionResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "connection", - "publicName": "connection", - "required": false, - "deprecated": false, - "type": { - "kind": "ref", - "name": "SshConnection" - } - }, - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "error_code", - "publicName": "error_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_message", - "publicName": "error_message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "expires_at", - "publicName": "expires_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "ready_at", - "publicName": "ready_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "retry_after_ms", - "publicName": "retry_after_ms", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "session_id", - "publicName": "session_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "wake_in_progress", - "ready", - "closed", - "expired", - "failed" - ], - "names": [ - "WakeInProgress", - "Ready", - "Closed", - "Expired", - "Failed" - ], - "deprecations": [ - false, - false, - false, - false, - false - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "TerminalListResponse", - "source": "TerminalListResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "items", - "publicName": "items", - "required": true, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "array", - "items": { - "kind": "ref", - "name": "TerminalResponse" - } - }, - { - "kind": "null" - } - ] - } - }, - { - "name": "next_cursor", - "publicName": "next_cursor", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "TerminalResponse", - "source": "TerminalResponse", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "created_at", - "publicName": "created_at", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "error_code", - "publicName": "error_code", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "error_message", - "publicName": "error_message", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "expires_at", - "publicName": "expires_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "height", - "publicName": "height", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "machine_id", - "publicName": "machine_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "protocol", - "publicName": "protocol", - "required": false, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "websocket" - ], - "names": [ - "Websocket" - ], - "deprecations": [ - false - ] - } - }, - { - "name": "ready_at", - "publicName": "ready_at", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string", - "format": "date-time" - } - }, - { - "name": "retry_after_ms", - "publicName": "retry_after_ms", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "status", - "publicName": "status", - "required": true, - "deprecated": false, - "type": { - "kind": "enum", - "values": [ - "wake_in_progress", - "ready", - "closed", - "expired", - "failed" - ], - "names": [ - "WakeInProgress", - "Ready", - "Closed", - "Expired", - "Failed" - ], - "deprecations": [ - false, - false, - false, - false, - false - ] - } - }, - { - "name": "stream_url", - "publicName": "stream_url", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "terminal_id", - "publicName": "terminal_id", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "width", - "publicName": "width", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "UpdateMachineRequest", - "source": "UpdateMachineRequest", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "autosleep", - "publicName": "autosleep", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "memory_mib", - "publicName": "memory_mib", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "storage_gib", - "publicName": "storage_gib", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "vcpu", - "publicName": "vcpu", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "number", - "format": "double" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "UsageBody", - "source": "UsageBody", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "billed_awake_seconds", - "publicName": "billed_awake_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "billed_cpu_millicore_seconds", - "publicName": "billed_cpu_millicore_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "billed_logical_storage_mib_seconds", - "publicName": "billed_logical_storage_mib_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "billed_memory_mib_seconds", - "publicName": "billed_memory_mib_seconds", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "included_storage_gib", - "publicName": "included_storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - }, - { - "name": "plan_slug", - "publicName": "plan_slug", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "provisioned_storage_gib", - "publicName": "provisioned_storage_gib", - "required": true, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "integer", - "format": "int64" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateResponseHeaders", - "source": "CreateResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "CreateResponseHeaders", - "source": "CreateResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "DeleteResponseHeaders", - "source": "DeleteResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "DeleteResponseHeaders", - "source": "DeleteResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "RetrieveResponseHeaders", - "source": "RetrieveResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - } - ], - "additionalProperties": false - } - }, - { - "name": "PatchResponseHeaders", - "source": "PatchResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "PatchResponseHeaders", - "source": "PatchResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "SleepResponseHeaders", - "source": "SleepResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "SleepResponseHeaders", - "source": "SleepResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "WakeResponseHeaders", - "source": "WakeResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - }, - { - "name": "WakeResponseHeaders", - "source": "WakeResponseHeaders", - "publicAliases": [], - "deprecated": false, - "type": { - "kind": "object", - "properties": [ - { - "name": "ETag", - "publicName": "ETag", - "required": false, - "deprecated": false, - "type": { - "kind": "primitive", - "type": "string" - } - }, - { - "name": "X-Dedalus-Storage-Operation-Id", - "publicName": "X-Dedalus-Storage-Operation-Id", - "required": false, - "deprecated": false, - "type": { - "kind": "union", - "variants": [ - { - "kind": "primitive", - "type": "string" - }, - { - "kind": "null" - } - ] - } - } - ], - "additionalProperties": false - } - } - ], - "resources": [ - "machineLifecycle", - "usage", - "usage.machines" - ], - "publicResources": [ - "machineLifecycle", - "usage", - "usage.machines" - ], - "operations": [ - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "list", - "publicOperation": "list", - "deprecated": false, - "method": "GET", - "path": "/v1/machines", - "pathParams": [], - "publicPathParams": [], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "MachineListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "create", - "publicOperation": "create", - "deprecated": false, - "method": "POST", - "path": "/v1/machines", - "pathParams": [], - "publicPathParams": [], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "autosleep", - "memory_mib", - "storage_gib", - "vcpu" - ], - "publicBodyParams": [ - "autosleep", - "memory_mib", - "storage_gib", - "vcpu" - ], - "publicPositionalParams": [], - "pathParamDetails": [], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleCreateParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "CreateResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "delete", - "publicOperation": "delete", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeleteParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "DeleteResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrieve", - "publicOperation": "retrieve", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrieveParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "RetrieveResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "patch", - "publicOperation": "patch", - "deprecated": false, - "method": "PATCH", - "path": "/v1/machines/{machine_id}", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "autosleep", - "memory_mib", - "storage_gib", - "vcpu" - ], - "publicBodyParams": [ - "autosleep", - "memory_mib", - "storage_gib", - "vcpu" - ], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecyclePatchParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "PatchResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listArtifacts", - "publicOperation": "listArtifacts", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/artifacts", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListArtifactsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ArtifactListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "deleteArtifact", - "publicOperation": "deleteArtifact", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}/artifacts/{artifact_id}", - "pathParams": [ - "machine_id", - "artifact_id" - ], - "publicPathParams": [ - "machine_id", - "artifact_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "artifact_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeleteArtifactParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ArtifactResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrieveArtifact", - "publicOperation": "retrieveArtifact", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/artifacts/{artifact_id}", - "pathParams": [ - "machine_id", - "artifact_id" - ], - "publicPathParams": [ - "machine_id", - "artifact_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "artifact_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrieveArtifactParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ArtifactResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listExecutions", - "publicOperation": "listExecutions", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/executions", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListExecutionsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "createExecution", - "publicOperation": "createExecution", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/executions", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "command", - "cwd", - "env", - "stdin", - "timeout_ms" - ], - "publicBodyParams": [ - "command", - "cwd", - "env", - "stdin", - "timeout_ms" - ], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleCreateExecutionParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "deleteExecution", - "publicOperation": "deleteExecution", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}/executions/{execution_id}", - "pathParams": [ - "machine_id", - "execution_id" - ], - "publicPathParams": [ - "machine_id", - "execution_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "execution_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeleteExecutionParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrieveExecution", - "publicOperation": "retrieveExecution", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/executions/{execution_id}", - "pathParams": [ - "machine_id", - "execution_id" - ], - "publicPathParams": [ - "machine_id", - "execution_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "execution_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrieveExecutionParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listExecutionEvents", - "publicOperation": "listExecutionEvents", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/executions/{execution_id}/events", - "pathParams": [ - "machine_id", - "execution_id" - ], - "publicPathParams": [ - "machine_id", - "execution_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "execution_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListExecutionEventsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionEventsResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listExecutionOutput", - "publicOperation": "listExecutionOutput", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/executions/{execution_id}/output", - "pathParams": [ - "machine_id", - "execution_id" - ], - "publicPathParams": [ - "machine_id", - "execution_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "execution_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListExecutionOutputParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "ExecutionOutputResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listPreviews", - "publicOperation": "listPreviews", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/previews", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListPreviewsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "PreviewListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "createPreview", - "publicOperation": "createPreview", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/previews", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "port", - "protocol", - "visibility" - ], - "publicBodyParams": [ - "port", - "protocol", - "visibility" - ], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleCreatePreviewParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "PreviewResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "deletePreview", - "publicOperation": "deletePreview", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}/previews/{preview_id}", - "pathParams": [ - "machine_id", - "preview_id" - ], - "publicPathParams": [ - "machine_id", - "preview_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "preview_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeletePreviewParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "PreviewResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrievePreview", - "publicOperation": "retrievePreview", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/previews/{preview_id}", - "pathParams": [ - "machine_id", - "preview_id" - ], - "publicPathParams": [ - "machine_id", - "preview_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "preview_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrievePreviewParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "PreviewResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "sleep", - "publicOperation": "sleep", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/sleep", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleSleepParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "SleepResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listSshSessions", - "publicOperation": "listSshSessions", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/ssh", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListSshSessionsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "SshSessionListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "createSshSession", - "publicOperation": "createSshSession", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/ssh", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "public_key" - ], - "publicBodyParams": [ - "public_key" - ], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleCreateSshSessionParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "SshSessionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "deleteSshSession", - "publicOperation": "deleteSshSession", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}/ssh/{session_id}", - "pathParams": [ - "machine_id", - "session_id" - ], - "publicPathParams": [ - "machine_id", - "session_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "session_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeleteSshSessionParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "SshSessionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrieveSshSession", - "publicOperation": "retrieveSshSession", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/ssh/{session_id}", - "pathParams": [ - "machine_id", - "session_id" - ], - "publicPathParams": [ - "machine_id", - "session_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "session_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrieveSshSessionParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "SshSessionResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "watchStatus", - "publicOperation": "watchStatus", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/status/stream", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id", - "Last-Event-ID" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id", - "Last-Event-ID" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - }, - { - "name": "Last-Event-ID", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleWatchStatusParams" - }, - "response": { - "status": "200", - "contentType": "text/event-stream", - "encoding": "text", - "contents": [ - { - "contentType": "text/event-stream", - "encoding": "text" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - } - ], - "responseLinks": [], - "transport": "http", - "streaming": "sse" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "listTerminals", - "publicOperation": "listTerminals", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/terminals", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [ - "limit", - "cursor" - ], - "publicQueryParams": [ - "limit", - "cursor" - ], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [ - { - "name": "limit", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "cursor", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleListTerminalsParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "TerminalListResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "createTerminal", - "publicOperation": "createTerminal", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/terminals", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [ - "cwd", - "env", - "height", - "shell", - "width" - ], - "publicBodyParams": [ - "cwd", - "env", - "height", - "shell", - "width" - ], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleCreateTerminalParams" - }, - "requestBody": { - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ], - "required": true, - "publicName": "body", - "publicIdentifier": "body" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "TerminalResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "deleteTerminal", - "publicOperation": "deleteTerminal", - "deprecated": false, - "method": "DELETE", - "path": "/v1/machines/{machine_id}/terminals/{terminal_id}", - "pathParams": [ - "machine_id", - "terminal_id" - ], - "publicPathParams": [ - "machine_id", - "terminal_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "terminal_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleDeleteTerminalParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "TerminalResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "retrieveTerminal", - "publicOperation": "retrieveTerminal", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/terminals/{terminal_id}", - "pathParams": [ - "machine_id", - "terminal_id" - ], - "publicPathParams": [ - "machine_id", - "terminal_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "terminal_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleRetrieveTerminalParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "TerminalResponse", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "connectTerminal", - "publicOperation": "connectTerminal", - "deprecated": false, - "method": "GET", - "path": "/v1/machines/{machine_id}/terminals/{terminal_id}/stream", - "pathParams": [ - "machine_id", - "terminal_id" - ], - "publicPathParams": [ - "machine_id", - "terminal_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - }, - { - "name": "terminal_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleConnectTerminalParams" - }, - "result": { - "successStatus": "101", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - } - ], - "responseLinks": [], - "transport": "websocket", - "websocket": {} - }, - { - "resource": "machineLifecycle", - "publicResource": "machineLifecycle", - "operation": "wake", - "publicOperation": "wake", - "deprecated": false, - "method": "POST", - "path": "/v1/machines/{machine_id}/wake", - "pathParams": [ - "machine_id" - ], - "publicPathParams": [ - "machine_id" - ], - "queryParams": [], - "publicQueryParams": [], - "headerParams": [ - "X-Dedalus-Org-Id" - ], - "publicHeaderParams": [ - "X-Dedalus-Org-Id" - ], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [ - { - "name": "machine_id", - "required": true, - "style": "simple", - "explode": false - } - ], - "queryParamDetails": [], - "headerParamDetails": [ - { - "name": "X-Dedalus-Org-Id", - "required": false, - "style": "form", - "explode": true - } - ], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineLifecycleWakeParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "LifecycleResponse", - "publicAliases": [] - }, - "responseHeadersModel": { - "name": "WakeResponseHeaders", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "401", - "403", - "409", - "429", - "503", - "default" - ] - }, - "errorResponses": [ - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "409", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "429", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "503", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "usage", - "publicResource": "usage", - "operation": "list", - "publicOperation": "list", - "deprecated": false, - "method": "GET", - "path": "/v1/usage", - "pathParams": [], - "publicPathParams": [], - "queryParams": [ - "period_start" - ], - "publicQueryParams": [ - "period_start" - ], - "headerParams": [], - "publicHeaderParams": [], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [], - "queryParamDetails": [ - { - "name": "period_start", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "UsageListParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "UsageBody", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "400", - "401", - "403", - "500", - "502", - "default" - ] - }, - "errorResponses": [ - { - "status": "400", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "500", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "502", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "usage.machines", - "publicResource": "usage.machines", - "operation": "listComputeUsage", - "publicOperation": "listComputeUsage", - "deprecated": false, - "method": "GET", - "path": "/v1/usage/machines/compute", - "pathParams": [], - "publicPathParams": [], - "queryParams": [ - "period_start", - "period_end", - "machine_id", - "granularity" - ], - "publicQueryParams": [ - "period_start", - "period_end", - "machine_id", - "granularity" - ], - "headerParams": [], - "publicHeaderParams": [], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [], - "queryParamDetails": [ - { - "name": "period_start", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "period_end", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "machine_id", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "granularity", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineListComputeUsageParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "MachineComputeUsageBody", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "400", - "401", - "403", - "500", - "502", - "default" - ] - }, - "errorResponses": [ - { - "status": "400", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "500", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "502", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - }, - { - "resource": "usage.machines", - "publicResource": "usage.machines", - "operation": "listStorageUsage", - "publicOperation": "listStorageUsage", - "deprecated": false, - "method": "GET", - "path": "/v1/usage/machines/storage", - "pathParams": [], - "publicPathParams": [], - "queryParams": [ - "period_start", - "period_end", - "machine_id" - ], - "publicQueryParams": [ - "period_start", - "period_end", - "machine_id" - ], - "headerParams": [], - "publicHeaderParams": [], - "bodyParams": [], - "publicBodyParams": [], - "publicPositionalParams": [], - "pathParamDetails": [], - "queryParamDetails": [ - { - "name": "period_start", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "period_end", - "required": false, - "style": "form", - "explode": false - }, - { - "name": "machine_id", - "required": false, - "style": "form", - "explode": false - } - ], - "headerParamDetails": [], - "cookieParams": [], - "publicCookieParams": [], - "cookieParamDetails": [], - "paramsModel": { - "publicName": "MachineListStorageUsageParams" - }, - "response": { - "status": "200", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - "responseModel": { - "name": "MachineStorageUsageBody", - "publicAliases": [] - }, - "result": { - "successStatus": "200", - "errorStatuses": [ - "400", - "401", - "403", - "500", - "502", - "default" - ] - }, - "errorResponses": [ - { - "status": "400", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "401", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "403", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "500", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "502", - "contentType": "application/json", - "encoding": "json", - "contents": [ - { - "contentType": "application/json", - "encoding": "json" - } - ] - }, - { - "status": "default", - "contentType": "application/problem+json", - "encoding": "json", - "contents": [ - { - "contentType": "application/problem+json", - "encoding": "json" - } - ], - "model": { - "name": "ErrorModel", - "publicAliases": [] - } - } - ], - "responseLinks": [], - "transport": "http" - } - ], + "schemas": [], + "resources": [], + "publicResources": [], + "operations": [], "webhooks": [] } diff --git a/src/cli/completions.ts b/src/cli/completions.ts new file mode 100644 index 0000000..5b0f001 --- /dev/null +++ b/src/cli/completions.ts @@ -0,0 +1,12 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +// Shell completion scripts for this CLI, rendered from the same command table the program is +// built from. `dedalus completion ` prints one of them. + +const bash = "# File generated from our OpenAPI spec by Scalar. Do not edit.\n\n__dedalus_known_command() {\n case \"$1\" in\n 'completion') return 0 ;;\n esac\n return 1\n}\n\n__dedalus_completion() {\n local cur node candidate words line index\n COMPREPLY=()\n cur=\"${COMP_WORDS[COMP_CWORD]}\"\n node=''\n for (( index = 1; index < COMP_CWORD; index++ )); do\n case \"${COMP_WORDS[index]}\" in\n -*) continue ;;\n esac\n if [ -z \"$node\" ]; then\n candidate=\"${COMP_WORDS[index]}\"\n else\n candidate=\"$node ${COMP_WORDS[index]}\"\n fi\n if __dedalus_known_command \"$candidate\"; then node=\"$candidate\"; fi\n done\n\n case \"$node\" in\n '') words='completion help --base-url --timeout --max-retries --format --format-error --transform --transform-error --raw-output --debug --api-key --x-api-key --bearer-auth --provider --provider-key --provider-model --version --help' ;;\n 'completion') words='bash zsh fish --help' ;;\n *) words='' ;;\n esac\n\n while IFS= read -r line; do\n COMPREPLY+=( \"$line\" )\n done < <(compgen -W \"$words\" -- \"$cur\")\n}\n\ncomplete -F __dedalus_completion 'dedalus'\n" + +const zsh = "#compdef dedalus\n# File generated from our OpenAPI spec by Scalar. Do not edit.\n\n__dedalus_known_command() {\n case \"$1\" in\n 'completion') return 0 ;;\n esac\n return 1\n}\n\n__dedalus_completion() {\n local node candidate\n local -a subcommands options\n local -i index matched\n node=''\n matched=1\n for (( index = 2; index < CURRENT; index++ )); do\n case ${words[index]} in\n -*) continue ;;\n esac\n if [[ -z $node ]]; then\n candidate=${words[index]}\n else\n candidate=\"$node ${words[index]}\"\n fi\n if __dedalus_known_command \"$candidate\"; then\n node=$candidate\n matched=$index\n fi\n done\n\n subcommands=()\n options=()\n case \"$node\" in\n '')\n subcommands=('completion:Print a shell completion script' 'help:Show help for a command')\n options=('--base-url[Override the base URL for API requests]:value:' '--timeout[Request timeout in milliseconds]:value:' '--max-retries[Number of retries for retryable failures]:value:' '--format[Output format\\: auto, json, jsonl, pretty, raw, yaml]:value:' '--format-error[Error output format\\: auto, json, jsonl, pretty, raw, yaml]:value:' '--transform[Dot-path transform for data output]:value:' '--transform-error[Dot-path transform for error output]:value:' '--raw-output[Print transformed string values without JSON quotes]' '--debug[Enable SDK debug logging]' '--api-key[API key authentication using Bearer token ($DEDALUS_API_KEY)]:value:' '--x-api-key[API key authentication using X-API-Key header ($DEDALUS_X_API_KEY)]:value:' '--bearer-auth[Dedalus API key in Authorization\\: Bearer ]:value:' '--provider[Provider name for BYOK mode]:value:' '--provider-key[Provider API key for BYOK mode]:value:' '--provider-model[Model identifier for BYOK provider]:value:' '--version[Print the CLI version]' '--help[Show help for this command]')\n ;;\n 'completion')\n subcommands=('bash:Print a bash completion script' 'zsh:Print a zsh completion script' 'fish:Print a fish completion script')\n options=('--help[Show help for this command]')\n ;;\n esac\n\n words=( \"$words[1]\" \"${(@)words[matched+1,-1]}\" )\n (( CURRENT -= matched - 1 ))\n\n (( ${#options} )) && _arguments -s -S $options\n (( ${#subcommands} )) && _describe -t commands 'command' subcommands\n}\n\nif [[ ${zsh_eval_context[-1]} == loadautofunc ]]; then\n __dedalus_completion \"$@\"\nelse\n compdef __dedalus_completion 'dedalus'\nfi\n" + +const fish = "# File generated from our OpenAPI spec by Scalar. Do not edit.\n\nset -g __dedalus_paths 'completion'\n\nfunction __dedalus_path\n set -l path ''\n set -l tokens (commandline -opc)\n for token in $tokens[2..-1]\n if string match -q -- '-*' $token\n continue\n end\n set -l candidate\n if test -z \"$path\"\n set candidate $token\n else\n set candidate \"$path $token\"\n end\n if contains -- $candidate $__dedalus_paths\n set path $candidate\n end\n end\n echo $path\nend\n\nfunction __dedalus_at --argument-names index\n set -l current (__dedalus_path)\n if test \"$index\" -eq 0\n test -z \"$current\"\n else\n test \"$current\" = \"$__dedalus_paths[$index]\"\n end\nend\n\ncomplete -c 'dedalus' -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -a 'completion' -d 'Print a shell completion script'\ncomplete -c 'dedalus' -n '__dedalus_at 0' -a 'help' -d 'Show help for a command'\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'base-url' -d 'Override the base URL for API requests' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'timeout' -d 'Request timeout in milliseconds' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'max-retries' -d 'Number of retries for retryable failures' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'format' -d 'Output format: auto, json, jsonl, pretty, raw, yaml' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'format-error' -d 'Error output format: auto, json, jsonl, pretty, raw, yaml' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'transform' -d 'Dot-path transform for data output' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'transform-error' -d 'Dot-path transform for error output' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'raw-output' -d 'Print transformed string values without JSON quotes'\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'debug' -d 'Enable SDK debug logging'\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'api-key' -d 'API key authentication using Bearer token ($DEDALUS_API_KEY)' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'x-api-key' -d 'API key authentication using X-API-Key header ($DEDALUS_X_API_KEY)' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'bearer-auth' -d 'Dedalus API key in Authorization: Bearer ' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'provider' -d 'Provider name for BYOK mode' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'provider-key' -d 'Provider API key for BYOK mode' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'provider-model' -d 'Model identifier for BYOK provider' -r -f\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'version' -d 'Print the CLI version'\ncomplete -c 'dedalus' -n '__dedalus_at 0' -l 'help' -d 'Show help for this command'\ncomplete -c 'dedalus' -n '__dedalus_at 1' -a 'bash' -d 'Print a bash completion script'\ncomplete -c 'dedalus' -n '__dedalus_at 1' -a 'zsh' -d 'Print a zsh completion script'\ncomplete -c 'dedalus' -n '__dedalus_at 1' -a 'fish' -d 'Print a fish completion script'\ncomplete -c 'dedalus' -n '__dedalus_at 1' -l 'help' -d 'Show help for this command'\n" + +export const completions = { bash, zsh, fish } as const diff --git a/src/cli/runtime.ts b/src/cli/runtime.ts index bd94700..8ec72a1 100644 --- a/src/cli/runtime.ts +++ b/src/cli/runtime.ts @@ -41,7 +41,7 @@ export type CliCommandDefinition = { readonly streaming?: 'sse' | 'jsonl' readonly iterable: boolean readonly callShape: 'options' | 'params' | 'body' - // Param key of a non-flattenable body argument forwarded to the SDK method as a single value. + // Param key of a body blob that is forwarded bare or spread into params, depending on callShape. readonly bodyParamKey?: string readonly positional: readonly CliFlagDefinition[] readonly flags: readonly CliFlagDefinition[] @@ -55,6 +55,10 @@ export type CliClientOptionDefinition = { readonly env?: string readonly description?: string readonly auth: boolean + // Documented in --help only. Deliberately NOT registered as a Commander default: a default + // would make the flag always look explicitly set, and the forwarded value would shadow the + // environment variable the SDK reads before falling back to this same default itself. + readonly defaultValue?: string } export type CreateProgramOptions = { @@ -66,6 +70,9 @@ export type CreateProgramOptions = { readonly defaultErrorFormat: OutputFormat readonly clientOptions: readonly CliClientOptionDefinition[] readonly commands: readonly CliCommandDefinition[] + // Completion script per shell, generated alongside the command table. Absent when the SDK + // config disables shell completions, in which case no `completion` command is registered. + readonly completions?: Readonly> } type OutputOptions = { @@ -92,7 +99,7 @@ type GlobalOptions = { readonly maxItems?: string } -export const createProgram = ({ SDK, binaryName, version, description, defaultFormat, defaultErrorFormat, clientOptions, commands }: CreateProgramOptions): Command => { +export const createProgram = ({ SDK, binaryName, version, description, defaultFormat, defaultErrorFormat, clientOptions, commands, completions }: CreateProgramOptions): Command => { const program = new Command() program .enablePositionalOptions() @@ -118,14 +125,52 @@ export const createProgram = ({ SDK, binaryName, version, description, defaultFo for (const definition of commands) addGeneratedCommand(program, SDK, clientOptions, definition) + if (completions) addCompletionCommand(program, binaryName, completions) + return program } +// Prints the completion script for one shell. The scripts are generated from the same command +// table this program is built from, so they always describe the commands and flags below; nothing +// is derived from the live Commander tree, and no shell code is assembled at runtime. +const addCompletionCommand = (program: Command, binaryName: string, completions: Readonly>): void => { + const shells = Object.keys(completions) + program + .command("completion") + .description("Print a shell completion script (" + shells.join(", ") + ")") + .argument("", "Shell to print a completion script for: " + shells.join(", ")) + .addHelpText("after", completionHelpExamples(binaryName, shells)) + .action((shell: string) => { + // Own-property lookup only: a bare `completions[shell]` would resolve inherited keys like + // "constructor" or "__proto__" to something that is not a completion script. + const script = Object.prototype.hasOwnProperty.call(completions, shell) ? completions[shell] : undefined + if (script === undefined) { + process.stderr.write("Unsupported shell '" + shell + "'. Supported shells: " + shells.join(", ") + "\n") + process.exitCode = 1 + return + } + processStdout.write(script) + }) +} + +// Shows how to load each script, since every shell wires completions up differently. +const completionHelpExamples = (binaryName: string, shells: readonly string[]): string => { + const examples: Record = { + bash: " eval \"$(" + binaryName + " completion bash)\" # or write it to /etc/bash_completion.d", + zsh: " eval \"$(" + binaryName + " completion zsh)\" # or write it to a directory on $fpath", + fish: " " + binaryName + " completion fish | source # or write it to ~/.config/fish/completions", + } + const lines = shells.map((shell) => examples[shell]).filter((line): line is string => line !== undefined) + return lines.length > 0 ? "\nAdd one of these to your shell startup file:\n" + lines.join("\n") : "" +} + const clientOptionDescription = (option: CliClientOptionDefinition): string => { - const base = option.description ?? "" - if (!option.env) return base - const envHint = "(can also be set with " + option.env + " env var)" - return base ? base + " " + envHint : envHint + const parts: string[] = [] + if (option.description) parts.push(option.description) + if (option.env) parts.push("(can also be set with " + option.env + " env var)") + // Stated last, matching resolution order: the flag wins, then the env var, then this value. + if (option.defaultValue !== undefined) parts.push("(defaults to " + option.defaultValue + ")") + return parts.join(" ") } const addGeneratedCommand = ( @@ -345,7 +390,17 @@ const callArguments = async ( if (definition.callShape === "options") return { args: [...positionalArgs, undefined], params } if (definition.callShape === "body") return { args: [...positionalArgs, bodyValue(sdkParams, definition), undefined], params } - return { args: [...positionalArgs, sdkParams, undefined], params } + return { args: [...positionalArgs, paramsValue(sdkParams, definition), undefined], params } +} + +const paramsValue = (params: Record, definition: CliCommandDefinition): unknown => { + if (definition.bodyParamKey === undefined) return params + const body = params[definition.bodyParamKey] + if (body === undefined) return params + // Scoped union bodies with headers are typed as the params root. A `--body` JSON blob therefore + // needs to sit beside header/query flags, or be passed as the root when it cannot be merged. + if (!isPlainObject(body)) return body + return mergeObjects(body, omitParams(params, [definition.bodyParamKey])) } const bodyValue = (params: Record, definition: CliCommandDefinition): unknown => { diff --git a/src/commands/index.ts b/src/commands/index.ts index 193ae47..f43c67b 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -3,14 +3,24 @@ import type { Command } from 'commander' import SDK from '../sdk/index' import { createProgram, type CliClientOptionDefinition, type CliCommandDefinition } from '../cli/runtime' +import { completions } from '../cli/completions' const clientOptions = [ { - "clientKey": "apiKeyAuth", - "sdkKey": "apiKeyAuth", - "name": "api-key-auth", - "optionKey": "apiKeyAuth", - "env": "API_KEY_AUTH", + "clientKey": "apiKey", + "sdkKey": "apiKey", + "name": "api-key", + "optionKey": "apiKey", + "env": "DEDALUS_API_KEY", + "description": "API key authentication using Bearer token", + "auth": true + }, + { + "clientKey": "xAPIKey", + "sdkKey": "xAPIKey", + "name": "x-api-key", + "optionKey": "xApiKey", + "env": "DEDALUS_X_API_KEY", "description": "API key authentication using X-API-Key header", "auth": true }, @@ -19,19 +29,10 @@ const clientOptions = [ "sdkKey": "bearerAuth", "name": "bearer-auth", "optionKey": "bearerAuth", - "env": "BEARER_AUTH", + "env": "DEDALUS_BEARER_AUTH", "description": "Dedalus API key in Authorization: Bearer .", "auth": true }, - { - "clientKey": "bearer", - "sdkKey": "bearer", - "name": "bearer", - "optionKey": "bearer", - "env": "BEARER", - "description": "API key authentication using Bearer token", - "auth": true - }, { "clientKey": "provider", "sdkKey": "provider", @@ -61,1542 +62,17 @@ const clientOptions = [ } ] as const satisfies readonly CliClientOptionDefinition[] -const commands = [ - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list" - ], - "methodName": "list", - "summary": "List machines", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "create" - ], - "methodName": "create", - "summary": "Create machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "autosleep", - "optionKey": "autosleep", - "paramKey": "autosleep", - "location": "body", - "required": false, - "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", - "valueKind": "string" - }, - { - "name": "memory-mib", - "optionKey": "memoryMib", - "paramKey": "memory_mib", - "location": "body", - "required": true, - "description": "Memory in MiB.", - "valueKind": "integer" - }, - { - "name": "storage-gib", - "optionKey": "storageGib", - "paramKey": "storage_gib", - "location": "body", - "required": true, - "description": "Storage in GiB.", - "valueKind": "integer" - }, - { - "name": "vcpu", - "optionKey": "vcpu", - "paramKey": "vcpu", - "location": "body", - "required": true, - "description": "CPU in vCPUs.", - "valueKind": "number" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete" - ], - "methodName": "delete", - "summary": "Destroy machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve" - ], - "methodName": "retrieve", - "summary": "Get machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "patch" - ], - "methodName": "patch", - "summary": "Update machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "autosleep", - "optionKey": "autosleep", - "paramKey": "autosleep", - "location": "body", - "required": false, - "description": "Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds (\"1800\"), or never to disable.", - "valueKind": "string" - }, - { - "name": "memory-mib", - "optionKey": "memoryMib", - "paramKey": "memory_mib", - "location": "body", - "required": false, - "description": "Memory in MiB.", - "valueKind": "integer" - }, - { - "name": "storage-gib", - "optionKey": "storageGib", - "paramKey": "storage_gib", - "location": "body", - "required": false, - "description": "Storage in GiB.", - "valueKind": "integer" - }, - { - "name": "vcpu", - "optionKey": "vcpu", - "paramKey": "vcpu", - "location": "body", - "required": false, - "description": "CPU in vCPUs.", - "valueKind": "number" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-artifacts" - ], - "methodName": "listArtifacts", - "summary": "List artifacts", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete-artifact" - ], - "methodName": "deleteArtifact", - "summary": "Delete artifact", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "artifact-id", - "optionKey": "artifactId", - "paramKey": "artifact_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve-artifact" - ], - "methodName": "retrieveArtifact", - "summary": "Get artifact", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "artifact-id", - "optionKey": "artifactId", - "paramKey": "artifact_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-executions" - ], - "methodName": "listExecutions", - "summary": "List executions", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "create-execution" - ], - "methodName": "createExecution", - "summary": "Create execution", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "command", - "optionKey": "command", - "paramKey": "command", - "location": "body", - "required": true, - "valueKind": "array" - }, - { - "name": "cwd", - "optionKey": "cwd", - "paramKey": "cwd", - "location": "body", - "required": false, - "valueKind": "string" - }, - { - "name": "env", - "optionKey": "env", - "paramKey": "env", - "location": "body", - "required": false, - "valueKind": "object" - }, - { - "name": "stdin", - "optionKey": "stdin", - "paramKey": "stdin", - "location": "body", - "required": false, - "valueKind": "string" - }, - { - "name": "timeout-ms", - "optionKey": "timeoutMs", - "paramKey": "timeout_ms", - "location": "body", - "required": false, - "valueKind": "integer" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete-execution" - ], - "methodName": "deleteExecution", - "summary": "Delete execution", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "execution-id", - "optionKey": "executionId", - "paramKey": "execution_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve-execution" - ], - "methodName": "retrieveExecution", - "summary": "Get execution", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "execution-id", - "optionKey": "executionId", - "paramKey": "execution_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-execution-events" - ], - "methodName": "listExecutionEvents", - "summary": "List execution events", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "execution-id", - "optionKey": "executionId", - "paramKey": "execution_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-execution-output" - ], - "methodName": "listExecutionOutput", - "summary": "Get execution output", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "execution-id", - "optionKey": "executionId", - "paramKey": "execution_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-previews" - ], - "methodName": "listPreviews", - "summary": "List previews", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "create-preview" - ], - "methodName": "createPreview", - "summary": "Create preview", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "port", - "optionKey": "port", - "paramKey": "port", - "location": "body", - "required": true, - "valueKind": "integer" - }, - { - "name": "protocol", - "optionKey": "protocol", - "paramKey": "protocol", - "location": "body", - "required": false, - "valueKind": "string" - }, - { - "name": "visibility", - "optionKey": "visibility", - "paramKey": "visibility", - "location": "body", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete-preview" - ], - "methodName": "deletePreview", - "summary": "Delete preview", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "preview-id", - "optionKey": "previewId", - "paramKey": "preview_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve-preview" - ], - "methodName": "retrievePreview", - "summary": "Get preview", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "preview-id", - "optionKey": "previewId", - "paramKey": "preview_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "sleep" - ], - "methodName": "sleep", - "summary": "Sleep a running machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-ssh-sessions" - ], - "methodName": "listSSHSessions", - "summary": "List SSH sessions", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "create-ssh-session" - ], - "methodName": "createSSHSession", - "summary": "Create SSH session", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "public-key", - "optionKey": "publicKey", - "paramKey": "public_key", - "location": "body", - "required": true, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete-ssh-session" - ], - "methodName": "deleteSSHSession", - "summary": "Delete SSH session", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "session-id", - "optionKey": "sessionId", - "paramKey": "session_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve-ssh-session" - ], - "methodName": "retrieveSSHSession", - "summary": "Get SSH session", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "session-id", - "optionKey": "sessionId", - "paramKey": "session_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "watch-status" - ], - "methodName": "watchStatus", - "summary": "Watch machine lifecycle status", - "description": "Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state.", - "transport": "http", - "streaming": "sse", - "iterable": true, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "description": "Machine identifier.", - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "description": "Organization ID header applied to all DCS requests.", - "valueKind": "string" - }, - { - "name": "last-event-id", - "optionKey": "lastEventId", - "paramKey": "Last-Event-ID", - "location": "header", - "required": false, - "description": "Optional resourceVersion bookmark used to resume a previous stream.", - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "list-terminals" - ], - "methodName": "listTerminals", - "summary": "List terminals", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "limit", - "optionKey": "limit", - "paramKey": "limit", - "location": "query", - "required": false, - "valueKind": "integer" - }, - { - "name": "cursor", - "optionKey": "cursor", - "paramKey": "cursor", - "location": "query", - "required": false, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "create-terminal" - ], - "methodName": "createTerminal", - "summary": "Create terminal", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - }, - { - "name": "cwd", - "optionKey": "cwd", - "paramKey": "cwd", - "location": "body", - "required": false, - "valueKind": "string" - }, - { - "name": "env", - "optionKey": "env", - "paramKey": "env", - "location": "body", - "required": false, - "valueKind": "object" - }, - { - "name": "height", - "optionKey": "height", - "paramKey": "height", - "location": "body", - "required": true, - "valueKind": "integer" - }, - { - "name": "shell", - "optionKey": "shell", - "paramKey": "shell", - "location": "body", - "required": false, - "valueKind": "string" - }, - { - "name": "width", - "optionKey": "width", - "paramKey": "width", - "location": "body", - "required": true, - "valueKind": "integer" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "delete-terminal" - ], - "methodName": "deleteTerminal", - "summary": "Delete terminal", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "terminal-id", - "optionKey": "terminalId", - "paramKey": "terminal_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "retrieve-terminal" - ], - "methodName": "retrieveTerminal", - "summary": "Get terminal", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "terminal-id", - "optionKey": "terminalId", - "paramKey": "terminal_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "connect-terminal" - ], - "methodName": "connectTerminal", - "summary": "Connect to terminal WebSocket stream", - "description": "Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields.", - "transport": "websocket", - "iterable": true, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "description": "Machine identifier.", - "valueKind": "string" - }, - { - "name": "terminal-id", - "optionKey": "terminalId", - "paramKey": "terminal_id", - "location": "path", - "required": true, - "description": "Terminal identifier.", - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "description": "Organization ID header applied to all DCS requests.", - "valueKind": "string" - }, - { - "name": "send", - "optionKey": "send", - "paramKey": "send", - "location": "body", - "required": false, - "description": "JSON message to send after connecting.", - "valueKind": "unknown" - } - ] - }, - { - "resourcePath": [ - "machineLifecycle" - ], - "commandPath": [ - "machine-lifecycle", - "wake" - ], - "methodName": "wake", - "summary": "Wake a sleeping machine", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "path", - "required": true, - "valueKind": "string" - }, - { - "name": "x-dedalus-org-id", - "optionKey": "xDedalusOrgId", - "paramKey": "X-Dedalus-Org-Id", - "location": "header", - "required": false, - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "usage" - ], - "commandPath": [ - "usage", - "list" - ], - "methodName": "list", - "summary": "Get usage summary", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "period-start", - "optionKey": "periodStart", - "paramKey": "period_start", - "location": "query", - "required": false, - "description": "Billing period start (YYYY-MM-DD). Defaults to first of current month.", - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "usage", - "machines" - ], - "commandPath": [ - "usage:machines", - "list-compute-usage" - ], - "methodName": "listComputeUsage", - "summary": "List machine compute usage breakdown", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "period-start", - "optionKey": "periodStart", - "paramKey": "period_start", - "location": "query", - "required": false, - "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", - "valueKind": "string" - }, - { - "name": "period-end", - "optionKey": "periodEnd", - "paramKey": "period_end", - "location": "query", - "required": false, - "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", - "valueKind": "string" - }, - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "query", - "required": false, - "description": "Optional machine ID filter.", - "valueKind": "string" - }, - { - "name": "granularity", - "optionKey": "granularity", - "paramKey": "granularity", - "location": "query", - "required": false, - "description": "Usage breakdown granularity: hour or day. Defaults to hour.", - "valueKind": "string" - } - ] - }, - { - "resourcePath": [ - "usage", - "machines" - ], - "commandPath": [ - "usage:machines", - "list-storage-usage" - ], - "methodName": "listStorageUsage", - "summary": "List machine storage usage breakdown", - "transport": "http", - "iterable": false, - "callShape": "params", - "positional": [], - "flags": [ - { - "name": "period-start", - "optionKey": "periodStart", - "paramKey": "period_start", - "location": "query", - "required": false, - "description": "Usage period start (YYYY-MM-DD). Defaults to first of current month.", - "valueKind": "string" - }, - { - "name": "period-end", - "optionKey": "periodEnd", - "paramKey": "period_end", - "location": "query", - "required": false, - "description": "Last UTC usage date to include (YYYY-MM-DD). Defaults to current time.", - "valueKind": "string" - }, - { - "name": "machine-id", - "optionKey": "machineId", - "paramKey": "machine_id", - "location": "query", - "required": false, - "description": "Optional machine ID filter.", - "valueKind": "string" - } - ] - } -] as const satisfies readonly CliCommandDefinition[] +const commands = [] as const satisfies readonly CliCommandDefinition[] export const getProgram = (): Command => createProgram({ SDK, binaryName: "dedalus", - version: "0.1.4", + version: "0.1.0", // x-release-please-version description: "CLI for Dedalus", defaultFormat: "auto", defaultErrorFormat: "auto", clientOptions, commands, + completions, }) diff --git a/src/sdk/api-promise.ts b/src/sdk/api-promise.ts index 11abf56..7ae484c 100644 --- a/src/sdk/api-promise.ts +++ b/src/sdk/api-promise.ts @@ -1,4 +1,81 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -/** @deprecated Import from ./core/api-promise instead */ -export * from './core/api-promise'; +import type { Dedalus } from './client'; +import type { FinalRequestOptions } from './internal/request-options'; + +export type APIResponseProps = { + readonly response: Response; + readonly options: FinalRequestOptions; + readonly controller: AbortController; + readonly requestLogID?: string | undefined; + readonly retryOfRequestLogID?: string | undefined; + readonly startTime?: number | undefined; +}; + +export type ParseResponse = (client: Dedalus, props: APIResponseProps) => T | Promise; + +export const defaultParseResponse = async (_client: unknown, props: APIResponseProps): Promise => { + const { response } = props; + if (response.status === 204) return null as T; + if (props.options.__binaryResponse) return response as T; + const contentType = response.headers.get('content-type'); + const mediaType = contentType?.split(';')[0]?.trim(); + const isJson = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); + if (isJson && response.headers.get('content-length') === '0') return undefined as T; + if (isJson) return (await response.json()) as T; + return (await response.text()) as unknown as T; +}; + +/** A Promise subclass providing SDK response helpers. */ +export class APIPromise extends Promise { + private parsedPromise: Promise | undefined; + + constructor( + private readonly client: Dedalus, + private readonly responsePromise: Promise, + private readonly parseResponse: ParseResponse = defaultParseResponse, + ) { + super((resolve) => { + resolve(undefined as T); + }); + } + + _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { + return new APIPromise(this.client, this.responsePromise, async (client, props) => + transform(await this.parseResponse(client, props), props), + ); + } + + asResponse(): Promise { + return this.responsePromise.then((props) => props.response); + } + + async withResponse(): Promise<{ data: T; response: Response }> { + const [data, response] = await Promise.all([this.parse(), this.asResponse()]); + return { data, response }; + } + + private parse(): Promise { + if (!this.parsedPromise) { + this.parsedPromise = this.responsePromise.then((props) => this.parseResponse(this.client, props)); + } + return this.parsedPromise; + } + + override then( + onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, + onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | undefined | null, + ): Promise { + return this.parse().then(onfulfilled, onrejected); + } + + override catch( + onrejected?: ((reason: unknown) => TResult | PromiseLike) | undefined | null, + ): Promise { + return this.parse().catch(onrejected); + } + + override finally(onfinally?: (() => void) | undefined | null): Promise { + return this.parse().finally(onfinally); + } +} diff --git a/src/sdk/client.ts b/src/sdk/client.ts index 895d4f1..fc82280 100644 --- a/src/sdk/client.ts +++ b/src/sdk/client.ts @@ -1,7 +1,6 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -import { APIPromise } from './api-promise'; -import type { APIResponseProps } from './internal/parse'; +import { APIPromise, type APIResponseProps } from './api-promise'; import * as Errors from './error'; import { uuid4 } from './internal/utils/uuid'; import { validatePositiveInteger, isAbsoluteURL, safeJSON, isEmptyObj } from './internal/utils/values'; @@ -17,39 +16,27 @@ import type { RequestInit, RequestInfo, BodyInit, Fetch } from './internal/built import { buildHeaders, type HeadersLike } from './internal/headers'; import type { FinalRequestOptions, RequestOptions } from './internal/request-options'; import type { HTTPMethod, FinalizedRequestInit, MergedRequestInit, PromiseOrValue } from './internal/types'; -import { stringify as stringifyQuery } from './internal/qs/stringify'; -import type { StringifyOptions } from './internal/qs/types'; +import { stringifyQuery } from './internal/utils/query'; import { toFile } from './core/uploads'; import { VERSION } from './version'; -import { MachineLifecycle, type CreateMachineRequest, type UpdateMachineRequest, type CreateExecutionRequest, type CreatePreviewRequest, type CreateSSHSessionRequest, type CreateTerminalRequest, type MachineLifecycleListResponse, type MachineLifecycleCreateResponse, type MachineLifecycleDeleteResponse, type MachineLifecycleRetrieveResponse, type MachineLifecyclePatchResponse, type MachineLifecycleListArtifactsResponse, type MachineLifecycleDeleteArtifactResponse, type MachineLifecycleRetrieveArtifactResponse, type MachineLifecycleListExecutionsResponse, type MachineLifecycleCreateExecutionResponse, type MachineLifecycleDeleteExecutionResponse, type MachineLifecycleRetrieveExecutionResponse, type MachineLifecycleListExecutionEventsResponse, type MachineLifecycleListExecutionOutputResponse, type MachineLifecycleListPreviewsResponse, type MachineLifecycleCreatePreviewResponse, type MachineLifecycleDeletePreviewResponse, type MachineLifecycleRetrievePreviewResponse, type MachineLifecycleSleepResponse, type MachineLifecycleListSSHSessionsResponse, type MachineLifecycleCreateSSHSessionResponse, type MachineLifecycleDeleteSSHSessionResponse, type MachineLifecycleRetrieveSSHSessionResponse, type MachineLifecycleWatchStatusResponse, type MachineLifecycleListTerminalsResponse, type MachineLifecycleCreateTerminalResponse, type MachineLifecycleDeleteTerminalResponse, type MachineLifecycleRetrieveTerminalResponse, type MachineLifecycleWakeResponse, type MachineLifecycleListParams, type MachineLifecycleCreateParams, type MachineLifecycleDeleteParams, type MachineLifecycleRetrieveParams, type MachineLifecyclePatchParams, type MachineLifecycleListArtifactsParams, type MachineLifecycleDeleteArtifactParams, type MachineLifecycleRetrieveArtifactParams, type MachineLifecycleListExecutionsParams, type MachineLifecycleCreateExecutionParams, type MachineLifecycleDeleteExecutionParams, type MachineLifecycleRetrieveExecutionParams, type MachineLifecycleListExecutionEventsParams, type MachineLifecycleListExecutionOutputParams, type MachineLifecycleListPreviewsParams, type MachineLifecycleCreatePreviewParams, type MachineLifecycleDeletePreviewParams, type MachineLifecycleRetrievePreviewParams, type MachineLifecycleSleepParams, type MachineLifecycleListSSHSessionsParams, type MachineLifecycleCreateSSHSessionParams, type MachineLifecycleDeleteSSHSessionParams, type MachineLifecycleRetrieveSSHSessionParams, type MachineLifecycleWatchStatusParams, type MachineLifecycleListTerminalsParams, type MachineLifecycleCreateTerminalParams, type MachineLifecycleDeleteTerminalParams, type MachineLifecycleRetrieveTerminalParams, type MachineLifecycleConnectTerminalParams, type MachineLifecycleWakeParams } from "./resources/machine-lifecycle/machine-lifecycle"; -import { Usage, type UsageListResponse, type UsageListParams } from "./resources/usage/usage"; export type AuthTokenProvider = () => string | Promise; -const queryArrayFormat: NonNullable = "comma"; -const queryAllowDots = false; - -const environments = { - production: "https://api.dedaluslabs.ai", - official_dcs_api: "https://dcs.dedaluslabs.ai", -}; -type Environment = keyof typeof environments; - export interface ClientOptions { /** - * API key authentication using X-API-Key header + * Dedalus API key for Bearer token authentication. */ - apiKeyAuth?: string | AuthTokenProvider | undefined; + apiKey?: string | AuthTokenProvider | null | undefined; /** - * Dedalus API key in Authorization: Bearer . + * Dedalus API key for X-API-Key header authentication. */ - bearerAuth?: string | AuthTokenProvider | undefined; + xAPIKey?: string | AuthTokenProvider | null | undefined; /** - * API key authentication using Bearer token + * Dedalus API key in Authorization: Bearer . */ - bearer?: string | AuthTokenProvider | undefined; + bearerAuth?: string | AuthTokenProvider | undefined; /** * Provider name for BYOK mode. @@ -76,15 +63,6 @@ export interface ClientOptions { */ dedalusOrgID?: string | null | undefined; - /** - * Specifies the environment to use for the API. - * - * Each environment maps to a different base URL: - * - `production` corresponds to `https://api.dedaluslabs.ai` - * - `official_dcs_api` corresponds to `https://dcs.dedaluslabs.ai` - */ - environment?: Environment | undefined; - /** * Override the default base URL for the API, e.g., "https://api.example.com/v2/" * @@ -161,9 +139,9 @@ export type DedalusOptions = ClientOptions; * API Client for interfacing with the Dedalus API. */ export class Dedalus { - apiKeyAuth: string | AuthTokenProvider | undefined; + apiKey: string | AuthTokenProvider | null; + xAPIKey: string | AuthTokenProvider | null; bearerAuth: string | AuthTokenProvider | undefined; - bearer: string | AuthTokenProvider | undefined; provider: string | null; providerKey: string | null; providerModel: string | null; @@ -186,15 +164,14 @@ export class Dedalus { /** * API Client for interfacing with the Dedalus API. * - * @param {string | AuthTokenProvider | undefined} [opts.apiKeyAuth=process.env["API_KEY_AUTH"] ?? undefined] - * @param {string | AuthTokenProvider | undefined} [opts.bearerAuth=process.env["BEARER_AUTH"] ?? undefined] - * @param {string | AuthTokenProvider | undefined} [opts.bearer=process.env["BEARER"] ?? undefined] + * @param {string | AuthTokenProvider | null | undefined} [opts.apiKey=process.env["DEDALUS_API_KEY"] ?? null] + * @param {string | AuthTokenProvider | null | undefined} [opts.xAPIKey=process.env["DEDALUS_X_API_KEY"] ?? null] + * @param {string | AuthTokenProvider | undefined} [opts.bearerAuth=process.env["DEDALUS_BEARER_AUTH"] ?? undefined] * @param {string | null | undefined} [opts.provider=process.env["DEDALUS_PROVIDER"] ?? null] * @param {string | null | undefined} [opts.providerKey=process.env["DEDALUS_PROVIDER_KEY"] ?? null] * @param {string | null | undefined} [opts.providerModel=process.env["DEDALUS_PROVIDER_MODEL"] ?? null] - * @param {string | null | undefined} [opts.asBaseURL=process.env["DEDALUS_AS_URL"] ?? null] + * @param {string | null | undefined} [opts.asBaseURL=process.env["DEDALUS_AS_URL"] ?? "https://as.dedaluslabs.ai"] * @param {string | null | undefined} [opts.dedalusOrgID=process.env["DEDALUS_ORG_ID"] ?? null] - * @param {Environment} [opts.environment=production] - Specifies the environment URL to use for the API. * @param {string} [opts.baseURL=process.env["DEDALUS_BASE_URL"] ?? https://api.dedaluslabs.ai] - Override the default base URL for the API. * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. @@ -205,32 +182,30 @@ export class Dedalus { */ constructor({ baseURL = readEnv("DEDALUS_BASE_URL"), - apiKeyAuth = readEnv("API_KEY_AUTH"), - bearerAuth = readEnv("BEARER_AUTH"), - bearer = readEnv("BEARER"), + apiKey = readEnv("DEDALUS_API_KEY") ?? null, + xAPIKey = readEnv("DEDALUS_X_API_KEY") ?? null, + bearerAuth = readEnv("DEDALUS_BEARER_AUTH"), provider = readEnv("DEDALUS_PROVIDER") ?? null, providerKey = readEnv("DEDALUS_PROVIDER_KEY") ?? null, providerModel = readEnv("DEDALUS_PROVIDER_MODEL") ?? null, - asBaseURL = readEnv("DEDALUS_AS_URL") ?? null, + asBaseURL = readEnv("DEDALUS_AS_URL") ?? "https://as.dedaluslabs.ai", dedalusOrgID = readEnv("DEDALUS_ORG_ID") ?? null, ...opts }: ClientOptions = {}) { const options: ClientOptions = { - apiKeyAuth, + apiKey, + xAPIKey, bearerAuth, - bearer, provider, providerKey, providerModel, asBaseURL, dedalusOrgID, ...opts, - baseURL: baseURL || null, + baseURL: baseURL || "https://api.dedaluslabs.ai", }; - const environment = options.environment ?? "production"; const baseURLOverridden = baseURL !== null && baseURL !== undefined && baseURL !== ""; - if (baseURLOverridden && options.environment) throw new Errors.DedalusError("Ambiguous URL; The `baseURL` option (or DEDALUS_BASE_URL env var) and the `environment` option are given. If you want to use the environment you must pass baseURL: null"); - const defaultBaseURL = environments[environment]; + const defaultBaseURL = "https://api.dedaluslabs.ai"; this.baseURL = options.baseURL || defaultBaseURL; this.timeout = options.timeout ?? Dedalus.DEFAULT_TIMEOUT /* 1 minute */; this.logger = options.logger ?? console; @@ -258,14 +233,14 @@ export class Dedalus { options.defaultHeaders = { ...parsed, ...options.defaultHeaders }; } - this._options = { ...options, baseURL: baseURLOverridden ? this.baseURL : undefined, environment }; + this._options = { ...options, baseURL: baseURLOverridden ? this.baseURL : undefined }; this._baseURLOverridden = baseURLOverridden; this._defaultBaseURL = defaultBaseURL; this.idempotencyHeader = "Idempotency-Key"; - this.apiKeyAuth = apiKeyAuth; + this.apiKey = apiKey; + this.xAPIKey = xAPIKey; this.bearerAuth = bearerAuth; - this.bearer = bearer; this.provider = provider; this.providerKey = providerKey; this.providerModel = providerModel; @@ -283,9 +258,9 @@ export class Dedalus { logLevel: this.logLevel, fetch: this.fetch, fetchOptions: this.fetchOptions, - apiKeyAuth: this.apiKeyAuth, + apiKey: this.apiKey, + xAPIKey: this.xAPIKey, bearerAuth: this.bearerAuth, - bearer: this.bearer, provider: this.provider, providerKey: this.providerKey, providerModel: this.providerModel, @@ -306,7 +281,7 @@ export class Dedalus { } protected stringifyQuery(query: object | Record): string { - return stringifyQuery(query, { arrayFormat: queryArrayFormat, allowDots: queryAllowDots }); + return stringifyQuery(query); } private getUserAgent(): string { @@ -812,29 +787,29 @@ export class Dedalus { } private validateAuth(url: string, headers: Headers, options: FinalRequestOptions): void { - if (headers.has("x-api-key")) return; - if (headerExplicitlyOmitted(options.headers, "x-api-key")) return; if (headers.has("Authorization")) return; if (headerExplicitlyOmitted(options.headers, "Authorization")) return; - throw new Errors.AuthenticationError(401, {}, "Could not resolve authentication method. Expected x-api-key or Authorization to be set.", headers); + if (headers.has("x-api-key")) return; + if (headerExplicitlyOmitted(options.headers, "x-api-key")) return; + throw new Errors.AuthenticationError(401, {}, "Could not resolve authentication method. Expected Authorization or x-api-key to be set.", headers); } authHeadersSync(): Record { const headers: Record = {}; - const apiKeyAuth = this.resolveAuthOptionSync("apiKeyAuth", this.apiKeyAuth); - if (apiKeyAuth) headers["x-api-key"] = apiKeyAuth; + const apiKey = this.resolveAuthOptionSync("apiKey", this.apiKey); + if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`; + const xAPIKey = this.resolveAuthOptionSync("xAPIKey", this.xAPIKey); + if (xAPIKey) headers["x-api-key"] = xAPIKey; const bearerAuth = this.resolveAuthOptionSync("bearerAuth", this.bearerAuth); if (bearerAuth) headers['Authorization'] = `Bearer ${bearerAuth}`; - const bearer = this.resolveAuthOptionSync("bearer", this.bearer); - if (bearer) headers['Authorization'] = `Bearer ${bearer}`; return headers; } webSocketAuthHeaders(): Record { - const bearerAuth = this.resolveAuthOptionSync("bearerAuth", this.bearerAuth); - if (bearerAuth) return { Authorization: `Bearer ${bearerAuth}` }; - const apiKeyAuth = this.resolveAuthOptionSync("apiKeyAuth", this.apiKeyAuth); - if (apiKeyAuth) return { "x-api-key": apiKeyAuth }; + const apiKey = this.resolveAuthOptionSync("apiKey", this.apiKey); + if (apiKey) return { Authorization: `Bearer ${apiKey}` }; + const xAPIKey = this.resolveAuthOptionSync("xAPIKey", this.xAPIKey); + if (xAPIKey) return { "x-api-key": xAPIKey }; return {}; } @@ -854,12 +829,12 @@ export class Dedalus { private async authHeadersAsync(): Promise> { const headers: Record = {}; - const apiKeyAuth = await this.resolveAuthOption("apiKeyAuth", this.apiKeyAuth); - if (apiKeyAuth) headers["x-api-key"] = apiKeyAuth; + const apiKey = await this.resolveAuthOption("apiKey", this.apiKey); + if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`; + const xAPIKey = await this.resolveAuthOption("xAPIKey", this.xAPIKey); + if (xAPIKey) headers["x-api-key"] = xAPIKey; const bearerAuth = await this.resolveAuthOption("bearerAuth", this.bearerAuth); if (bearerAuth) headers['Authorization'] = `Bearer ${bearerAuth}`; - const bearer = await this.resolveAuthOption("bearer", this.bearer); - if (bearer) headers['Authorization'] = `Bearer ${bearer}`; return headers; } @@ -896,89 +871,10 @@ export class Dedalus { static toFile = toFile; - machineLifecycle: MachineLifecycle = new MachineLifecycle(this); - usage: Usage = new Usage(this); } -Dedalus.MachineLifecycle = MachineLifecycle; -Dedalus.Usage = Usage; - export declare namespace Dedalus { export type RequestOptions = Opts.RequestOptions; - export { - MachineLifecycle as MachineLifecycle, - type CreateMachineRequest as CreateMachineRequest, - type UpdateMachineRequest as UpdateMachineRequest, - type CreateExecutionRequest as CreateExecutionRequest, - type CreatePreviewRequest as CreatePreviewRequest, - type CreateSSHSessionRequest as CreateSSHSessionRequest, - type CreateTerminalRequest as CreateTerminalRequest, - type MachineLifecycleListResponse as MachineLifecycleListResponse, - type MachineLifecycleCreateResponse as MachineLifecycleCreateResponse, - type MachineLifecycleDeleteResponse as MachineLifecycleDeleteResponse, - type MachineLifecycleRetrieveResponse as MachineLifecycleRetrieveResponse, - type MachineLifecyclePatchResponse as MachineLifecyclePatchResponse, - type MachineLifecycleListArtifactsResponse as MachineLifecycleListArtifactsResponse, - type MachineLifecycleDeleteArtifactResponse as MachineLifecycleDeleteArtifactResponse, - type MachineLifecycleRetrieveArtifactResponse as MachineLifecycleRetrieveArtifactResponse, - type MachineLifecycleListExecutionsResponse as MachineLifecycleListExecutionsResponse, - type MachineLifecycleCreateExecutionResponse as MachineLifecycleCreateExecutionResponse, - type MachineLifecycleDeleteExecutionResponse as MachineLifecycleDeleteExecutionResponse, - type MachineLifecycleRetrieveExecutionResponse as MachineLifecycleRetrieveExecutionResponse, - type MachineLifecycleListExecutionEventsResponse as MachineLifecycleListExecutionEventsResponse, - type MachineLifecycleListExecutionOutputResponse as MachineLifecycleListExecutionOutputResponse, - type MachineLifecycleListPreviewsResponse as MachineLifecycleListPreviewsResponse, - type MachineLifecycleCreatePreviewResponse as MachineLifecycleCreatePreviewResponse, - type MachineLifecycleDeletePreviewResponse as MachineLifecycleDeletePreviewResponse, - type MachineLifecycleRetrievePreviewResponse as MachineLifecycleRetrievePreviewResponse, - type MachineLifecycleSleepResponse as MachineLifecycleSleepResponse, - type MachineLifecycleListSSHSessionsResponse as MachineLifecycleListSSHSessionsResponse, - type MachineLifecycleCreateSSHSessionResponse as MachineLifecycleCreateSSHSessionResponse, - type MachineLifecycleDeleteSSHSessionResponse as MachineLifecycleDeleteSSHSessionResponse, - type MachineLifecycleRetrieveSSHSessionResponse as MachineLifecycleRetrieveSSHSessionResponse, - type MachineLifecycleWatchStatusResponse as MachineLifecycleWatchStatusResponse, - type MachineLifecycleListTerminalsResponse as MachineLifecycleListTerminalsResponse, - type MachineLifecycleCreateTerminalResponse as MachineLifecycleCreateTerminalResponse, - type MachineLifecycleDeleteTerminalResponse as MachineLifecycleDeleteTerminalResponse, - type MachineLifecycleRetrieveTerminalResponse as MachineLifecycleRetrieveTerminalResponse, - type MachineLifecycleWakeResponse as MachineLifecycleWakeResponse, - type MachineLifecycleListParams as MachineLifecycleListParams, - type MachineLifecycleCreateParams as MachineLifecycleCreateParams, - type MachineLifecycleDeleteParams as MachineLifecycleDeleteParams, - type MachineLifecycleRetrieveParams as MachineLifecycleRetrieveParams, - type MachineLifecyclePatchParams as MachineLifecyclePatchParams, - type MachineLifecycleListArtifactsParams as MachineLifecycleListArtifactsParams, - type MachineLifecycleDeleteArtifactParams as MachineLifecycleDeleteArtifactParams, - type MachineLifecycleRetrieveArtifactParams as MachineLifecycleRetrieveArtifactParams, - type MachineLifecycleListExecutionsParams as MachineLifecycleListExecutionsParams, - type MachineLifecycleCreateExecutionParams as MachineLifecycleCreateExecutionParams, - type MachineLifecycleDeleteExecutionParams as MachineLifecycleDeleteExecutionParams, - type MachineLifecycleRetrieveExecutionParams as MachineLifecycleRetrieveExecutionParams, - type MachineLifecycleListExecutionEventsParams as MachineLifecycleListExecutionEventsParams, - type MachineLifecycleListExecutionOutputParams as MachineLifecycleListExecutionOutputParams, - type MachineLifecycleListPreviewsParams as MachineLifecycleListPreviewsParams, - type MachineLifecycleCreatePreviewParams as MachineLifecycleCreatePreviewParams, - type MachineLifecycleDeletePreviewParams as MachineLifecycleDeletePreviewParams, - type MachineLifecycleRetrievePreviewParams as MachineLifecycleRetrievePreviewParams, - type MachineLifecycleSleepParams as MachineLifecycleSleepParams, - type MachineLifecycleListSSHSessionsParams as MachineLifecycleListSSHSessionsParams, - type MachineLifecycleCreateSSHSessionParams as MachineLifecycleCreateSSHSessionParams, - type MachineLifecycleDeleteSSHSessionParams as MachineLifecycleDeleteSSHSessionParams, - type MachineLifecycleRetrieveSSHSessionParams as MachineLifecycleRetrieveSSHSessionParams, - type MachineLifecycleWatchStatusParams as MachineLifecycleWatchStatusParams, - type MachineLifecycleListTerminalsParams as MachineLifecycleListTerminalsParams, - type MachineLifecycleCreateTerminalParams as MachineLifecycleCreateTerminalParams, - type MachineLifecycleDeleteTerminalParams as MachineLifecycleDeleteTerminalParams, - type MachineLifecycleRetrieveTerminalParams as MachineLifecycleRetrieveTerminalParams, - type MachineLifecycleConnectTerminalParams as MachineLifecycleConnectTerminalParams, - type MachineLifecycleWakeParams as MachineLifecycleWakeParams, - }; - - export { - Usage as Usage, - type UsageListResponse as UsageListResponse, - type UsageListParams as UsageListParams, - }; } diff --git a/src/sdk/core/EventEmitter.ts b/src/sdk/core/EventEmitter.ts deleted file mode 100644 index 24c7fd6..0000000 --- a/src/sdk/core/EventEmitter.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -type EventListener = Events[EventType]; - -type EventListeners = Array<{ listener: EventListener; once?: boolean; }>; - -export type EventParameters = { - [Event in EventType]: EventListener extends (...args: infer P) => unknown ? P : never; -}[EventType]; - -export class EventEmitter unknown>> { - #listeners: { [Event in keyof EventTypes]?: EventListeners; } = {}; - - on(event: Event, listener: EventListener): this { - const listeners: EventListeners = this.#listeners[event] || (this.#listeners[event] = []); - listeners.push({ listener }); - return this; - } - - off(event: Event, listener: EventListener): this { - const listeners = this.#listeners[event]; - if (!listeners) return this; - const index = listeners.findIndex((item) => item.listener === listener); - if (index >= 0) listeners.splice(index, 1); - return this; - } - - once(event: Event, listener: EventListener): this { - const listeners: EventListeners = this.#listeners[event] || (this.#listeners[event] = []); - listeners.push({ listener, once: true }); - return this; - } - - protected _emit(event: Event, ...args: EventParameters): void { - const listeners = this.#listeners[event]; - if (!listeners) return; - this.#listeners[event] = listeners.filter((listener) => !listener.once) as EventListeners; - for (const { listener } of listeners) (listener as (...args: EventParameters) => unknown)(...args); - } - - protected _hasListener(event: keyof EventTypes): boolean { - return (this.#listeners[event]?.length ?? 0) > 0; - } -} - -export class InternalEventEmitter unknown>> extends EventEmitter { - override _emit(event: Event, ...args: EventParameters): void { - super._emit(event, ...args); - } -} diff --git a/src/sdk/core/api-promise.ts b/src/sdk/core/api-promise.ts index 9c22b4c..c2994e2 100644 --- a/src/sdk/core/api-promise.ts +++ b/src/sdk/core/api-promise.ts @@ -1,92 +1,4 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -import { type Dedalus } from '../client'; - -import { type PromiseOrValue } from '../internal/types'; -import { APIResponseProps, defaultParseResponse } from '../internal/parse'; - -/** - * A subclass of `Promise` providing additional helper methods - * for interacting with the SDK. - */ -export class APIPromise extends Promise { - private parsedPromise: Promise | undefined; - #client: Dedalus; - - constructor( - client: Dedalus, - private responsePromise: Promise, - private parseResponse: ( - client: Dedalus, - props: APIResponseProps, - ) => PromiseOrValue = defaultParseResponse, - ) { - super((resolve) => { - // this is maybe a bit weird but this has to be a no-op to not implicitly - // parse the response body; instead .then, .catch, .finally are overridden - // to parse the response - resolve(null as any); - }); - this.#client = client; - } - - _thenUnwrap(transform: (data: T, props: APIResponseProps) => U): APIPromise { - return new APIPromise(this.#client, this.responsePromise, async (client, props) => - transform(await this.parseResponse(client, props), props), - ); - } - - /** - * Gets the raw `Response` instance instead of parsing the response - * data. - * - * If you want to parse the response body but still get the `Response` - * instance, you can use {@link withResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - asResponse(): Promise { - return this.responsePromise.then((p) => p.response); - } - - /** - * Gets the parsed response data and the raw `Response` instance. - * - * If you just want to get the raw `Response` instance without parsing it, - * you can use {@link asResponse()}. - * - * 👋 Getting the wrong TypeScript type for `Response`? - * Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]` - * to your `tsconfig.json`. - */ - async withResponse(): Promise<{ data: T; response: Response }> { - const [data, response] = await Promise.all([this.parse(), this.asResponse()]); - return { data, response }; - } - - private parse(): Promise { - if (!this.parsedPromise) { - this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(this.#client, data)); - } - return this.parsedPromise; - } - - override then( - onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null, - ): Promise { - return this.parse().then(onfulfilled, onrejected); - } - - override catch( - onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null, - ): Promise { - return this.parse().catch(onrejected); - } - - override finally(onfinally?: (() => void) | undefined | null): Promise { - return this.parse().finally(onfinally); - } -} +export { APIPromise, defaultParseResponse } from '../api-promise'; +export type { APIResponseProps, ParseResponse } from '../api-promise'; diff --git a/src/sdk/resources/machine-lifecycle.ts b/src/sdk/core/resource.ts similarity index 64% rename from src/sdk/resources/machine-lifecycle.ts rename to src/sdk/core/resource.ts index dd79961..b71ddf0 100644 --- a/src/sdk/resources/machine-lifecycle.ts +++ b/src/sdk/core/resource.ts @@ -1,3 +1,3 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -export * from "./machine-lifecycle/index"; +export { APIResource } from '../resource'; diff --git a/src/sdk/core/streaming.ts b/src/sdk/core/streaming.ts deleted file mode 100644 index 6b8893e..0000000 --- a/src/sdk/core/streaming.ts +++ /dev/null @@ -1,333 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { DedalusError } from './error'; -import { type ReadableStream } from '../internal/shim-types'; -import { makeReadableStream } from '../internal/shims'; -import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line'; -import { ReadableStreamToAsyncIterable } from '../internal/shims'; -import { isAbortError } from '../internal/errors'; -import { safeJSON } from '../internal/utils/values'; -import { encodeUTF8 } from '../internal/utils/bytes'; -import { loggerFor } from '../internal/utils/log'; -import type { Dedalus } from '../client'; - -import { APIError } from './error'; - -type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; - -export type ServerSentEvent = { - event: string | null; - data: string; - raw: string[]; -}; - -export class Stream implements AsyncIterable { - controller: AbortController; - #client: Dedalus | undefined; - - constructor( - private iterator: () => AsyncIterator, - controller: AbortController, - client?: Dedalus, - ) { - this.controller = controller; - this.#client = client; - } - - static fromSSEResponse( - response: Response, - controller: AbortController, - client?: Dedalus, - ): Stream { - let consumed = false; - const logger = client ? loggerFor(client) : console; - - async function* iterator(): AsyncIterator { - if (consumed) { - throw new DedalusError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); - } - consumed = true; - let done = false; - try { - for await (const sse of _iterSSEMessages(response, controller)) { - if (done) continue; - - if (sse.data.startsWith('[DONE]')) { - done = true; - continue; - } - - if (sse.event === 'error') { - throw new APIError(undefined, safeJSON(sse.data) ?? sse.data, undefined, response.headers); - } - - if (sse.event === null) { - try { - yield JSON.parse(sse.data); - } catch (e) { - logger.error(`Could not parse message into JSON:`, sse.data); - logger.error(`From chunk:`, sse.raw); - throw e; - } - } - } - done = true; - } catch (e) { - // If the user calls `stream.controller.abort()`, we should exit without throwing. - if (isAbortError(e)) return; - throw e; - } finally { - // If the user `break`s, abort the ongoing request. - if (!done) controller.abort(); - } - } - - return new Stream(iterator, controller, client); - } - - /** - * Generates a Stream from a newline-separated ReadableStream - * where each item is a JSON value. - */ - static fromReadableStream( - readableStream: ReadableStream, - controller: AbortController, - client?: Dedalus, - ): Stream { - let consumed = false; - - async function* iterLines(): AsyncGenerator { - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(readableStream); - for await (const chunk of iter) { - for (const line of lineDecoder.decode(chunk)) { - yield line; - } - } - - for (const line of lineDecoder.flush()) { - yield line; - } - } - - async function* iterator(): AsyncIterator { - if (consumed) { - throw new DedalusError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); - } - consumed = true; - let done = false; - try { - for await (const line of iterLines()) { - if (done) continue; - if (line) yield JSON.parse(line); - } - done = true; - } catch (e) { - // If the user calls `stream.controller.abort()`, we should exit without throwing. - if (isAbortError(e)) return; - throw e; - } finally { - // If the user `break`s, abort the ongoing request. - if (!done) controller.abort(); - } - } - - return new Stream(iterator, controller, client); - } - - [Symbol.asyncIterator](): AsyncIterator { - return this.iterator(); - } - - /** - * Splits the stream into two streams which can be - * independently read from at different speeds. - */ - tee(): [Stream, Stream] { - const left: Array>> = []; - const right: Array>> = []; - const iterator = this.iterator(); - - const teeIterator = (queue: Array>>): AsyncIterator => { - return { - next: () => { - if (queue.length === 0) { - const result = iterator.next(); - left.push(result); - right.push(result); - } - return queue.shift()!; - }, - }; - }; - - return [ - new Stream(() => teeIterator(left), this.controller, this.#client), - new Stream(() => teeIterator(right), this.controller, this.#client), - ]; - } - - /** - * Converts this stream to a newline-separated ReadableStream of - * JSON stringified values in the stream - * which can be turned back into a Stream with `Stream.fromReadableStream()`. - */ - toReadableStream(): ReadableStream { - const self = this; - let iter: AsyncIterator; - - return makeReadableStream({ - async start() { - iter = self[Symbol.asyncIterator](); - }, - async pull(ctrl: any) { - try { - const { value, done } = await iter.next(); - if (done) return ctrl.close(); - - const bytes = encodeUTF8(JSON.stringify(value) + '\n'); - - ctrl.enqueue(bytes); - } catch (err) { - ctrl.error(err); - } - }, - async cancel() { - await iter.return?.(); - }, - }); - } -} - -export async function* _iterSSEMessages( - response: Response, - controller: AbortController, -): AsyncGenerator { - if (!response.body) { - controller.abort(); - if ( - typeof (globalThis as any).navigator !== 'undefined' && - (globalThis as any).navigator.product === 'ReactNative' - ) { - throw new DedalusError( - `The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`, - ); - } - throw new DedalusError(`Attempted to iterate over a response with no body`); - } - - const sseDecoder = new SSEDecoder(); - const lineDecoder = new LineDecoder(); - - const iter = ReadableStreamToAsyncIterable(response.body); - for await (const sseChunk of iterSSEChunks(iter)) { - for (const line of lineDecoder.decode(sseChunk)) { - const sse = sseDecoder.decode(line); - if (sse) yield sse; - } - } - - for (const line of lineDecoder.flush()) { - const sse = sseDecoder.decode(line); - if (sse) yield sse; - } -} - -/** - * Given an async iterable iterator, iterates over it and yields full - * SSE chunks, i.e. yields when a double new-line is encountered. - */ -async function* iterSSEChunks(iterator: AsyncIterableIterator): AsyncGenerator { - let data = new Uint8Array(); - - for await (const chunk of iterator) { - if (chunk == null) { - continue; - } - - const binaryChunk = - chunk instanceof ArrayBuffer ? new Uint8Array(chunk) - : typeof chunk === 'string' ? encodeUTF8(chunk) - : chunk; - - let newData = new Uint8Array(data.length + binaryChunk.length); - newData.set(data); - newData.set(binaryChunk, data.length); - data = newData; - - let patternIndex; - while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) { - yield data.slice(0, patternIndex); - data = data.slice(patternIndex); - } - } - - if (data.length > 0) { - yield data; - } -} - -class SSEDecoder { - private data: string[]; - private event: string | null; - private chunks: string[]; - - constructor() { - this.event = null; - this.data = []; - this.chunks = []; - } - - decode(line: string) { - if (line.endsWith('\r')) { - line = line.substring(0, line.length - 1); - } - - if (!line) { - // empty line and we didn't previously encounter any messages - if (!this.event && !this.data.length) return null; - - const sse: ServerSentEvent = { - event: this.event, - data: this.data.join('\n'), - raw: this.chunks, - }; - - this.event = null; - this.data = []; - this.chunks = []; - - return sse; - } - - this.chunks.push(line); - - if (line.startsWith(':')) { - return null; - } - - let [fieldname, _, value] = partition(line, ':'); - - if (value.startsWith(' ')) { - value = value.substring(1); - } - - if (fieldname === 'event') { - this.event = value; - } else if (fieldname === 'data') { - this.data.push(value); - } - - return null; - } -} - -function partition(str: string, delimiter: string): [string, string, string] { - const index = str.indexOf(delimiter); - if (index !== -1) { - return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)]; - } - - return [str, '', '']; -} diff --git a/src/sdk/index.ts b/src/sdk/index.ts index 46ba028..45c1d83 100644 --- a/src/sdk/index.ts +++ b/src/sdk/index.ts @@ -4,8 +4,7 @@ export { Dedalus as default } from './client.js'; export { type Uploadable, toFile } from './core/uploads'; export { APIPromise } from './api-promise'; -export { type RawWebSocketData, type ReconnectingEvent, type ReconnectingOverrides, type UnsentMessage } from './internal/ws'; -export { Dedalus, type ClientOptions, type DedalusOptions, type Logger, type LogLevel } from './client.js'; +export { Dedalus, type ClientOptions } from './client.js'; export { DedalusError, APIError, diff --git a/src/sdk/internal/decoders/line.ts b/src/sdk/internal/decoders/line.ts deleted file mode 100644 index b3bfa97..0000000 --- a/src/sdk/internal/decoders/line.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes'; - -export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined; - -/** - * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally - * reading lines from text. - * - * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258 - */ -export class LineDecoder { - // prettier-ignore - static NEWLINE_CHARS = new Set(['\n', '\r']); - static NEWLINE_REGEXP = /\r\n|[\n\r]/g; - - #buffer: Uint8Array; - #carriageReturnIndex: number | null; - - constructor() { - this.#buffer = new Uint8Array(); - this.#carriageReturnIndex = null; - } - - decode(chunk: Bytes): string[] { - if (chunk == null) { - return []; - } - - const binaryChunk = - chunk instanceof ArrayBuffer ? new Uint8Array(chunk) - : typeof chunk === 'string' ? encodeUTF8(chunk) - : chunk; - - this.#buffer = concatBytes([this.#buffer, binaryChunk]); - - const lines: string[] = []; - let patternIndex; - while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) { - if (patternIndex.carriage && this.#carriageReturnIndex == null) { - // skip until we either get a corresponding `\n`, a new `\r` or nothing - this.#carriageReturnIndex = patternIndex.index; - continue; - } - - // we got double \r or \rtext\n - if ( - this.#carriageReturnIndex != null && - (patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage) - ) { - lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1))); - this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex); - this.#carriageReturnIndex = null; - continue; - } - - const endIndex = - this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding; - - const line = decodeUTF8(this.#buffer.subarray(0, endIndex)); - lines.push(line); - - this.#buffer = this.#buffer.subarray(patternIndex.index); - this.#carriageReturnIndex = null; - } - - return lines; - } - - flush(): string[] { - if (!this.#buffer.length) { - return []; - } - return this.decode('\n'); - } -} - -/** - * This function searches the buffer for the end patterns, (\r or \n) - * and returns an object with the index preceding the matched newline and the - * index after the newline char. `null` is returned if no new line is found. - * - * ```ts - * findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 } - * ``` - */ -function findNewlineIndex( - buffer: Uint8Array, - startIndex: number | null, -): { preceding: number; index: number; carriage: boolean } | null { - const newline = 0x0a; // \n - const carriage = 0x0d; // \r - - for (let i = startIndex ?? 0; i < buffer.length; i++) { - if (buffer[i] === newline) { - return { preceding: i, index: i + 1, carriage: false }; - } - - if (buffer[i] === carriage) { - return { preceding: i, index: i + 1, carriage: true }; - } - } - - return null; -} - -export function findDoubleNewlineIndex(buffer: Uint8Array): number { - // This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n) - // and returns the index right after the first occurrence of any pattern, - // or -1 if none of the patterns are found. - const newline = 0x0a; // \n - const carriage = 0x0d; // \r - - for (let i = 0; i < buffer.length - 1; i++) { - if (buffer[i] === newline && buffer[i + 1] === newline) { - // \n\n - return i + 2; - } - if (buffer[i] === carriage && buffer[i + 1] === carriage) { - // \r\r - return i + 2; - } - if ( - buffer[i] === carriage && - buffer[i + 1] === newline && - i + 3 < buffer.length && - buffer[i + 2] === carriage && - buffer[i + 3] === newline - ) { - // \r\n\r\n - return i + 4; - } - } - - return -1; -} diff --git a/src/sdk/internal/parse.ts b/src/sdk/internal/parse.ts index bf0f241..a657cc5 100644 --- a/src/sdk/internal/parse.ts +++ b/src/sdk/internal/parse.ts @@ -1,76 +1,4 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -import type { FinalRequestOptions } from './request-options'; -import { Stream } from '../core/streaming'; -import { type Dedalus } from '../client'; -import { formatRequestDetails, loggerFor } from './utils/log'; - -export type APIResponseProps = { - response: Response; - options: FinalRequestOptions; - controller: AbortController; - requestLogID: string; - retryOfRequestLogID: string | undefined; - startTime: number; -}; - -export async function defaultParseResponse(client: Dedalus, props: APIResponseProps): Promise { - const { response, requestLogID, retryOfRequestLogID, startTime } = props; - const body = await (async () => { - if (props.options.stream) { - loggerFor(client).debug('response', response.status, response.url, response.headers, response.body); - - // Note: there is an invariant here that isn't represented in the type system - // that if you set `stream: true` the response type must also be `Stream` - - if (props.options.__streamClass) { - return props.options.__streamClass.fromSSEResponse(response, props.controller, client) as any; - } - - const contentType = response.headers.get('content-type'); - if (contentType?.includes('ndjson') || contentType?.includes('jsonl')) { - if (!response.body) throw new Error('Attempted to iterate over a response with no body'); - return Stream.fromReadableStream(response.body, props.controller, client) as any; - } - - return Stream.fromSSEResponse(response, props.controller, client) as any; - } - - // fetch refuses to read the body when the status code is 204. - if (response.status === 204) { - return null as T; - } - - if (props.options.__binaryResponse) { - return response as unknown as T; - } - - const contentType = response.headers.get('content-type'); - const mediaType = contentType?.split(';')[0]?.trim(); - const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json'); - if (isJSON) { - const contentLength = response.headers.get('content-length'); - if (contentLength === '0') { - // if there is no content we can't do anything - return undefined as T; - } - - const json = await response.json(); - return json as T; - } - - const text = await response.text(); - return text as unknown as T; - })(); - loggerFor(client).debug( - `[${requestLogID}] response parsed`, - formatRequestDetails({ - retryOfRequestLogID, - url: response.url, - status: response.status, - body, - durationMs: Date.now() - startTime, - }), - ); - return body; -} +export { defaultParseResponse } from '../api-promise'; +export type { APIResponseProps, ParseResponse } from '../api-promise'; diff --git a/src/sdk/internal/request-options.ts b/src/sdk/internal/request-options.ts index 55b1438..4dc4258 100644 --- a/src/sdk/internal/request-options.ts +++ b/src/sdk/internal/request-options.ts @@ -1,89 +1,35 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -import { NullableHeaders } from './headers'; - import type { BodyInit } from './builtin-types'; -import { Stream } from '../core/streaming'; import type { HTTPMethod, MergedRequestInit } from './types'; -import { type HeadersLike } from './headers'; - -export type FinalRequestOptions = RequestOptions & { method: HTTPMethod; path: string }; +import type { HeadersLike, NullableHeaders } from './headers'; export type RequestOptions = { - /** - * The HTTP method for the request (e.g., 'get', 'post', 'put', 'delete'). - */ - method?: HTTPMethod; - - /** - * The URL path for the request. - * - * @example "/v1/foo" - */ - path?: string; - - /** - * Query parameters to include in the request URL. - */ + method?: HTTPMethod | undefined; + path?: string | undefined; + headers?: HeadersLike | undefined; query?: object | undefined | null; - - /** - * The request body. Can be a string, JSON object, FormData, or other supported types. - */ body?: unknown; - - /** - * HTTP headers to include with the request. Can be a Headers object, plain object, or array of tuples. - */ - headers?: HeadersLike; - - /** - * The maximum number of times that the client will retry a request in case of a - * temporary failure, like a network error or a 5XX error from the server. - * - * @default 2 - */ - maxRetries?: number; - + timeout?: number | undefined; + maxRetries?: number | undefined; stream?: boolean | undefined; - - /** - * The maximum amount of time (in milliseconds) that the client should wait for a response - * from the server before timing out a single request. - * - * @unit milliseconds - */ - timeout?: number; - - /** - * Additional `RequestInit` options to be passed to the underlying `fetch` call. - * These options will be merged with the client's default fetch options. - */ - fetchOptions?: MergedRequestInit; - - /** - * An AbortSignal that can be used to cancel the request. - */ signal?: AbortSignal | undefined | null; - - /** - * A unique key for this request to enable idempotency. - */ - idempotencyKey?: string; - - /** - * Override the default base URL for this specific request. - */ + fetchOptions?: MergedRequestInit | undefined; + idempotencyKey?: string | undefined; defaultBaseURL?: string | undefined; - __binaryResponse?: boolean | undefined; - __streamClass?: typeof Stream; +}; + +export type FinalRequestOptions = RequestOptions & { + method: HTTPMethod; + path: string; }; export type EncodedContent = { bodyHeaders: HeadersLike; body: BodyInit }; export type RequestEncoder = (request: { headers: NullableHeaders; body: unknown }) => EncodedContent; -export const FallbackEncoder: RequestEncoder = ({ headers, body }) => { +/** Fallback JSON encoder used when a request body is not already a fetch body type. */ +export const FallbackEncoder: RequestEncoder = ({ body }) => { return { bodyHeaders: { 'content-type': 'application/json', diff --git a/src/sdk/internal/types.ts b/src/sdk/internal/types.ts index 93a4f5a..7c7dee6 100644 --- a/src/sdk/internal/types.ts +++ b/src/sdk/internal/types.ts @@ -62,16 +62,22 @@ type OverloadedParameters = * * [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition */ +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users with \@types/node */ /* prettier-ignore */ type UndiciTypesRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users with undici */ /* prettier-ignore */ type UndiciRequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users with \@types/bun */ /* prettier-ignore */ type BunRequestInit = globalThis.FetchRequestInit; +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users with node-fetch@2 */ /* prettier-ignore */ type NodeFetch2RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users with node-fetch@3, doesn't need file extension because types are at ./@types/index.d.ts */ /* prettier-ignore */ type NodeFetch3RequestInit = NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny | NotAny; +// biome-ignore format: the whole alias must stay on one physical line — the @ts-ignore below only suppresses the first line of the statement it precedes /** @ts-ignore For users who use Deno */ /* prettier-ignore */ type FetchRequestInit = NonNullable[1]>; diff --git a/src/sdk/internal/utils.ts b/src/sdk/internal/utils.ts index 57f670f..986ff46 100644 --- a/src/sdk/internal/utils.ts +++ b/src/sdk/internal/utils.ts @@ -6,3 +6,4 @@ export * from './utils/env'; export * from './utils/log'; export * from './utils/uuid'; export * from './utils/sleep'; +export * from './utils/query'; diff --git a/src/sdk/internal/utils/query.ts b/src/sdk/internal/utils/query.ts new file mode 100644 index 0000000..638b5a0 --- /dev/null +++ b/src/sdk/internal/utils/query.ts @@ -0,0 +1,10 @@ +// File generated from our OpenAPI spec by Scalar. See README.md for details. + +import * as qs from '../qs/stringify'; + +export function stringifyQuery(query: object | Record): string { + // Substituted from the same config as the client's own `stringifyQuery`; this module is re-exported + // through the package's `./*` subpath, so a hardcoded format here would serialize arrays differently + // from every generated request. + return qs.stringify(query, { arrayFormat: 'comma', allowDots: false }); +} diff --git a/src/sdk/internal/ws-adapter-browser.ts b/src/sdk/internal/ws-adapter-browser.ts deleted file mode 100644 index 1a1aa4c..0000000 --- a/src/sdk/internal/ws-adapter-browser.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { WebSocketLike } from './ws-adapter'; - -/** A generic event listener callback. */ -type Listener = (...args: any[]) => void; - -/** A DOM-style event handler passed to addEventListener/removeEventListener. */ -type DOMEventHandler = (ev: any) => void; - -// Minimal browser API type declarations. -declare class WebSocket { - readonly readyState: number; - binaryType: string; - send(data: string | ArrayBufferLike | ArrayBufferView): void; - close(code?: number, reason?: string): void; - addEventListener(type: string, listener: DOMEventHandler): void; - removeEventListener(type: string, listener: DOMEventHandler): void; -} - -interface MessageEvent { - data: any; -} - -interface CloseEvent { - code: number; - reason: string; -} - -export class BrowserWebSocket implements WebSocketLike { - private _ws: WebSocket; - private _listenerMap = new Map>(); - - constructor(ws: WebSocket) { - this._ws = ws; - this._ws.binaryType = 'arraybuffer'; - } - - /** The underlying platform-specific socket. Code that accesses this will not be isomorphic across server and browser environments. */ - get platformSocket(): WebSocket { - return this._ws; - } - - get readyState(): number { - return this._ws.readyState; - } - - send(data: string | ArrayBufferLike | ArrayBufferView): void { - this._ws.send(data); - } - - close(code?: number, reason?: string): void { - this._ws.close(code, reason); - } - - on(event: string, listener: Listener): void { - const wrapped = this._wrapListener(event, listener); - this._listenersFor(event).set(listener, wrapped); - this._ws.addEventListener(event, wrapped); - } - - off(event: string, listener: Listener): void { - const byListener = this._listenerMap.get(event); - if (!byListener) return; - const wrapped = byListener.get(listener); - if (wrapped) { - byListener.delete(listener); - this._ws.removeEventListener(event, wrapped); - } - } - - once(event: string, listener: Listener): void { - const onceListener: Listener = (...args) => { - this.off(event, listener); - listener(...args); - }; - const wrapped = this._wrapListener(event, onceListener); - this._listenersFor(event).set(listener, wrapped); - this._ws.addEventListener(event, wrapped); - } - - private _listenersFor(event: string): Map { - let map = this._listenerMap.get(event); - if (!map) { - map = new Map(); - this._listenerMap.set(event, map); - } - return map; - } - - /** - * Converts browser event objects to positional arguments matching the - * {@link WebSocketLike} interface. - */ - private _wrapListener(event: string, listener: Listener): DOMEventHandler { - switch (event) { - case 'message': - return (ev: MessageEvent) => { - const isBinary = typeof ev.data !== 'string'; - listener(ev.data, isBinary); - }; - - case 'close': - return (ev: CloseEvent) => { - listener(ev.code, ev.reason); - }; - - case 'error': - return (ev: any) => { - // Some environments provide an ErrorEvent with a `.message`; - // fall back to a generic message when the event carries nothing. - const message = ev?.message || ev?.error?.message || 'WebSocket error'; - const err = new Error(message); - if (ev?.error) { - (err as any).cause = ev.error; - } - listener(err); - }; - - case 'open': - default: - return listener as DOMEventHandler; - } - } -} diff --git a/src/sdk/internal/ws-adapter-node.ts b/src/sdk/internal/ws-adapter-node.ts deleted file mode 100644 index c8632aa..0000000 --- a/src/sdk/internal/ws-adapter-node.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type * as WS from 'ws'; -import type { WebSocketLike } from './ws-adapter'; - -/** A generic event listener callback. */ -type Listener = (...args: any[]) => void; - -export class NodeWebSocket implements WebSocketLike { - private _ws: WS.WebSocket; - - /** Maps `(event, originalListener)` -> wrapped listener for correct `off()` removal. */ - private _listenerMap = new Map>(); - - constructor(ws: WS.WebSocket) { - this._ws = ws; - } - - /** The underlying platform-specific socket. Code that accesses this will not be isomorphic across server and browser environments. */ - get platformSocket(): WS.WebSocket { - return this._ws; - } - - get readyState(): number { - return this._ws.readyState; - } - - send(data: string | ArrayBufferLike | ArrayBufferView): void { - this._ws.send(data); - } - - close(code?: number, reason?: string): void { - this._ws.close(code, reason); - } - - on(event: string, listener: Listener): void { - const wrapped = this._wrapListener(event, listener); - this._listenersFor(event).set(listener, wrapped); - this._ws.on(event, wrapped); - } - - off(event: string, listener: Listener): void { - const byListener = this._listenerMap.get(event); - if (!byListener) return; - const wrapped = byListener.get(listener); - if (wrapped) { - byListener.delete(listener); - this._ws.removeListener(event, wrapped); - } - } - - once(event: string, listener: Listener): void { - const onceListener: Listener = (...args) => { - this.off(event, listener); - listener(...args); - }; - const wrapped = this._wrapListener(event, onceListener); - this._listenersFor(event).set(listener, wrapped); - this._ws.on(event, wrapped); - } - - private _listenersFor(event: string): Map { - let map = this._listenerMap.get(event); - if (!map) { - map = new Map(); - this._listenerMap.set(event, map); - } - return map; - } - - /** - * Normalizes `ws` message payloads: text frames become strings, - * binary frames stay as `Buffer`, and fragmented frames are merged. - */ - private static _normalizeMessageData( - data: Buffer | ArrayBuffer | Buffer[], - isBinary: boolean, - ): string | Buffer { - if (!isBinary) { - if (Array.isArray(data)) return Buffer.concat(data).toString(); - if (data instanceof ArrayBuffer) return Buffer.from(data).toString(); - return data.toString(); - } - - if (Array.isArray(data)) return Buffer.concat(data); - if (data instanceof ArrayBuffer) return Buffer.from(data); - return data; - } - - private _wrapListener(event: string, listener: Listener): Listener { - switch (event) { - case 'message': - return (data: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) => { - listener(NodeWebSocket._normalizeMessageData(data, isBinary), isBinary); - }; - - case 'close': - return (code: number, reason: Buffer) => { - listener(code, reason.toString()); - }; - - // 'open' and 'error' pass through unchanged - default: - return listener; - } - } -} diff --git a/src/sdk/internal/ws-adapter.ts b/src/sdk/internal/ws-adapter.ts deleted file mode 100644 index 579d1f9..0000000 --- a/src/sdk/internal/ws-adapter.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Normalized WebSocket interface that abstracts over the `ws` package (Node.js) - * and the native WebSocket API (browser). - */ -export interface WebSocketLike { - readonly readyState: number; - - send(data: string | ArrayBufferLike | ArrayBufferView): void; - close(code?: number, reason?: string): void; - - on(event: 'open', listener: () => void): void; - on( - event: 'message', - listener: (data: string | ArrayBuffer | ArrayBufferView, isBinary: boolean) => void, - ): void; - on(event: 'close', listener: (code: number, reason: string) => void): void; - on(event: 'error', listener: (err: Error) => void): void; - on(event: string, listener: (...args: any[]) => void): void; - - off(event: string, listener: (...args: any[]) => void): void; - once(event: string, listener: (...args: any[]) => void): void; -} - -/** Standard WebSocket readyState values (RFC 6455). */ -export const ReadyState = { - CONNECTING: 0, - OPEN: 1, - CLOSING: 2, - CLOSED: 3, -} as const; diff --git a/src/sdk/internal/ws.ts b/src/sdk/internal/ws.ts deleted file mode 100644 index 7e17902..0000000 --- a/src/sdk/internal/ws.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { concatBytes, encodeUTF8 } from './utils/bytes'; - -/** Reconnection event passed to the `onReconnecting` handler and event listeners. */ -export interface ReconnectingEvent> { - /** Which retry attempt this is (1-based). */ - readonly attempt: number; - /** Total attempts that will be made. */ - readonly maxAttempts: number; - /** Delay in ms before this attempt connects. */ - readonly delay: number; - /** The WebSocket close code that triggered reconnection. */ - readonly closeCode: number; - /** The current query parameters. */ - readonly parameters: (Parameters & Record) | undefined; -} - -/** - * Optional overrides returned from the `onReconnecting` handler - * to customize the next reconnection attempt. - */ -export type ReconnectingOverrides> = - | { - /** - * If provided, assigns the query parameters for the next connection. - * Set to `undefined` to clear all query parameters. - */ - parameters?: (Parameters & Record) | undefined; - } - | { - /** - * If set, will stop attempting to reconnect. - */ - abort: true; - }; - -/** - * Raw data types that can be sent over a WebSocket without serialization. - */ -export type RawWebSocketData = string | ArrayBufferLike | ArrayBufferView | ArrayBufferView[]; - -export type UnsentMessage = { type: 'message'; message: T } | { type: 'raw'; data: RawWebSocketData }; - -type QueueEntry = - | { kind: 'json'; data: string; byteLength: number } - | { kind: 'raw'; data: RawWebSocketData; byteLength: number }; - -function toUint8Array(view: ArrayBufferView): Uint8Array { - if (view instanceof Uint8Array) return view; - return new Uint8Array(view.buffer, view.byteOffset, view.byteLength); -} - -/** - * Flatten `ArrayBufferView[]` fragments into a single `Uint8Array` so that - * `ws.send()` transmits the correct bytes. - */ -export function flattenRawData(data: RawWebSocketData): Exclude { - if (Array.isArray(data)) return concatBytes(data.map(toUint8Array)); - return data; -} - -function snapshotRawData(data: RawWebSocketData): Exclude { - if (typeof data === 'string') return data; - if (Array.isArray(data)) return concatBytes(data.map(toUint8Array)); - if (ArrayBuffer.isView(data)) { - const copy = new Uint8Array(data.byteLength); - copy.set(toUint8Array(data)); - return copy; - } - return data.slice(0); -} - -function rawByteLength(data: RawWebSocketData): number { - if (typeof data === 'string') return encodeUTF8(data).byteLength; - if (Array.isArray(data)) return data.reduce((sum, buf) => sum + buf.byteLength, 0); - if ('byteLength' in data) return data.byteLength; - return 0; -} - -/** - * A bounded queue for outgoing WebSocket messages. JSON messages are - * serialized on enqueue; raw messages are stored as-is. The queue enforces - * a configurable byte-size limit and can return the original messages via - * {@link drain} when the connection permanently closes. - */ -export class SendQueue { - private _queue: QueueEntry[] = []; - private _bytes: number = 0; - private _maxBytes: number; - - constructor(maxBytes: number = 1_048_576) { - this._maxBytes = maxBytes; - } - - /** - * Serialize and enqueue a JSON message. Returns `true` if the message was - * accepted, `false` if it would exceed the byte-size limit. - */ - enqueue(event: T): boolean { - const data = JSON.stringify(event); - const byteLength = encodeUTF8(data).byteLength; - if (this._bytes + byteLength > this._maxBytes && this._queue.length > 0) { - return false; - } - this._queue.push({ kind: 'json', data, byteLength }); - this._bytes += byteLength; - return true; - } - - /** - * Enqueue raw data without serialization. Returns `true` if the data was - * accepted, `false` if it would exceed the byte-size limit. - */ - enqueueRaw(data: RawWebSocketData): boolean { - const snapshot = snapshotRawData(data); - const byteLength = rawByteLength(snapshot); - if (this._bytes + byteLength > this._maxBytes && this._queue.length > 0) { - return false; - } - this._queue.push({ kind: 'raw', data: snapshot, byteLength }); - this._bytes += byteLength; - return true; - } - - /** - * Send every queued message via `send`. If `send` throws, the failing - * message and all subsequent messages are re-queued and the error is - * re-thrown so the caller can report it. - */ - flush(send: (data: RawWebSocketData) => void): void { - const pending = this._queue.splice(0); - this._bytes = 0; - for (let i = 0; i < pending.length; i++) { - try { - send(pending[i]!.data); - } catch (err) { - const remaining = pending.slice(i); - this._queue = remaining.concat(this._queue); - this._bytes = this._queue.reduce((sum, item) => sum + item.byteLength, 0); - throw err; - } - } - } - - /** - * Drain the queue and return the unsent messages. JSON messages are - * deserialized back to their original form. Resets byte tracking to zero. - */ - drain(): UnsentMessage[] { - const unsent = this._queue.map((entry): UnsentMessage => { - if (entry.kind === 'raw') return { type: 'raw', data: entry.data }; - return { type: 'message', message: JSON.parse(entry.data) as T }; - }); - this._queue = []; - this._bytes = 0; - return unsent; - } -} - -// RFC 6455 §7.4.1 -export function isRecoverableClose(code: number): boolean { - switch (code) { - case 1000: - return false; // Normal closure - case 1001: - return true; // Going away (server shutting down) - case 1002: - return false; // Protocol error - case 1003: - return false; // Unsupported data - case 1005: - return true; // No status code (abnormal) - case 1006: - return true; // Abnormal closure (network drop) - case 1007: - return false; // Invalid payload - case 1008: - return false; // Policy violation - case 1009: - return false; // Message too big - case 1010: - return false; // Missing extension - case 1011: - return true; // Internal server error - case 1012: - return true; // Service restart - case 1013: - return true; // Try again later - case 1015: - return true; // TLS handshake failure - default: - return false; - } -} diff --git a/src/sdk/resource.ts b/src/sdk/resource.ts index 93968f2..066a2ba 100644 --- a/src/sdk/resource.ts +++ b/src/sdk/resource.ts @@ -2,7 +2,7 @@ import type { Dedalus } from './client'; -export class APIResource { +export abstract class APIResource { protected _client: Dedalus; constructor(client: Dedalus) { diff --git a/src/sdk/resources/usage.ts b/src/sdk/resources.ts similarity index 71% rename from src/sdk/resources/usage.ts rename to src/sdk/resources.ts index 4c65efb..c029f2b 100644 --- a/src/sdk/resources/usage.ts +++ b/src/sdk/resources.ts @@ -1,3 +1,3 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -export * from "./usage/index"; +export {}; diff --git a/src/sdk/resources/index.ts b/src/sdk/resources/index.ts index bbaaf18..7029d46 100644 --- a/src/sdk/resources/index.ts +++ b/src/sdk/resources/index.ts @@ -1,8 +1,2 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -export { MachineLifecycle } from "./machine-lifecycle/machine-lifecycle"; -export type { CreateMachineRequest, UpdateMachineRequest, CreateExecutionRequest, CreatePreviewRequest, CreateSSHSessionRequest, CreateTerminalRequest, MachineLifecycleListParams, MachineLifecycleListResponse, MachineLifecycleCreateParams, MachineLifecycleCreateResponse, MachineLifecycleDeleteParams, MachineLifecycleDeleteResponse, MachineLifecycleRetrieveParams, MachineLifecycleRetrieveResponse, MachineLifecyclePatchParams, MachineLifecyclePatchResponse, MachineLifecycleListArtifactsParams, MachineLifecycleListArtifactsResponse, MachineLifecycleDeleteArtifactParams, MachineLifecycleDeleteArtifactResponse, MachineLifecycleRetrieveArtifactParams, MachineLifecycleRetrieveArtifactResponse, MachineLifecycleListExecutionsParams, MachineLifecycleListExecutionsResponse, MachineLifecycleCreateExecutionParams, MachineLifecycleCreateExecutionResponse, MachineLifecycleDeleteExecutionParams, MachineLifecycleDeleteExecutionResponse, MachineLifecycleRetrieveExecutionParams, MachineLifecycleRetrieveExecutionResponse, MachineLifecycleListExecutionEventsParams, MachineLifecycleListExecutionEventsResponse, MachineLifecycleListExecutionOutputParams, MachineLifecycleListExecutionOutputResponse, MachineLifecycleListPreviewsParams, MachineLifecycleListPreviewsResponse, MachineLifecycleCreatePreviewParams, MachineLifecycleCreatePreviewResponse, MachineLifecycleDeletePreviewParams, MachineLifecycleDeletePreviewResponse, MachineLifecycleRetrievePreviewParams, MachineLifecycleRetrievePreviewResponse, MachineLifecycleSleepParams, MachineLifecycleSleepResponse, MachineLifecycleListSSHSessionsParams, MachineLifecycleListSSHSessionsResponse, MachineLifecycleCreateSSHSessionParams, MachineLifecycleCreateSSHSessionResponse, MachineLifecycleDeleteSSHSessionParams, MachineLifecycleDeleteSSHSessionResponse, MachineLifecycleRetrieveSSHSessionParams, MachineLifecycleRetrieveSSHSessionResponse, MachineLifecycleWatchStatusParams, MachineLifecycleWatchStatusResponse, MachineLifecycleListTerminalsParams, MachineLifecycleListTerminalsResponse, MachineLifecycleCreateTerminalParams, MachineLifecycleCreateTerminalResponse, MachineLifecycleDeleteTerminalParams, MachineLifecycleDeleteTerminalResponse, MachineLifecycleRetrieveTerminalParams, MachineLifecycleRetrieveTerminalResponse, MachineLifecycleConnectTerminalParams, MachineLifecycleWakeParams, MachineLifecycleWakeResponse } from "./machine-lifecycle/machine-lifecycle"; -export { MachineLifecycle as MachineLifecycleResource } from "./machine-lifecycle/machine-lifecycle"; -export { Usage } from "./usage/usage"; -export type { UsageListParams, UsageListResponse } from "./usage/usage"; -export { Usage as UsageResource } from "./usage/usage"; diff --git a/src/sdk/resources/machine-lifecycle/index.ts b/src/sdk/resources/machine-lifecycle/index.ts deleted file mode 100644 index f441339..0000000 --- a/src/sdk/resources/machine-lifecycle/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -export { MachineLifecycle } from "./machine-lifecycle"; -export type { CreateMachineRequest, UpdateMachineRequest, CreateExecutionRequest, CreatePreviewRequest, CreateSSHSessionRequest, CreateTerminalRequest, MachineLifecycleListParams, MachineLifecycleListResponse, MachineLifecycleCreateParams, MachineLifecycleCreateResponse, MachineLifecycleDeleteParams, MachineLifecycleDeleteResponse, MachineLifecycleRetrieveParams, MachineLifecycleRetrieveResponse, MachineLifecyclePatchParams, MachineLifecyclePatchResponse, MachineLifecycleListArtifactsParams, MachineLifecycleListArtifactsResponse, MachineLifecycleDeleteArtifactParams, MachineLifecycleDeleteArtifactResponse, MachineLifecycleRetrieveArtifactParams, MachineLifecycleRetrieveArtifactResponse, MachineLifecycleListExecutionsParams, MachineLifecycleListExecutionsResponse, MachineLifecycleCreateExecutionParams, MachineLifecycleCreateExecutionResponse, MachineLifecycleDeleteExecutionParams, MachineLifecycleDeleteExecutionResponse, MachineLifecycleRetrieveExecutionParams, MachineLifecycleRetrieveExecutionResponse, MachineLifecycleListExecutionEventsParams, MachineLifecycleListExecutionEventsResponse, MachineLifecycleListExecutionOutputParams, MachineLifecycleListExecutionOutputResponse, MachineLifecycleListPreviewsParams, MachineLifecycleListPreviewsResponse, MachineLifecycleCreatePreviewParams, MachineLifecycleCreatePreviewResponse, MachineLifecycleDeletePreviewParams, MachineLifecycleDeletePreviewResponse, MachineLifecycleRetrievePreviewParams, MachineLifecycleRetrievePreviewResponse, MachineLifecycleSleepParams, MachineLifecycleSleepResponse, MachineLifecycleListSSHSessionsParams, MachineLifecycleListSSHSessionsResponse, MachineLifecycleCreateSSHSessionParams, MachineLifecycleCreateSSHSessionResponse, MachineLifecycleDeleteSSHSessionParams, MachineLifecycleDeleteSSHSessionResponse, MachineLifecycleRetrieveSSHSessionParams, MachineLifecycleRetrieveSSHSessionResponse, MachineLifecycleWatchStatusParams, MachineLifecycleWatchStatusResponse, MachineLifecycleListTerminalsParams, MachineLifecycleListTerminalsResponse, MachineLifecycleCreateTerminalParams, MachineLifecycleCreateTerminalResponse, MachineLifecycleDeleteTerminalParams, MachineLifecycleDeleteTerminalResponse, MachineLifecycleRetrieveTerminalParams, MachineLifecycleRetrieveTerminalResponse, MachineLifecycleConnectTerminalParams, MachineLifecycleWakeParams, MachineLifecycleWakeResponse } from "./machine-lifecycle"; -export { MachineLifecycleWS, type MachineLifecycleWSClientOptions } from './ws'; -export type { MachineLifecycleWSReconnectOptions, MachineLifecycleWSParameters } from './ws-base'; diff --git a/src/sdk/resources/machine-lifecycle/internal-base.ts b/src/sdk/resources/machine-lifecycle/internal-base.ts deleted file mode 100644 index 902230c..0000000 --- a/src/sdk/resources/machine-lifecycle/internal-base.ts +++ /dev/null @@ -1,105 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { path as __scalarPath } from "../../internal/utils/path"; -import * as MachineLifecycleAPI from "./machine-lifecycle"; -import { Dedalus } from "../../client"; -import { EventEmitter, type EventParameters } from "../../core/EventEmitter"; -import { DedalusError } from "../../error"; -import type { RawWebSocketData, ReconnectingEvent, UnsentMessage } from "../../internal/ws"; -import type { MachineLifecycleWSParameters } from "./ws-base"; - -type EventTypeOf = T extends { type?: infer EventType } ? EventType : never; -type MachineLifecycleWSErrorEvent = Extract; - -export type MachineLifecycleWSStreamMessage = - | { type: 'connecting' | 'open' | 'closing' } - | { type: 'close'; code: number; reason: string; unsent: UnsentMessage[] } - | { type: 'reconnecting'; reconnect: ReconnectingEvent } - | { type: 'reconnected' } - | { type: 'message'; message: unknown } - | { type: 'raw'; data: RawWebSocketData } - | { type: 'error'; error: WebSocketError }; - -export class WebSocketError extends DedalusError { - error?: MachineLifecycleWSErrorEvent | undefined; - - constructor(message: string, event: MachineLifecycleWSErrorEvent | null) { - super(message); - this.error = event ?? undefined; - } -} - -type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; - -type WebSocketEvents = Simplify< - { - event: (event: unknown) => void; - raw: (data: RawWebSocketData) => void; - error: (error: WebSocketError) => void; - close: (code: number, reason: string, unsent: UnsentMessage[]) => void; - reconnecting: (event: ReconnectingEvent) => void; - reconnected: () => void; - } & { - [EventType in Exclude>, 'error'> & string]: ( - event: Extract, - ) => unknown; - } ->; - -export abstract class MachineLifecycleWSEmitter extends EventEmitter { - /** Send an event to the API. */ - abstract send(event: unknown): void; - - /** Send raw data over the WebSocket without JSON serialization. */ - abstract sendRaw(data: RawWebSocketData): void; - - /** Close the WebSocket connection. */ - abstract close(props?: { code: number; reason: string }): void; - - protected _onError(event: null, message: string, cause: unknown): void; - protected _onError(event: MachineLifecycleWSErrorEvent, message?: string | undefined): void; - protected _onError(event: MachineLifecycleWSErrorEvent | null, message?: string | undefined, cause?: unknown): void { - message = message ?? safeJSONStringify(event) ?? 'unknown error'; - - if (!this._hasListener('error')) { - const error = new WebSocketError( - message + - "\n\nTo resolve these unhandled rejection errors you should bind an `error` callback, e.g. `ws.on('error', (error) => ...)` ", - event, - ); - (error as Error & { cause?: unknown }).cause = cause; - Promise.reject(error); - return; - } - - const error = new WebSocketError(message, event); - (error as Error & { cause?: unknown }).cause = cause; - this._emit('error', error); - } - - public _emit(event: Event, ...args: EventParameters): void { - super._emit(event, ...args); - } -} - -export function buildURL(client: Dedalus, parameters: Record): URL { - const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...query } = parameters; - const endpoint = __scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}/stream`; - const url = new URL(client.buildURL(endpoint, query, undefined)); - url.protocol = url.protocol === 'http:' || url.protocol === 'ws:' ? 'ws:' : 'wss:'; - return url; -} - -export function parameterHeaders(parameters: Record): Record { - const headers: Record = {}; - if (parameters["X-Dedalus-Org-Id"] !== undefined) headers["X-Dedalus-Org-Id"] = String(parameters["X-Dedalus-Org-Id"]); - return headers; -} - -function safeJSONStringify(value: unknown): string | null { - try { - return JSON.stringify(value); - } catch { - return null; - } -} diff --git a/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts b/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts deleted file mode 100644 index 4b26964..0000000 --- a/src/sdk/resources/machine-lifecycle/machine-lifecycle.ts +++ /dev/null @@ -1,2616 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { APIResource } from "../../resource"; -import { APIPromise } from "../../api-promise"; -import { Stream } from "../../core/streaming"; -import type { RequestOptions } from "../../internal/request-options"; -import { buildHeaders } from "../../internal/headers"; -import { path as __scalarPath } from "../../internal/utils/path"; -import { MachineLifecycleWS, type MachineLifecycleWSClientOptions } from "./ws"; - -export class MachineLifecycle extends APIResource { - /** - * List machines - * - * @param {MachineLifecycleListParams} [params] - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const list = await client.machineLifecycle.list(); - * ``` - */ - list(params: MachineLifecycleListParams | null | undefined = {}, options?: RequestOptions): APIPromise { - const { limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get("/v1/machines", { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Create machine - * - * @param {MachineLifecycleCreateParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} Create converged inline - * - * @example - * ```ts - * const create = await client.machineLifecycle.create({ - * memory_mib: 0, - * storage_gib: 0, - * vcpu: 0, - * }); - * ``` - */ - create(params: MachineLifecycleCreateParams, options?: RequestOptions): APIPromise { - const { "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.post("/v1/machines", { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Destroy machine - * - * @param {MachineLifecycleDeleteParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const delete_ = await client.machineLifecycle.delete({ - * machine_id: "machineID", - * }); - * ``` - */ - delete(params: MachineLifecycleDeleteParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get machine - * - * @param {MachineLifecycleRetrieveParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrieve = await client.machineLifecycle.retrieve({ - * machine_id: "machineID", - * }); - * ``` - */ - retrieve(params: MachineLifecycleRetrieveParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Update machine - * - * @param {MachineLifecyclePatchParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const patch = await client.machineLifecycle.patch({ - * machine_id: "machineID", - * }); - * ``` - */ - patch(params: MachineLifecyclePatchParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.patch(__scalarPath`/v1/machines/${machine_id}`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * List artifacts - * - * @param {MachineLifecycleListArtifactsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listArtifacts = await client.machineLifecycle.listArtifacts({ - * machine_id: "machineID", - * }); - * ``` - */ - listArtifacts(params: MachineLifecycleListArtifactsParams, options?: RequestOptions): APIPromise { - const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/artifacts`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Delete artifact - * - * @param {MachineLifecycleDeleteArtifactParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const deleteArtifact = await client.machineLifecycle.deleteArtifact({ - * machine_id: "machineID", - * artifact_id: "artifactID", - * }); - * ``` - */ - deleteArtifact(params: MachineLifecycleDeleteArtifactParams, options?: RequestOptions): APIPromise { - const { machine_id, artifact_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}/artifacts/${artifact_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get artifact - * - * @param {MachineLifecycleRetrieveArtifactParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrieveArtifact = await client.machineLifecycle.retrieveArtifact({ - * machine_id: "machineID", - * artifact_id: "artifactID", - * }); - * ``` - */ - retrieveArtifact(params: MachineLifecycleRetrieveArtifactParams, options?: RequestOptions): APIPromise { - const { machine_id, artifact_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/artifacts/${artifact_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * List executions - * - * @param {MachineLifecycleListExecutionsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listExecutions = await client.machineLifecycle.listExecutions({ - * machine_id: "machineID", - * }); - * ``` - */ - listExecutions(params: MachineLifecycleListExecutionsParams, options?: RequestOptions): APIPromise { - const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Create execution - * - * @param {MachineLifecycleCreateExecutionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const createExecution = await client.machineLifecycle.createExecution({ - * machine_id: "machineID", - * command: [], - * }); - * ``` - */ - createExecution(params: MachineLifecycleCreateExecutionParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/executions`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Delete execution - * - * @param {MachineLifecycleDeleteExecutionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const deleteExecution = await client.machineLifecycle.deleteExecution({ - * machine_id: "machineID", - * execution_id: "executionID", - * }); - * ``` - */ - deleteExecution(params: MachineLifecycleDeleteExecutionParams, options?: RequestOptions): APIPromise { - const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get execution - * - * @param {MachineLifecycleRetrieveExecutionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrieveExecution = await client.machineLifecycle.retrieveExecution({ - * machine_id: "machineID", - * execution_id: "executionID", - * }); - * ``` - */ - retrieveExecution(params: MachineLifecycleRetrieveExecutionParams, options?: RequestOptions): APIPromise { - const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * List execution events - * - * @param {MachineLifecycleListExecutionEventsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listExecutionEvents = await client.machineLifecycle.listExecutionEvents({ - * machine_id: "machineID", - * execution_id: "executionID", - * }); - * ``` - */ - listExecutionEvents(params: MachineLifecycleListExecutionEventsParams, options?: RequestOptions): APIPromise { - const { machine_id, execution_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}/events`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get execution output - * - * @param {MachineLifecycleListExecutionOutputParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listExecutionOutput = await client.machineLifecycle.listExecutionOutput({ - * machine_id: "machineID", - * execution_id: "executionID", - * }); - * ``` - */ - listExecutionOutput(params: MachineLifecycleListExecutionOutputParams, options?: RequestOptions): APIPromise { - const { machine_id, execution_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/executions/${execution_id}/output`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * List previews - * - * @param {MachineLifecycleListPreviewsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listPreviews = await client.machineLifecycle.listPreviews({ - * machine_id: "machineID", - * }); - * ``` - */ - listPreviews(params: MachineLifecycleListPreviewsParams, options?: RequestOptions): APIPromise { - const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/previews`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Create preview - * - * @param {MachineLifecycleCreatePreviewParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const createPreview = await client.machineLifecycle.createPreview({ - * machine_id: "machineID", - * port: 0, - * }); - * ``` - */ - createPreview(params: MachineLifecycleCreatePreviewParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/previews`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Delete preview - * - * @param {MachineLifecycleDeletePreviewParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const deletePreview = await client.machineLifecycle.deletePreview({ - * machine_id: "machineID", - * preview_id: "previewID", - * }); - * ``` - */ - deletePreview(params: MachineLifecycleDeletePreviewParams, options?: RequestOptions): APIPromise { - const { machine_id, preview_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}/previews/${preview_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get preview - * - * @param {MachineLifecycleRetrievePreviewParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrievePreview = await client.machineLifecycle.retrievePreview({ - * machine_id: "machineID", - * preview_id: "previewID", - * }); - * ``` - */ - retrievePreview(params: MachineLifecycleRetrievePreviewParams, options?: RequestOptions): APIPromise { - const { machine_id, preview_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/previews/${preview_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Sleep a running machine - * - * @param {MachineLifecycleSleepParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const sleep = await client.machineLifecycle.sleep({ - * machine_id: "machineID", - * }); - * ``` - */ - sleep(params: MachineLifecycleSleepParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/sleep`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * List SSH sessions - * - * @param {MachineLifecycleListSSHSessionsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listSSHSessions = await client.machineLifecycle.listSSHSessions({ - * machine_id: "machineID", - * }); - * ``` - */ - listSSHSessions(params: MachineLifecycleListSSHSessionsParams, options?: RequestOptions): APIPromise { - const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/ssh`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Create SSH session - * - * @param {MachineLifecycleCreateSSHSessionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const createSSHSession = await client.machineLifecycle.createSSHSession({ - * machine_id: "machineID", - * public_key: "", - * }); - * ``` - */ - createSSHSession(params: MachineLifecycleCreateSSHSessionParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/ssh`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Delete SSH session - * - * @param {MachineLifecycleDeleteSSHSessionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const deleteSSHSession = await client.machineLifecycle.deleteSSHSession({ - * machine_id: "machineID", - * session_id: "sessionID", - * }); - * ``` - */ - deleteSSHSession(params: MachineLifecycleDeleteSSHSessionParams, options?: RequestOptions): APIPromise { - const { machine_id, session_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}/ssh/${session_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get SSH session - * - * @param {MachineLifecycleRetrieveSSHSessionParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrieveSSHSession = await client.machineLifecycle.retrieveSSHSession({ - * machine_id: "machineID", - * session_id: "sessionID", - * }); - * ``` - */ - retrieveSSHSession(params: MachineLifecycleRetrieveSSHSessionParams, options?: RequestOptions): APIPromise { - const { machine_id, session_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/ssh/${session_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Streams machine lifecycle updates over Server-Sent Events. Each `status` event contains a full `LifecycleResponse` payload. The stream closes after the machine reaches its current desired state. - * - * @param {MachineLifecycleWatchStatusParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise>} Server-Sent Event stream (`text/event-stream`) of machine lifecycle updates. - * - * @example - * ```ts - * const stream = await client.machineLifecycle.watchStatus({ - * machine_id: "machineID", - * }); - * for await (const event of stream) { - * console.log(event); - * } - * ``` - */ - watchStatus(params: MachineLifecycleWatchStatusParams, options?: RequestOptions): APIPromise> { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, "Last-Event-ID": lastEventID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/status/stream`, { ...options, headers: buildHeaders([{ Accept: "text/event-stream", ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}), ...(lastEventID !== undefined ? { "Last-Event-ID": lastEventID } : {}) }, options?.headers]), stream: true }); - } - - /** - * List terminals - * - * @param {MachineLifecycleListTerminalsParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listTerminals = await client.machineLifecycle.listTerminals({ - * machine_id: "machineID", - * }); - * ``` - */ - listTerminals(params: MachineLifecycleListTerminalsParams, options?: RequestOptions): APIPromise { - const { machine_id, limit, cursor, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/terminals`, { query: { limit: limit, cursor: cursor }, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Create terminal - * - * @param {MachineLifecycleCreateTerminalParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const createTerminal = await client.machineLifecycle.createTerminal({ - * machine_id: "machineID", - * height: 0, - * width: 0, - * }); - * ``` - */ - createTerminal(params: MachineLifecycleCreateTerminalParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID, ...body } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/terminals`, { body: body, ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Delete terminal - * - * @param {MachineLifecycleDeleteTerminalParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const deleteTerminal = await client.machineLifecycle.deleteTerminal({ - * machine_id: "machineID", - * terminal_id: "terminalID", - * }); - * ``` - */ - deleteTerminal(params: MachineLifecycleDeleteTerminalParams, options?: RequestOptions): APIPromise { - const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.delete(__scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Get terminal - * - * @param {MachineLifecycleRetrieveTerminalParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const retrieveTerminal = await client.machineLifecycle.retrieveTerminal({ - * machine_id: "machineID", - * terminal_id: "terminalID", - * }); - * ``` - */ - retrieveTerminal(params: MachineLifecycleRetrieveTerminalParams, options?: RequestOptions): APIPromise { - const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.get(__scalarPath`/v1/machines/${machine_id}/terminals/${terminal_id}`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } - - /** - * Upgrades to a WebSocket connection for interactive terminal I/O. Clients send JSON `TerminalClientEvent` messages and receive JSON `TerminalServerEvent` messages. Terminal byte streams are base64-encoded inside `input` and `output` events; `resize` events use integer `width` and `height` fields. - * - * @param {MachineLifecycleConnectTerminalParams} params - The parameters to send with the request. - * @param {MachineLifecycleWSClientOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {MachineLifecycleWS} Switching Protocols to WebSocket - * - * @example - * ```ts - * const connection = client.machineLifecycle.connectTerminal({ - * machine_id: "machineID", - * terminal_id: "terminalID", - * }); - * try { - * for await (const message of connection) { - * console.log(message); - * } - * } finally { - * connection.close(); - * } - * ``` - */ - connectTerminal(params: MachineLifecycleConnectTerminalParams, options?: MachineLifecycleWSClientOptions): MachineLifecycleWS { - const { machine_id, terminal_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return new MachineLifecycleWS(this._client, { machine_id: machine_id, terminal_id: terminal_id, ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options); - } - - /** - * Wake a sleeping machine - * - * @param {MachineLifecycleWakeParams} params - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const wake = await client.machineLifecycle.wake({ - * machine_id: "machineID", - * }); - * ``` - */ - wake(params: MachineLifecycleWakeParams, options?: RequestOptions): APIPromise { - const { machine_id, "X-Dedalus-Org-Id": xDedalusOrgID } = params ?? {}; - return this._client.post(__scalarPath`/v1/machines/${machine_id}/wake`, { ...options, headers: buildHeaders([{ ...(xDedalusOrgID !== undefined ? { "X-Dedalus-Org-Id": xDedalusOrgID } : {}) }, options?.headers]) }); - } -} - -export interface CreateMachineRequest { - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - /** - * Storage in GiB. - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; - /** - * Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. - */ - autosleep?: string; -} - -export interface UpdateMachineRequest { - /** - * Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. - */ - autosleep?: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib?: number; - /** - * Storage in GiB. - * @format int64 - */ - storage_gib?: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu?: number; -} - -export interface CreateExecutionRequest { - command: Array | null; - cwd?: string; - env?: Record; - stdin?: string; - /** - * @format int64 - */ - timeout_ms?: number; -} - -export interface CreatePreviewRequest { - /** - * @format int64 - */ - port: number; - protocol?: "http" | "https"; - visibility?: "public" | "private" | "org"; -} - -export interface CreateSSHSessionRequest { - public_key: string; -} - -export interface CreateTerminalRequest { - /** - * @format int64 - */ - height: number; - /** - * @format int64 - */ - width: number; - cwd?: string; - env?: Record; - shell?: string; -} - -export interface MachineLifecycleListParams { - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListResponse { - export interface Item { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - /** - * @format date-time - */ - created_at: string; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: Item.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; - } - - export namespace Item { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } - } -} - -export interface MachineLifecycleCreateParams { - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param: Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. - */ - autosleep?: string; - /** - * Body param: Memory in MiB. - * @format int64 - */ - memory_mib: number; - /** - * Body param: Storage in GiB. - * @format int64 - */ - storage_gib: number; - /** - * Body param: CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export interface MachineLifecycleCreateResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleCreateResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleCreateResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecycleDeleteParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeleteResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleDeleteResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleDeleteResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecycleRetrieveParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrieveResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleRetrieveResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleRetrieveResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecyclePatchParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param: Idle window before autosleep. Accepts fixed duration units like 30s, 30m, 2h, 7d3h4s, or 1w3d, raw seconds ("1800"), or never to disable. - */ - autosleep?: string; - /** - * Body param: Memory in MiB. - * @format int64 - */ - memory_mib?: number; - /** - * Body param: Storage in GiB. - * @format int64 - */ - storage_gib?: number; - /** - * Body param: CPU in vCPUs. - * @format double - */ - vcpu?: number; -} - -export interface MachineLifecyclePatchResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecyclePatchResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecyclePatchResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecycleListArtifactsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListArtifactsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListArtifactsResponse { - export interface Item { - artifact_id: string; - /** - * @format date-time - */ - created_at: string; - machine_id: string; - name: string; - /** - * @format int64 - */ - size_bytes: number; - download_url?: string; - execution_id?: string; - /** - * @format date-time - */ - expires_at?: string; - mime_type?: string; - sha256?: string; - } -} - -export interface MachineLifecycleDeleteArtifactParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - artifact_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeleteArtifactResponse { - artifact_id: string; - /** - * @format date-time - */ - created_at: string; - machine_id: string; - name: string; - /** - * @format int64 - */ - size_bytes: number; - download_url?: string; - execution_id?: string; - /** - * @format date-time - */ - expires_at?: string; - mime_type?: string; - sha256?: string; -} - -export interface MachineLifecycleRetrieveArtifactParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - artifact_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrieveArtifactResponse { - artifact_id: string; - /** - * @format date-time - */ - created_at: string; - machine_id: string; - name: string; - /** - * @format int64 - */ - size_bytes: number; - download_url?: string; - execution_id?: string; - /** - * @format date-time - */ - expires_at?: string; - mime_type?: string; - sha256?: string; -} - -export interface MachineLifecycleListExecutionsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListExecutionsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListExecutionsResponse { - export interface Item { - command: Array | null; - /** - * @format date-time - */ - created_at: string; - execution_id: string; - machine_id: string; - status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; - artifacts?: Array | null; - /** - * @format date-time - */ - completed_at?: string; - cwd?: string; - env_keys?: Array | null; - error_code?: string; - error_message?: string; - /** - * @format int64 - */ - exit_code?: number; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - /** - * @format int64 - */ - signal?: number; - /** - * @format date-time - */ - started_at?: string; - /** - * @format int64 - */ - stderr_bytes?: number; - stderr_truncated?: boolean; - /** - * @format int64 - */ - stdout_bytes?: number; - stdout_truncated?: boolean; - } - - export namespace Item { - export interface Artifact { - artifact_id: string; - name: string; - } - } -} - -export interface MachineLifecycleCreateExecutionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param - */ - command: Array | null; - /** - * Body param - */ - cwd?: string; - /** - * Body param - */ - env?: Record; - /** - * Body param - */ - stdin?: string; - /** - * Body param - * @format int64 - */ - timeout_ms?: number; -} - -export interface MachineLifecycleCreateExecutionResponse { - command: Array | null; - /** - * @format date-time - */ - created_at: string; - execution_id: string; - machine_id: string; - status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; - artifacts?: Array | null; - /** - * @format date-time - */ - completed_at?: string; - cwd?: string; - env_keys?: Array | null; - error_code?: string; - error_message?: string; - /** - * @format int64 - */ - exit_code?: number; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - /** - * @format int64 - */ - signal?: number; - /** - * @format date-time - */ - started_at?: string; - /** - * @format int64 - */ - stderr_bytes?: number; - stderr_truncated?: boolean; - /** - * @format int64 - */ - stdout_bytes?: number; - stdout_truncated?: boolean; -} - -export namespace MachineLifecycleCreateExecutionResponse { - export interface Artifact { - artifact_id: string; - name: string; - } -} - -export interface MachineLifecycleDeleteExecutionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - execution_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeleteExecutionResponse { - command: Array | null; - /** - * @format date-time - */ - created_at: string; - execution_id: string; - machine_id: string; - status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; - artifacts?: Array | null; - /** - * @format date-time - */ - completed_at?: string; - cwd?: string; - env_keys?: Array | null; - error_code?: string; - error_message?: string; - /** - * @format int64 - */ - exit_code?: number; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - /** - * @format int64 - */ - signal?: number; - /** - * @format date-time - */ - started_at?: string; - /** - * @format int64 - */ - stderr_bytes?: number; - stderr_truncated?: boolean; - /** - * @format int64 - */ - stdout_bytes?: number; - stdout_truncated?: boolean; -} - -export namespace MachineLifecycleDeleteExecutionResponse { - export interface Artifact { - artifact_id: string; - name: string; - } -} - -export interface MachineLifecycleRetrieveExecutionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - execution_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrieveExecutionResponse { - command: Array | null; - /** - * @format date-time - */ - created_at: string; - execution_id: string; - machine_id: string; - status: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; - artifacts?: Array | null; - /** - * @format date-time - */ - completed_at?: string; - cwd?: string; - env_keys?: Array | null; - error_code?: string; - error_message?: string; - /** - * @format int64 - */ - exit_code?: number; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - /** - * @format int64 - */ - signal?: number; - /** - * @format date-time - */ - started_at?: string; - /** - * @format int64 - */ - stderr_bytes?: number; - stderr_truncated?: boolean; - /** - * @format int64 - */ - stdout_bytes?: number; - stdout_truncated?: boolean; -} - -export namespace MachineLifecycleRetrieveExecutionResponse { - export interface Artifact { - artifact_id: string; - name: string; - } -} - -export interface MachineLifecycleListExecutionEventsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - execution_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListExecutionEventsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListExecutionEventsResponse { - export interface Item { - /** - * @format date-time - */ - at: string; - /** - * @format int64 - */ - sequence: number; - type: "lifecycle" | "stdout" | "stderr"; - chunk?: string; - error_code?: string; - error_message?: string; - /** - * @format int64 - */ - exit_code?: number; - /** - * @format int64 - */ - signal?: number; - status?: "wake_in_progress" | "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired"; - } -} - -export interface MachineLifecycleListExecutionOutputParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - execution_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListExecutionOutputResponse { - execution_id: string; - stderr?: string; - /** - * @format int64 - */ - stderr_bytes?: number; - stderr_truncated?: boolean; - stdout?: string; - /** - * @format int64 - */ - stdout_bytes?: number; - stdout_truncated?: boolean; -} - -export interface MachineLifecycleListPreviewsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListPreviewsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListPreviewsResponse { - export interface Item { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - /** - * @format int64 - */ - port: number; - preview_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - visibility: "public" | "private" | "org"; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "http" | "https"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - url?: string; - } -} - -export interface MachineLifecycleCreatePreviewParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param - * @format int64 - */ - port: number; - /** - * Body param - */ - protocol?: "http" | "https"; - /** - * Body param - */ - visibility?: "public" | "private" | "org"; -} - -export interface MachineLifecycleCreatePreviewResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - /** - * @format int64 - */ - port: number; - preview_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - visibility: "public" | "private" | "org"; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "http" | "https"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - url?: string; -} - -export interface MachineLifecycleDeletePreviewParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - preview_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeletePreviewResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - /** - * @format int64 - */ - port: number; - preview_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - visibility: "public" | "private" | "org"; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "http" | "https"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - url?: string; -} - -export interface MachineLifecycleRetrievePreviewParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - preview_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrievePreviewResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - /** - * @format int64 - */ - port: number; - preview_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - visibility: "public" | "private" | "org"; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "http" | "https"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - url?: string; -} - -export interface MachineLifecycleSleepParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleSleepResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleSleepResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleSleepResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecycleListSSHSessionsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListSSHSessionsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListSSHSessionsResponse { - export interface Item { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - session_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - connection?: Item.Connection; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - } - - export namespace Item { - export interface Connection { - endpoint: string; - /** - * @format int64 - */ - port: number; - ssh_username: string; - host_trust?: Connection.HostTrust; - user_certificate?: string; - } - - export namespace Connection { - export interface HostTrust { - host_pattern: string; - kind: "cert_authority"; - public_key: string; - } - } - } -} - -export interface MachineLifecycleCreateSSHSessionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param - */ - public_key: string; -} - -export interface MachineLifecycleCreateSSHSessionResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - session_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - connection?: MachineLifecycleCreateSSHSessionResponse.Connection; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; -} - -export namespace MachineLifecycleCreateSSHSessionResponse { - export interface Connection { - endpoint: string; - /** - * @format int64 - */ - port: number; - ssh_username: string; - host_trust?: Connection.HostTrust; - user_certificate?: string; - } - - export namespace Connection { - export interface HostTrust { - host_pattern: string; - kind: "cert_authority"; - public_key: string; - } - } -} - -export interface MachineLifecycleDeleteSSHSessionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - session_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeleteSSHSessionResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - session_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - connection?: MachineLifecycleDeleteSSHSessionResponse.Connection; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; -} - -export namespace MachineLifecycleDeleteSSHSessionResponse { - export interface Connection { - endpoint: string; - /** - * @format int64 - */ - port: number; - ssh_username: string; - host_trust?: Connection.HostTrust; - user_certificate?: string; - } - - export namespace Connection { - export interface HostTrust { - host_pattern: string; - kind: "cert_authority"; - public_key: string; - } - } -} - -export interface MachineLifecycleRetrieveSSHSessionParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - session_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrieveSSHSessionResponse { - /** - * @format date-time - */ - created_at: string; - machine_id: string; - session_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - connection?: MachineLifecycleRetrieveSSHSessionResponse.Connection; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; -} - -export namespace MachineLifecycleRetrieveSSHSessionResponse { - export interface Connection { - endpoint: string; - /** - * @format int64 - */ - port: number; - ssh_username: string; - host_trust?: Connection.HostTrust; - user_certificate?: string; - } - - export namespace Connection { - export interface HostTrust { - host_pattern: string; - kind: "cert_authority"; - public_key: string; - } - } -} - -export interface MachineLifecycleWatchStatusParams { - /** - * Path param: Machine identifier. - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param: Organization ID header applied to all DCS requests. - * @format uuid - */ - "X-Dedalus-Org-Id"?: string; - /** - * Header param: Optional resourceVersion bookmark used to resume a previous stream. - */ - "Last-Event-ID"?: string; -} - -export interface MachineLifecycleWatchStatusResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleWatchStatusResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleWatchStatusResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} - -export interface MachineLifecycleListTerminalsParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Query param - * @format int64 - */ - limit?: number; - /** - * Query param - */ - cursor?: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleListTerminalsResponse { - items: Array | null; - next_cursor?: string; -} - -export namespace MachineLifecycleListTerminalsResponse { - export interface Item { - /** - * @format date-time - */ - created_at: string; - /** - * @format int64 - */ - height: number; - machine_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - terminal_id: string; - /** - * @format int64 - */ - width: number; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "websocket"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - stream_url?: string; - } -} - -export interface MachineLifecycleCreateTerminalParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; - /** - * Body param - */ - cwd?: string; - /** - * Body param - */ - env?: Record; - /** - * Body param - * @format int64 - */ - height: number; - /** - * Body param - */ - shell?: string; - /** - * Body param - * @format int64 - */ - width: number; -} - -export interface MachineLifecycleCreateTerminalResponse { - /** - * @format date-time - */ - created_at: string; - /** - * @format int64 - */ - height: number; - machine_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - terminal_id: string; - /** - * @format int64 - */ - width: number; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "websocket"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - stream_url?: string; -} - -export interface MachineLifecycleDeleteTerminalParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - terminal_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleDeleteTerminalResponse { - /** - * @format date-time - */ - created_at: string; - /** - * @format int64 - */ - height: number; - machine_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - terminal_id: string; - /** - * @format int64 - */ - width: number; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "websocket"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - stream_url?: string; -} - -export interface MachineLifecycleRetrieveTerminalParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - terminal_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleRetrieveTerminalResponse { - /** - * @format date-time - */ - created_at: string; - /** - * @format int64 - */ - height: number; - machine_id: string; - status: "wake_in_progress" | "ready" | "closed" | "expired" | "failed"; - terminal_id: string; - /** - * @format int64 - */ - width: number; - error_code?: string; - error_message?: string; - /** - * @format date-time - */ - expires_at?: string; - protocol?: "websocket"; - /** - * @format date-time - */ - ready_at?: string; - /** - * @format int64 - */ - retry_after_ms?: number; - stream_url?: string; -} - -export interface MachineLifecycleConnectTerminalParams { - /** - * Path param: Machine identifier. - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Path param: Terminal identifier. - * @minLength 1 - * @maxLength 253 - * @pattern ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ - */ - terminal_id: string; - /** - * Header param: Organization ID header applied to all DCS requests. - * @format uuid - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleWakeParams { - /** - * Path param - * @minLength 4 - * @maxLength 253 - * @pattern ^dm-[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - */ - machine_id: string; - /** - * Header param - */ - "X-Dedalus-Org-Id"?: string; -} - -export interface MachineLifecycleWakeResponse { - /** - * Seconds of inactivity before autosleep. 0 disables autosleep. - * @format int64 - * @minimum 0 - * @maximum 9223372036 - */ - autosleep_seconds: number; - desired_state: "running" | "sleeping" | "destroyed"; - machine_id: string; - /** - * Memory in MiB. - * @format int64 - */ - memory_mib: number; - status: MachineLifecycleWakeResponse.Status; - /** - * @format int64 - */ - storage_gib: number; - /** - * CPU in vCPUs. - * @format double - */ - vcpu: number; -} - -export namespace MachineLifecycleWakeResponse { - export interface Status { - /** - * @format date-time - */ - last_progress_at: string; - /** - * @format date-time - */ - last_transition_at: string; - phase: "accepted" | "placement_pending" | "starting" | "running" | "stopping" | "sleeping" | "destroying" | "destroyed" | "failed"; - reason: string; - retryable: boolean; - revision: string; - last_error?: string; - } -} -export declare namespace MachineLifecycle { - export { - type CreateMachineRequest as CreateMachineRequest, - type UpdateMachineRequest as UpdateMachineRequest, - type CreateExecutionRequest as CreateExecutionRequest, - type CreatePreviewRequest as CreatePreviewRequest, - type CreateSSHSessionRequest as CreateSSHSessionRequest, - type CreateTerminalRequest as CreateTerminalRequest, - type MachineLifecycleListResponse as MachineLifecycleListResponse, - type MachineLifecycleCreateResponse as MachineLifecycleCreateResponse, - type MachineLifecycleDeleteResponse as MachineLifecycleDeleteResponse, - type MachineLifecycleRetrieveResponse as MachineLifecycleRetrieveResponse, - type MachineLifecyclePatchResponse as MachineLifecyclePatchResponse, - type MachineLifecycleListArtifactsResponse as MachineLifecycleListArtifactsResponse, - type MachineLifecycleDeleteArtifactResponse as MachineLifecycleDeleteArtifactResponse, - type MachineLifecycleRetrieveArtifactResponse as MachineLifecycleRetrieveArtifactResponse, - type MachineLifecycleListExecutionsResponse as MachineLifecycleListExecutionsResponse, - type MachineLifecycleCreateExecutionResponse as MachineLifecycleCreateExecutionResponse, - type MachineLifecycleDeleteExecutionResponse as MachineLifecycleDeleteExecutionResponse, - type MachineLifecycleRetrieveExecutionResponse as MachineLifecycleRetrieveExecutionResponse, - type MachineLifecycleListExecutionEventsResponse as MachineLifecycleListExecutionEventsResponse, - type MachineLifecycleListExecutionOutputResponse as MachineLifecycleListExecutionOutputResponse, - type MachineLifecycleListPreviewsResponse as MachineLifecycleListPreviewsResponse, - type MachineLifecycleCreatePreviewResponse as MachineLifecycleCreatePreviewResponse, - type MachineLifecycleDeletePreviewResponse as MachineLifecycleDeletePreviewResponse, - type MachineLifecycleRetrievePreviewResponse as MachineLifecycleRetrievePreviewResponse, - type MachineLifecycleSleepResponse as MachineLifecycleSleepResponse, - type MachineLifecycleListSSHSessionsResponse as MachineLifecycleListSSHSessionsResponse, - type MachineLifecycleCreateSSHSessionResponse as MachineLifecycleCreateSSHSessionResponse, - type MachineLifecycleDeleteSSHSessionResponse as MachineLifecycleDeleteSSHSessionResponse, - type MachineLifecycleRetrieveSSHSessionResponse as MachineLifecycleRetrieveSSHSessionResponse, - type MachineLifecycleWatchStatusResponse as MachineLifecycleWatchStatusResponse, - type MachineLifecycleListTerminalsResponse as MachineLifecycleListTerminalsResponse, - type MachineLifecycleCreateTerminalResponse as MachineLifecycleCreateTerminalResponse, - type MachineLifecycleDeleteTerminalResponse as MachineLifecycleDeleteTerminalResponse, - type MachineLifecycleRetrieveTerminalResponse as MachineLifecycleRetrieveTerminalResponse, - type MachineLifecycleWakeResponse as MachineLifecycleWakeResponse, - type MachineLifecycleListParams as MachineLifecycleListParams, - type MachineLifecycleCreateParams as MachineLifecycleCreateParams, - type MachineLifecycleDeleteParams as MachineLifecycleDeleteParams, - type MachineLifecycleRetrieveParams as MachineLifecycleRetrieveParams, - type MachineLifecyclePatchParams as MachineLifecyclePatchParams, - type MachineLifecycleListArtifactsParams as MachineLifecycleListArtifactsParams, - type MachineLifecycleDeleteArtifactParams as MachineLifecycleDeleteArtifactParams, - type MachineLifecycleRetrieveArtifactParams as MachineLifecycleRetrieveArtifactParams, - type MachineLifecycleListExecutionsParams as MachineLifecycleListExecutionsParams, - type MachineLifecycleCreateExecutionParams as MachineLifecycleCreateExecutionParams, - type MachineLifecycleDeleteExecutionParams as MachineLifecycleDeleteExecutionParams, - type MachineLifecycleRetrieveExecutionParams as MachineLifecycleRetrieveExecutionParams, - type MachineLifecycleListExecutionEventsParams as MachineLifecycleListExecutionEventsParams, - type MachineLifecycleListExecutionOutputParams as MachineLifecycleListExecutionOutputParams, - type MachineLifecycleListPreviewsParams as MachineLifecycleListPreviewsParams, - type MachineLifecycleCreatePreviewParams as MachineLifecycleCreatePreviewParams, - type MachineLifecycleDeletePreviewParams as MachineLifecycleDeletePreviewParams, - type MachineLifecycleRetrievePreviewParams as MachineLifecycleRetrievePreviewParams, - type MachineLifecycleSleepParams as MachineLifecycleSleepParams, - type MachineLifecycleListSSHSessionsParams as MachineLifecycleListSSHSessionsParams, - type MachineLifecycleCreateSSHSessionParams as MachineLifecycleCreateSSHSessionParams, - type MachineLifecycleDeleteSSHSessionParams as MachineLifecycleDeleteSSHSessionParams, - type MachineLifecycleRetrieveSSHSessionParams as MachineLifecycleRetrieveSSHSessionParams, - type MachineLifecycleWatchStatusParams as MachineLifecycleWatchStatusParams, - type MachineLifecycleListTerminalsParams as MachineLifecycleListTerminalsParams, - type MachineLifecycleCreateTerminalParams as MachineLifecycleCreateTerminalParams, - type MachineLifecycleDeleteTerminalParams as MachineLifecycleDeleteTerminalParams, - type MachineLifecycleRetrieveTerminalParams as MachineLifecycleRetrieveTerminalParams, - type MachineLifecycleConnectTerminalParams as MachineLifecycleConnectTerminalParams, - type MachineLifecycleWakeParams as MachineLifecycleWakeParams, - }; -} -export { MachineLifecycle as MachineLifecycleResource }; diff --git a/src/sdk/resources/machine-lifecycle/ws-base.ts b/src/sdk/resources/machine-lifecycle/ws-base.ts deleted file mode 100644 index 794898d..0000000 --- a/src/sdk/resources/machine-lifecycle/ws-base.ts +++ /dev/null @@ -1,264 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { MachineLifecycleWSEmitter, MachineLifecycleWSStreamMessage, WebSocketError, buildURL, parameterHeaders } from "./internal-base"; -import { InternalEventEmitter } from "../../core/EventEmitter"; -import { sleep } from "../../internal/utils/sleep"; -import { type WebSocketLike, ReadyState } from "../../internal/ws-adapter"; -import { SendQueue, flattenRawData, isRecoverableClose, type RawWebSocketData, type ReconnectingEvent, type ReconnectingOverrides, type UnsentMessage } from "../../internal/ws"; -import * as MachineLifecycleAPI from "./machine-lifecycle"; -import { Dedalus } from "../../client"; -import { DedalusError } from "../../error"; - -export interface MachineLifecycleWSParameters extends Record { - machine_id: string; - - terminal_id: string; - - "X-Dedalus-Org-Id"?: string; - -} - -export interface MachineLifecycleWSReconnectOptions { - /** Called before each reconnect attempt. */ - onReconnecting(event: ReconnectingEvent): ReconnectingOverrides | void; - /** Maximum number of reconnection attempts. Default: 5. Set to 0 to disable reconnection. */ - maxRetries?: number; - /** Initial backoff delay in milliseconds. Default: 500. */ - initialDelay?: number; - /** Maximum backoff delay in milliseconds. Default: 8000. */ - maxDelay?: number; -} - -export interface MachineLifecycleWSBaseOptions { - /** Options for automatic reconnection on recoverable close codes. */ - reconnect?: MachineLifecycleWSReconnectOptions | null | undefined; - /** Maximum size of the outgoing message queue in bytes. Default: 1 MB. */ - maxQueueSize?: number | undefined; -} - -export abstract class MachineLifecycleWSBase extends MachineLifecycleWSEmitter { - url!: URL; - socket!: TSocket; - - protected _client: Dedalus; - protected _parameters: MachineLifecycleWSParameters | null | undefined; - private _reconnectOptions: MachineLifecycleWSReconnectOptions | null; - private _sendQueue: SendQueue; - private _isReconnecting = false; - private _intentionallyClosed = false; - private _closeCode = 1000; - private _closeReason = 'OK'; - private _lastCloseCode = 1006; - private _lastCloseReason = ''; - private _internalEvents = new InternalEventEmitter<{ socketSwap: (oldSocket: TSocket, newSocket: TSocket) => void; reconnecting: (event: ReconnectingEvent) => void; reconnected: () => void; close: (code: number, reason: string, unsent: UnsentMessage[]) => void; }>(); - - constructor(client: Dedalus, parameters: MachineLifecycleWSParameters, options?: MachineLifecycleWSBaseOptions | undefined) { - super(); - this._client = client; - this._parameters = parameters ?? undefined; - this._reconnectOptions = options?.reconnect ?? null; - this._sendQueue = new SendQueue(options?.maxQueueSize); - } - - protected _connectInitial(): void { - this.url = buildURL(this._client, this._parameters ?? {}); - this.socket = this._connect(); - } - - protected abstract _createSocket(url: URL, authHeaders: Record): TSocket; - - send(event: unknown): void { - if (this._isReconnecting || this.socket.readyState === ReadyState.CONNECTING) { - if (!this._sendQueue.enqueue(event)) this._onError(null, "send queue is full, message discarded", undefined); - return; - } - if (this.socket.readyState !== ReadyState.OPEN) { - this._onError(null, "cannot send on a closed WebSocket", undefined); - return; - } - try { - this.socket.send(JSON.stringify(event)); - } catch (err) { - this._onError(null, "could not send data", err); - } - } - - sendRaw(data: RawWebSocketData): void { - if (this._isReconnecting || this.socket.readyState === ReadyState.CONNECTING) { - if (!this._sendQueue.enqueueRaw(data)) this._onError(null, "send queue is full, message discarded", undefined); - return; - } - if (this.socket.readyState !== ReadyState.OPEN) { - this._onError(null, "cannot send on a closed WebSocket", undefined); - return; - } - try { - this.socket.send(flattenRawData(data)); - } catch (err) { - this._onError(null, "could not send data", err); - } - } - - close(props?: { code: number; reason: string }): void { - this._intentionallyClosed = true; - this._closeCode = props?.code ?? 1000; - this._closeReason = props?.reason ?? 'OK'; - try { this.socket.close(this._closeCode, this._closeReason); } catch (err) { this._onError(null, "could not close the connection", err); } - } - - stream(): AsyncIterableIterator { - return this[Symbol.asyncIterator](); - } - - [Symbol.asyncIterator](): AsyncIterableIterator { - const queue: MachineLifecycleWSStreamMessage[] = []; - const resolvers: (() => void)[] = []; - let done = false; - let currentSocket = this.socket; - const push = (msg: MachineLifecycleWSStreamMessage) => { queue.push(msg); resolvers.shift()?.(); }; - const flushResolvers = () => { for (let resolver = resolvers.shift(); resolver; resolver = resolvers.shift()) resolver(); }; - const cleanup = () => { - this.off("event", onEvent); - this.off("raw", onRaw); - this.off("error", onEmitterError); - currentSocket.off("open", onOpen); - this._internalEvents.off("close", onClose); - this._internalEvents.off("socketSwap", onSocketSwap); - this._internalEvents.off("reconnecting", onReconnecting); - this._internalEvents.off("reconnected", onReconnected); - }; - const onEvent = (event: unknown) => { if (!isErrorEvent(event)) push({ type: "message", message: event as never }); }; - const onRaw = (data: RawWebSocketData) => push({ type: "raw", data }); - const onEmitterError = (error: WebSocketError) => push({ type: "error", error }); - const onOpen = () => push({ type: "open" }); - const onReconnecting = (event: ReconnectingEvent) => push({ type: "reconnecting", reconnect: event }); - const onReconnected = () => push({ type: "reconnected" }); - const onClose = (code: number, reason: string, unsent: UnsentMessage[]) => { push({ type: "close", code, reason, unsent }); done = true; flushResolvers(); cleanup(); }; - const onSocketSwap = (oldSocket: TSocket, newSocket: TSocket) => { oldSocket.off("open", onOpen); newSocket.on("open", onOpen); currentSocket = newSocket; }; - this.on("event", onEvent); - this.on("raw", onRaw); - this.on("error", onEmitterError); - this.socket.on("open", onOpen); - this._internalEvents.on("close", onClose); - this._internalEvents.on("socketSwap", onSocketSwap); - this._internalEvents.on("reconnecting", onReconnecting); - this._internalEvents.on("reconnected", onReconnected); - if (this._isReconnecting) push({ type: "reconnecting", reconnect: { attempt: 0, maxAttempts: 0, delay: 0, closeCode: 0, parameters: undefined } }); - else if (this.socket.readyState === ReadyState.CONNECTING) push({ type: "connecting" }); - else if (this.socket.readyState === ReadyState.OPEN) push({ type: "open" }); - else if (this.socket.readyState === ReadyState.CLOSING) push({ type: "closing" }); - else { push({ type: "close", code: this._lastCloseCode, reason: this._lastCloseReason, unsent: this._sendQueue.drain() }); done = true; cleanup(); } - const next = (): Promise> => new Promise((resolve) => { - if (queue.length > 0) resolve({ value: queue.shift()!, done: false }); - else if (done) resolve({ value: undefined, done: true }); - else resolvers.push(() => { - if (queue.length > 0) resolve({ value: queue.shift()!, done: false }); - else resolve({ value: undefined, done: true }); - }); - }); - return { - next, - return: () => { done = true; cleanup(); flushResolvers(); return Promise.resolve({ value: undefined, done: true }); }, - [Symbol.asyncIterator]() { return this; }, - }; - } - - private _connect(): TSocket { - this.url = buildURL(this._client, this._parameters ?? {}); - const socket = this._createSocket(this.url, this._authHeaders()); - socket.on("message", (data: string | ArrayBuffer | ArrayBufferView, isBinary: boolean) => { - if (isBinary) { this._emit("raw", data); return; } - const text = typeof data === "string" ? data : String(data); - let event: unknown; - try { event = JSON.parse(text); } catch { this._emit("raw", data); return; } - this._emit("event", event as never); - if (isErrorEvent(event)) this._onError(event as never); - else emitTypedEvent(this, event); - }); - socket.on("error", (err: Error) => { if (!this._isReconnecting) this._onError(null, err.message, err); }); - socket.on("open", () => this._flushSendQueue()); - socket.on("close", (code: number, reason: string) => { - if (socket !== this.socket) return; - if (!this._intentionallyClosed && this._canReconnect(code)) this._reconnect(code); - else if (!this._isReconnecting) this._emitPermanentClose(code, reason); - }); - return socket; - } - - private _canReconnect(code: number): boolean { - if (this._intentionallyClosed || !this._reconnectOptions || this._reconnectOptions.maxRetries === 0 || !this._reconnectOptions.onReconnecting) return false; - return isRecoverableClose(code); - } - - private async _reconnect(closeCode: number): Promise { - if (this._isReconnecting || !this._reconnectOptions) return; - this._isReconnecting = true; - const maxRetries = this._reconnectOptions.maxRetries ?? 5; - const initialDelay = this._reconnectOptions.initialDelay ?? 500; - const maxDelay = this._reconnectOptions.maxDelay ?? 8000; - for (let attempt = 1; attempt <= maxRetries; attempt++) { - if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } - const delay = Math.round(Math.min(initialDelay * 2 ** (attempt - 1), maxDelay) * (0.75 + Math.random() * 0.25)); - let reconnectingEvent: ReconnectingEvent = { attempt, maxAttempts: maxRetries, delay, closeCode, parameters: this._parameters ? { ...this._parameters } : undefined }; - let overrides: ReconnectingOverrides | void; - try { overrides = this._reconnectOptions.onReconnecting(reconnectingEvent); } catch (err) { this._isReconnecting = false; this._onError(null, "onReconnecting callback threw", err); this._emitPermanentClose(closeCode, "onReconnecting callback threw"); return; } - if (overrides && "abort" in overrides && overrides.abort) { this._isReconnecting = false; this._emitPermanentClose(closeCode, "reconnect aborted by handler"); return; } - if (overrides && "parameters" in overrides) { this._parameters = overrides.parameters; reconnectingEvent = { ...reconnectingEvent, parameters: this._parameters }; } - this._emit("reconnecting", reconnectingEvent); - this._internalEvents._emit("reconnecting", reconnectingEvent); - if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } - await sleep(delay); - if (!this._canReconnect(closeCode)) { this._isReconnecting = false; this._emitPermanentClose(this._intentionallyClosed ? this._closeCode : closeCode, this._intentionallyClosed ? this._closeReason : "reconnect aborted"); return; } - let closeCodePromise: Promise | undefined; - try { - const oldSocket = this.socket; - this.socket = this._connect(); - closeCodePromise = new Promise((resolve) => { this.socket.once("close", resolve); }); - await this._awaitOpen(this.socket); - this._internalEvents._emit("socketSwap", oldSocket, this.socket); - this._isReconnecting = false; - this._flushSendQueue(); - this._emit("reconnected"); - this._internalEvents._emit("reconnected"); - return; - } catch { if (closeCodePromise) closeCode = await closeCodePromise; } - } - this._isReconnecting = false; - this._onError(null, `WebSocket reconnect failed after ${maxRetries} attempts (close code: ${closeCode})`, undefined); - this._emitPermanentClose(closeCode, `reconnect failed after ${maxRetries} attempts`); - } - - private _awaitOpen(socket: TSocket): Promise { - return new Promise((resolve, reject) => { - const cleanup = () => { socket.off("open", onOpen); socket.off("error", onError); socket.off("close", onFail); }; - const onOpen = () => { cleanup(); resolve(); }; - const onError = (err: Error) => { cleanup(); reject(err); }; - const onFail = () => { cleanup(); reject(new Error("socket closed before open")); }; - socket.once("open", onOpen); socket.once("error", onError); socket.once("close", onFail); - }); - } - - private _flushSendQueue(): void { - try { this._sendQueue.flush((data) => this.socket.send(flattenRawData(data))); } catch (err) { this._onError(null, "could not send queued data", err); } - } - - private _emitPermanentClose(code: number, reason: string): void { - this._lastCloseCode = code; - this._lastCloseReason = reason; - const unsent = this._sendQueue.drain(); - this._internalEvents._emit("close", code, reason, unsent); - this._emit("close", code, reason, unsent); - } - - protected _authHeaders(): Record { - return { ...this._client.webSocketAuthHeaders(), ...parameterHeaders(this._parameters ?? {}) }; - } -} - -const isErrorEvent = (event: unknown): boolean => typeof event === "object" && event !== null && "type" in event && event.type === "error"; - -const emitTypedEvent = (emitter: MachineLifecycleWSEmitter, event: unknown): void => { - if (typeof event === "object" && event !== null && "type" in event && typeof event.type === "string") { - (emitter._emit as (eventName: string, payload: unknown) => void)(event.type, event); - } -}; diff --git a/src/sdk/resources/machine-lifecycle/ws.ts b/src/sdk/resources/machine-lifecycle/ws.ts deleted file mode 100644 index 68299ea..0000000 --- a/src/sdk/resources/machine-lifecycle/ws.ts +++ /dev/null @@ -1,42 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { WebSocket, type ClientOptions } from 'ws'; -import { NodeWebSocket } from "../../internal/ws-adapter-node"; -import { MachineLifecycleWSBase, type MachineLifecycleWSBaseOptions, type MachineLifecycleWSParameters } from "./ws-base"; -import { Dedalus } from "../../client"; - -export type { MachineLifecycleWSParameters, MachineLifecycleWSReconnectOptions } from "./ws-base"; - -export interface MachineLifecycleWSClientOptions extends ClientOptions, MachineLifecycleWSBaseOptions {} - -export class MachineLifecycleWS extends MachineLifecycleWSBase { - private _wsOptions: ClientOptions | null | undefined; - - constructor( - client: Dedalus, - parameters: MachineLifecycleWSParameters, - options?: MachineLifecycleWSClientOptions | null | undefined, - ) { - if (!WebSocket) { - throw new Error( - "MachineLifecycleWS requires the \"ws\" package but it could not be loaded.", - ); - } - - const { reconnect, maxQueueSize, ...wsOptions } = options ?? {}; - super(client, parameters, { reconnect, maxQueueSize }); - this._wsOptions = wsOptions; - this._connectInitial(); - } - - protected _createSocket(url: URL, authHeaders: Record): NodeWebSocket { - const ws = new WebSocket(url, { - ...this._wsOptions, - headers: { - ...authHeaders, - ...this._wsOptions?.headers, - }, - }); - return new NodeWebSocket(ws); - } -} diff --git a/src/sdk/resources/usage/index.ts b/src/sdk/resources/usage/index.ts deleted file mode 100644 index 64318c3..0000000 --- a/src/sdk/resources/usage/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -export { Usage } from "./usage"; -export type { UsageListParams, UsageListResponse } from "./usage"; -export { Machines } from "./machines"; -export type { MachineListComputeUsageParams, MachineListComputeUsageResponse, MachineListStorageUsageParams, MachineListStorageUsageResponse } from "./machines"; diff --git a/src/sdk/resources/usage/machines.ts b/src/sdk/resources/usage/machines.ts deleted file mode 100644 index 988311c..0000000 --- a/src/sdk/resources/usage/machines.ts +++ /dev/null @@ -1,244 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { APIResource } from "../../resource"; -import { APIPromise } from "../../api-promise"; -import type { RequestOptions } from "../../internal/request-options"; - -export class Machines extends APIResource { - /** - * List machine compute usage breakdown - * - * @param {MachineListComputeUsageParams} [params] - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listComputeUsage = await client.usage.machines.listComputeUsage(); - * ``` - */ - listComputeUsage(params: MachineListComputeUsageParams | null | undefined = {}, options?: RequestOptions): APIPromise { - const { period_start, period_end, machine_id, granularity } = params ?? {}; - return this._client.get("/v1/usage/machines/compute", { query: { period_start: period_start, period_end: period_end, machine_id: machine_id, granularity: granularity }, ...options }); - } - - /** - * List machine storage usage breakdown - * - * @param {MachineListStorageUsageParams} [params] - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const listStorageUsage = await client.usage.machines.listStorageUsage(); - * ``` - */ - listStorageUsage(params: MachineListStorageUsageParams | null | undefined = {}, options?: RequestOptions): APIPromise { - const { period_start, period_end, machine_id } = params ?? {}; - return this._client.get("/v1/usage/machines/storage", { query: { period_start: period_start, period_end: period_end, machine_id: machine_id }, ...options }); - } -} - -export interface MachineListComputeUsageParams { - /** - * Usage period start (YYYY-MM-DD). Defaults to first of current month. - */ - period_start?: string; - /** - * Last UTC usage date to include (YYYY-MM-DD). Defaults to current time. - */ - period_end?: string; - /** - * Optional machine ID filter. - */ - machine_id?: string; - /** - * Usage breakdown granularity: hour or day. Defaults to hour. - */ - granularity?: string; -} - -export interface MachineListComputeUsageResponse { - /** - * Usage breakdown granularity used for rows: hour or day. - */ - granularity: string; - /** - * Exclusive usage period end. - * @format date-time - */ - period_end: string; - /** - * Inclusive usage period start. - * @format date-time - */ - period_start: string; - /** - * Machine-level compute usage breakdown rows. - */ - rows: Array | null; -} - -export namespace MachineListComputeUsageResponse { - export interface Row { - /** - * Machine-awake seconds in this bucket. - * @format int64 - */ - awake_seconds: number; - /** - * Exclusive usage bucket end. - * @format date-time - */ - bucket_end: string; - /** - * Inclusive usage bucket start. - * @format date-time - */ - bucket_start: string; - /** - * Requested vCPU millicores multiplied by guest-owned active CPU seconds. - * @format int64 - */ - cpu_millicore_seconds: number; - /** - * Latest raw window_end represented by this row. - * @format date-time - */ - last_window_end: string; - /** - * Machine identifier. - */ - machine_id: string; - /** - * Requested memory MiB multiplied by running allocation seconds. - * @format int64 - */ - memory_mib_seconds: number; - /** - * Org compute bucket IDs this row contributes to. - */ - org_metering_bucket_ids: Array | null; - /** - * Requested memory for this shape, in MiB. - * @format int32 - */ - requested_memory_mib: number; - /** - * Requested storage for this shape, in GiB. - * @format int32 - */ - requested_storage_gib: number; - /** - * Requested vCPU for this shape. - * @format double - */ - requested_vcpu: number; - /** - * Stable fingerprint for the requested machine shape. - */ - spec_fingerprint: string; - /** - * Stripe CPU meter event identifiers linked to those org buckets. - */ - stripe_cpu_identifiers: Array | null; - /** - * Stripe memory meter event identifiers linked to those org buckets. - */ - stripe_memory_identifiers: Array | null; - /** - * Raw usage windows compacted into this row. - * @format int64 - */ - window_count: number; - /** - * Latest Stripe emission timestamp for linked org buckets, when emitted. - * @format date-time - */ - latest_stripe_emitted_at?: string; - } -} - -export interface MachineListStorageUsageParams { - /** - * Usage period start (YYYY-MM-DD). Defaults to first of current month. - */ - period_start?: string; - /** - * Last UTC usage date to include (YYYY-MM-DD). Defaults to current time. - */ - period_end?: string; - /** - * Optional machine ID filter. - */ - machine_id?: string; -} - -export interface MachineListStorageUsageResponse { - /** - * Exclusive usage period end. - * @format date-time - */ - period_end: string; - /** - * Inclusive usage period start. - * @format date-time - */ - period_start: string; - /** - * Machine-level storage usage breakdown rows. - */ - rows: Array | null; -} - -export namespace MachineListStorageUsageResponse { - export interface Row { - /** - * Exclusive usage bucket end. - * @format date-time - */ - bucket_end: string; - /** - * Inclusive usage bucket start. - * @format date-time - */ - bucket_start: string; - /** - * Machine logical bytes observed for storage allocation. - * @format int64 - */ - logical_storage_bytes: number; - /** - * Machine identifier. - */ - machine_id: string; - /** - * Org storage bucket ID this row contributes to. - */ - org_metering_bucket_id: string; - /** - * Allocated logical MiB-seconds for this machine. - * @format int64 - */ - storage_mib_seconds: number; - /** - * Stripe storage meter event identifier linked to that org bucket. - */ - stripe_storage_identifier: string; - /** - * Latest Stripe emission timestamp for the linked org bucket, when emitted. - * @format date-time - */ - latest_stripe_emitted_at?: string; - } -} -export declare namespace Machines { - export { - type MachineListComputeUsageResponse as MachineListComputeUsageResponse, - type MachineListStorageUsageResponse as MachineListStorageUsageResponse, - type MachineListComputeUsageParams as MachineListComputeUsageParams, - type MachineListStorageUsageParams as MachineListStorageUsageParams, - }; -} -export { Machines as MachineResource }; diff --git a/src/sdk/resources/usage/usage.ts b/src/sdk/resources/usage/usage.ts deleted file mode 100644 index 320b4ef..0000000 --- a/src/sdk/resources/usage/usage.ts +++ /dev/null @@ -1,88 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -import { APIResource } from "../../resource"; -import { APIPromise } from "../../api-promise"; -import type { RequestOptions } from "../../internal/request-options"; -import { Machines, type MachineListComputeUsageResponse, type MachineListStorageUsageResponse, type MachineListComputeUsageParams, type MachineListStorageUsageParams } from "./machines"; - -export class Usage extends APIResource { - machines: Machines = new Machines(this._client); - - /** - * Get usage summary - * - * @param {UsageListParams} [params] - The parameters to send with the request. - * @param {RequestOptions} [options] - Options to apply to the request, such as headers and an abort signal. - * @returns {APIPromise} OK - * - * @example - * ```ts - * const list = await client.usage.list(); - * ``` - */ - list(params: UsageListParams | null | undefined = {}, options?: RequestOptions): APIPromise { - const { period_start } = params ?? {}; - return this._client.get("/v1/usage", { query: { period_start: period_start }, ...options }); - } -} - -export interface UsageListParams { - /** - * Billing period start (YYYY-MM-DD). Defaults to first of current month. - */ - period_start?: string; -} - -export interface UsageListResponse { - /** - * Closed awake seconds in billed org buckets for the period. - * @format int64 - */ - billed_awake_seconds: number; - /** - * Closed requested vCPU millicores multiplied by guest-owned active CPU seconds for the period. - * @format int64 - */ - billed_cpu_millicore_seconds: number; - /** - * Closed billable logical MiB-seconds for the period, matching the Stripe storage meter. - * @format int64 - */ - billed_logical_storage_mib_seconds: number; - /** - * Closed requested memory MiB multiplied by running allocation seconds for the period. - * @format int64 - */ - billed_memory_mib_seconds: number; - /** - * Plan-included storage in GiB, used as a local guardrail only. - * @format int64 - */ - included_storage_gib: number; - /** - * Billing plan in effect for the organization. - */ - plan_slug: string; - /** - * Current provisioned storage summed across machines in GiB. - * @format int64 - */ - provisioned_storage_gib: number; -} -Usage.Machines = Machines; - -export declare namespace Usage { - export { - type UsageListResponse as UsageListResponse, - type UsageListParams as UsageListParams, - }; - - export { - Machines as Machines, - type MachineListComputeUsageResponse as MachineListComputeUsageResponse, - type MachineListStorageUsageResponse as MachineListStorageUsageResponse, - type MachineListComputeUsageParams as MachineListComputeUsageParams, - type MachineListStorageUsageParams as MachineListStorageUsageParams, - }; -} -export { Usage as UsageResource }; diff --git a/src/sdk/streaming.ts b/src/sdk/streaming.ts deleted file mode 100644 index a497962..0000000 --- a/src/sdk/streaming.ts +++ /dev/null @@ -1,4 +0,0 @@ -// File generated from our OpenAPI spec by Scalar. See README.md for details. - -/** @deprecated Import from ./core/streaming instead */ -export * from './core/streaming'; diff --git a/src/sdk/version.ts b/src/sdk/version.ts index 1bee298..fa11664 100644 --- a/src/sdk/version.ts +++ b/src/sdk/version.ts @@ -1,3 +1,3 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -export const VERSION = "0.0.1"; +export const VERSION = "0.1.0"; // x-release-please-version diff --git a/tests/smoke-test.ts b/tests/smoke-test.ts index 0687e2c..b69e69b 100644 --- a/tests/smoke-test.ts +++ b/tests/smoke-test.ts @@ -33,230 +33,6 @@ type SmokeResult = { // are metadata used for filtering and reporting. This list is generated, so it stays in sync with // the CLI command surface. const cases: { operation: string; method: string; path: string; args: string[] }[] = [ - { - operation: "list", - method: "GET", - path: "/v1/machines", - args: ["machine-lifecycle","list"], - }, - - { - operation: "create", - method: "POST", - path: "/v1/machines", - args: ["machine-lifecycle","create","--memory-mib","1","--storage-gib","1","--vcpu","1"], - }, - - { - operation: "delete", - method: "DELETE", - path: "/v1/machines/{machine_id}", - args: ["machine-lifecycle","delete","--machine-id","machine_id"], - }, - - { - operation: "retrieve", - method: "GET", - path: "/v1/machines/{machine_id}", - args: ["machine-lifecycle","retrieve","--machine-id","machine_id"], - }, - - { - operation: "patch", - method: "PATCH", - path: "/v1/machines/{machine_id}", - args: ["machine-lifecycle","patch","--machine-id","machine_id"], - }, - - { - operation: "listArtifacts", - method: "GET", - path: "/v1/machines/{machine_id}/artifacts", - args: ["machine-lifecycle","list-artifacts","--machine-id","machine_id"], - }, - - { - operation: "deleteArtifact", - method: "DELETE", - path: "/v1/machines/{machine_id}/artifacts/{artifact_id}", - args: ["machine-lifecycle","delete-artifact","--machine-id","machine_id","--artifact-id","artifact_id"], - }, - - { - operation: "retrieveArtifact", - method: "GET", - path: "/v1/machines/{machine_id}/artifacts/{artifact_id}", - args: ["machine-lifecycle","retrieve-artifact","--machine-id","machine_id","--artifact-id","artifact_id"], - }, - - { - operation: "listExecutions", - method: "GET", - path: "/v1/machines/{machine_id}/executions", - args: ["machine-lifecycle","list-executions","--machine-id","machine_id"], - }, - - { - operation: "createExecution", - method: "POST", - path: "/v1/machines/{machine_id}/executions", - args: ["machine-lifecycle","create-execution","--machine-id","machine_id","--command","[\"command\"]"], - }, - - { - operation: "deleteExecution", - method: "DELETE", - path: "/v1/machines/{machine_id}/executions/{execution_id}", - args: ["machine-lifecycle","delete-execution","--machine-id","machine_id","--execution-id","execution_id"], - }, - - { - operation: "retrieveExecution", - method: "GET", - path: "/v1/machines/{machine_id}/executions/{execution_id}", - args: ["machine-lifecycle","retrieve-execution","--machine-id","machine_id","--execution-id","execution_id"], - }, - - { - operation: "listExecutionEvents", - method: "GET", - path: "/v1/machines/{machine_id}/executions/{execution_id}/events", - args: ["machine-lifecycle","list-execution-events","--machine-id","machine_id","--execution-id","execution_id"], - }, - - { - operation: "listExecutionOutput", - method: "GET", - path: "/v1/machines/{machine_id}/executions/{execution_id}/output", - args: ["machine-lifecycle","list-execution-output","--machine-id","machine_id","--execution-id","execution_id"], - }, - - { - operation: "listPreviews", - method: "GET", - path: "/v1/machines/{machine_id}/previews", - args: ["machine-lifecycle","list-previews","--machine-id","machine_id"], - }, - - { - operation: "createPreview", - method: "POST", - path: "/v1/machines/{machine_id}/previews", - args: ["machine-lifecycle","create-preview","--machine-id","machine_id","--port","1"], - }, - - { - operation: "deletePreview", - method: "DELETE", - path: "/v1/machines/{machine_id}/previews/{preview_id}", - args: ["machine-lifecycle","delete-preview","--machine-id","machine_id","--preview-id","preview_id"], - }, - - { - operation: "retrievePreview", - method: "GET", - path: "/v1/machines/{machine_id}/previews/{preview_id}", - args: ["machine-lifecycle","retrieve-preview","--machine-id","machine_id","--preview-id","preview_id"], - }, - - { - operation: "sleep", - method: "POST", - path: "/v1/machines/{machine_id}/sleep", - args: ["machine-lifecycle","sleep","--machine-id","machine_id"], - }, - - { - operation: "listSshSessions", - method: "GET", - path: "/v1/machines/{machine_id}/ssh", - args: ["machine-lifecycle","list-ssh-sessions","--machine-id","machine_id"], - }, - - { - operation: "createSshSession", - method: "POST", - path: "/v1/machines/{machine_id}/ssh", - args: ["machine-lifecycle","create-ssh-session","--machine-id","machine_id","--public-key","public_key"], - }, - - { - operation: "deleteSshSession", - method: "DELETE", - path: "/v1/machines/{machine_id}/ssh/{session_id}", - args: ["machine-lifecycle","delete-ssh-session","--machine-id","machine_id","--session-id","session_id"], - }, - - { - operation: "retrieveSshSession", - method: "GET", - path: "/v1/machines/{machine_id}/ssh/{session_id}", - args: ["machine-lifecycle","retrieve-ssh-session","--machine-id","machine_id","--session-id","session_id"], - }, - - { - operation: "watchStatus", - method: "GET", - path: "/v1/machines/{machine_id}/status/stream", - args: ["machine-lifecycle","watch-status","--machine-id","machine_id","--max-items","10"], - }, - - { - operation: "listTerminals", - method: "GET", - path: "/v1/machines/{machine_id}/terminals", - args: ["machine-lifecycle","list-terminals","--machine-id","machine_id"], - }, - - { - operation: "createTerminal", - method: "POST", - path: "/v1/machines/{machine_id}/terminals", - args: ["machine-lifecycle","create-terminal","--machine-id","machine_id","--height","1","--width","1"], - }, - - { - operation: "deleteTerminal", - method: "DELETE", - path: "/v1/machines/{machine_id}/terminals/{terminal_id}", - args: ["machine-lifecycle","delete-terminal","--machine-id","machine_id","--terminal-id","terminal_id"], - }, - - { - operation: "retrieveTerminal", - method: "GET", - path: "/v1/machines/{machine_id}/terminals/{terminal_id}", - args: ["machine-lifecycle","retrieve-terminal","--machine-id","machine_id","--terminal-id","terminal_id"], - }, - - { - operation: "wake", - method: "POST", - path: "/v1/machines/{machine_id}/wake", - args: ["machine-lifecycle","wake","--machine-id","machine_id"], - }, - - { - operation: "list", - method: "GET", - path: "/v1/usage", - args: ["usage","list"], - }, - - { - operation: "listComputeUsage", - method: "GET", - path: "/v1/usage/machines/compute", - args: ["usage:machines","list-compute-usage"], - }, - - { - operation: "listStorageUsage", - method: "GET", - path: "/v1/usage/machines/storage", - args: ["usage:machines","list-storage-usage"], - }, - ] // Each command gets its own budget so one hanging command fails on its own instead of stalling From d3892e26352b20aa7d024b4fb6afdfb6dc8c9503 Mon Sep 17 00:00:00 2001 From: "scalar-docs[bot]" <148485328+scalar-docs[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:26:06 +0000 Subject: [PATCH 3/3] release: 0.6.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- src/commands/index.ts | 2 +- src/sdk/version.ts | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2aca35a..4208b5c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.0" + ".": "0.6.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d989661..8829a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.6.0](https://github.com/dedalus-labs/dedalus-cli/compare/v0.5.0...v0.6.0) (2026-08-10) + + +### Features + +* **api:** initial SDK generation ([50afe86](https://github.com/dedalus-labs/dedalus-cli/commit/50afe863f2a606f9be967d4c4dbecab89ab36e64)) + + +### Chores + +* **api:** regenerate SDK ([fd5540f](https://github.com/dedalus-labs/dedalus-cli/commit/fd5540f2220b861f6b36ebf653f954e36d5b069a)) + ## 0.5.0 (2026-07-10) Full Changelog: [v0.4.0...v0.5.0](https://github.com/dedalus-labs/dedalus-cli/compare/v0.4.0...v0.5.0) diff --git a/package.json b/package.json index 8baf425..6c2cc91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dedalus-cli", - "version": "0.1.0", + "version": "0.6.0", "description": "Controlplane API for Dedalus Cloud Services (DCS).", "type": "module", "bin": { diff --git a/src/commands/index.ts b/src/commands/index.ts index f43c67b..cb040d7 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -68,7 +68,7 @@ export const getProgram = (): Command => createProgram({ SDK, binaryName: "dedalus", - version: "0.1.0", // x-release-please-version + version: "0.6.0", // x-release-please-version description: "CLI for Dedalus", defaultFormat: "auto", defaultErrorFormat: "auto", diff --git a/src/sdk/version.ts b/src/sdk/version.ts index fa11664..16ea94e 100644 --- a/src/sdk/version.ts +++ b/src/sdk/version.ts @@ -1,3 +1,3 @@ // File generated from our OpenAPI spec by Scalar. See README.md for details. -export const VERSION = "0.1.0"; // x-release-please-version +export const VERSION = "0.6.0"; // x-release-please-version