[GH-ISSUE #72] Add App Store assets (screenshots + previews) upload support #18

Closed
opened 2026-02-26 21:32:48 +03:00 by kerem · 2 comments
Owner

Originally created by @rudrankriyam on GitHub (Jan 24, 2026).
Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/72

Summary

Add App Store metadata assets management (screenshots + app previews). ASC already handles text localizations, but does not support asset upload/manage.

Current State (verified)

Repo contains localizations for text fields only. No appScreenshot* or appPreview* commands or client support.

API Endpoints (App Store Connect OpenAPI)

Screenshot / Preview Sets and Assets

  • POST /v1/appScreenshotSets

  • GET|DELETE /v1/appScreenshotSets/{id}

  • GET /v1/appScreenshotSets/{id}/appScreenshots

  • GET|PATCH /v1/appScreenshotSets/{id}/relationships/appScreenshots

  • POST /v1/appScreenshots

  • GET|PATCH|DELETE /v1/appScreenshots/{id}

  • POST /v1/appPreviewSets

  • GET|DELETE /v1/appPreviewSets/{id}

  • GET /v1/appPreviewSets/{id}/appPreviews

  • GET|PATCH /v1/appPreviewSets/{id}/relationships/appPreviews

  • POST /v1/appPreviews

  • GET|PATCH|DELETE /v1/appPreviews/{id}

Linking from version localizations

  • GET /v1/appStoreVersionLocalizations/{id}/appScreenshotSets
  • GET /v1/appStoreVersionLocalizations/{id}/appPreviewSets

Proposed CLI

asc assets screenshots list --version-localization LOC_ID
asc assets screenshots upload --version-localization LOC_ID --path ./screenshots --device-type IPHONE_65
asc assets screenshots delete --id SCREENSHOT_ID --confirm

asc assets previews list --version-localization LOC_ID
asc assets previews upload --version-localization LOC_ID --path ./previews --device-type IPHONE_65
asc assets previews delete --id PREVIEW_ID --confirm

Implementation Notes

  • Asset upload is multi-step (create asset, upload operations, then patch state). Mirror the build upload flow (upload operations) with safe file handling.
  • Maintain JSON-first output with table/markdown available.

Implementation Plan

  1. internal/asc/assets.go
  • Types for appScreenshot/appPreview resources and upload operations.
  • Client methods: create/get/delete sets; create/get/patch/delete screenshots/previews; list assets by set/localization.
  1. cmd/assets.go
  • New assets command group with screenshots + previews subcommands.
  • Validation: required IDs/paths, supported device types, file existence.
  1. Output helpers
  • Add table/markdown views for assets and sets.
  1. Tests
  • CLI validation tests and upload flow tests.
  • HTTP client tests for create/upload/patch sequences.

Acceptance Criteria

  • Can list, upload, and delete screenshots/previews for a localization.
  • Upload flow is safe (no symlink writes) and reports all created asset IDs.
  • JSON output by default; table/markdown supported.
Originally created by @rudrankriyam on GitHub (Jan 24, 2026). Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/72 ## Summary Add App Store metadata assets management (screenshots + app previews). ASC already handles text localizations, but **does not** support asset upload/manage. ## Current State (verified) Repo contains `localizations` for text fields only. No `appScreenshot*` or `appPreview*` commands or client support. ## API Endpoints (App Store Connect OpenAPI) Screenshot / Preview Sets and Assets - `POST /v1/appScreenshotSets` - `GET|DELETE /v1/appScreenshotSets/{id}` - `GET /v1/appScreenshotSets/{id}/appScreenshots` - `GET|PATCH /v1/appScreenshotSets/{id}/relationships/appScreenshots` - `POST /v1/appScreenshots` - `GET|PATCH|DELETE /v1/appScreenshots/{id}` - `POST /v1/appPreviewSets` - `GET|DELETE /v1/appPreviewSets/{id}` - `GET /v1/appPreviewSets/{id}/appPreviews` - `GET|PATCH /v1/appPreviewSets/{id}/relationships/appPreviews` - `POST /v1/appPreviews` - `GET|PATCH|DELETE /v1/appPreviews/{id}` Linking from version localizations - `GET /v1/appStoreVersionLocalizations/{id}/appScreenshotSets` - `GET /v1/appStoreVersionLocalizations/{id}/appPreviewSets` ## Proposed CLI ``` asc assets screenshots list --version-localization LOC_ID asc assets screenshots upload --version-localization LOC_ID --path ./screenshots --device-type IPHONE_65 asc assets screenshots delete --id SCREENSHOT_ID --confirm asc assets previews list --version-localization LOC_ID asc assets previews upload --version-localization LOC_ID --path ./previews --device-type IPHONE_65 asc assets previews delete --id PREVIEW_ID --confirm ``` ## Implementation Notes - Asset upload is multi-step (create asset, upload operations, then patch state). Mirror the build upload flow (upload operations) with safe file handling. - Maintain JSON-first output with table/markdown available. ## Implementation Plan 1) `internal/asc/assets.go` - Types for appScreenshot/appPreview resources and upload operations. - Client methods: create/get/delete sets; create/get/patch/delete screenshots/previews; list assets by set/localization. 2) `cmd/assets.go` - New `assets` command group with `screenshots` + `previews` subcommands. - Validation: required IDs/paths, supported device types, file existence. 3) Output helpers - Add table/markdown views for assets and sets. 4) Tests - CLI validation tests and upload flow tests. - HTTP client tests for create/upload/patch sequences. ## Acceptance Criteria - Can list, upload, and delete screenshots/previews for a localization. - Upload flow is safe (no symlink writes) and reports all created asset IDs. - JSON output by default; table/markdown supported.
kerem closed this issue 2026-02-26 21:32:48 +03:00
Author
Owner

@rudrankriyam commented on GitHub (Jan 24, 2026):

@cursor

Implementation Guide

Codebase Context

This feature involves multi-step upload flows. See cmd/builds.go and internal/asc/client_builds.go for the existing build upload pattern with upload operations.

File Structure

1. internal/asc/assets.go (~250-300 lines)

// Screenshot Set types
type AppScreenshotSetAttributes struct {
    ScreenshotDisplayType string `json:"screenshotDisplayType"` // e.g., APP_IPHONE_65
}

type AppScreenshotAttributes struct {
    FileSize         int    `json:"fileSize"`
    FileName         string `json:"fileName"`
    SourceFileChecksum *Checksum `json:"sourceFileChecksum,omitempty"`
    ImageAsset       *ImageAsset `json:"imageAsset,omitempty"`
    AssetToken       string `json:"assetToken,omitempty"`
    AssetType        string `json:"assetType,omitempty"`
    UploadOperations []UploadOperation `json:"uploadOperations,omitempty"`
    AssetDeliveryState *AssetDeliveryState `json:"assetDeliveryState,omitempty"`
}

type ImageAsset struct {
    TemplateURL string `json:"templateUrl"`
    Width       int    `json:"width"`
    Height      int    `json:"height"`
}

type AssetDeliveryState struct {
    State   string   `json:"state"` // AWAITING_UPLOAD, UPLOAD_COMPLETE, COMPLETE, FAILED
    Errors  []ErrorDetail `json:"errors,omitempty"`
}

// Preview Set types (similar structure)
type AppPreviewSetAttributes struct {
    PreviewType string `json:"previewType"` // e.g., IPHONE_65
}

type AppPreviewAttributes struct {
    FileSize         int    `json:"fileSize"`
    FileName         string `json:"fileName"`
    SourceFileChecksum *Checksum `json:"sourceFileChecksum,omitempty"`
    PreviewFrameTimeCode string `json:"previewFrameTimeCode,omitempty"`
    MimeType         string `json:"mimeType,omitempty"`
    VideoURL         string `json:"videoUrl,omitempty"`
    PreviewImage     *ImageAsset `json:"previewImage,omitempty"`
    UploadOperations []UploadOperation `json:"uploadOperations,omitempty"`
    AssetDeliveryState *AssetDeliveryState `json:"assetDeliveryState,omitempty"`
}

// UploadOperation already exists in client.go - reuse it
// type UploadOperation struct { ... }

// Response types
type AppScreenshotSetsResponse = Response[AppScreenshotSetAttributes]
type AppScreenshotSetResponse = SingleResponse[AppScreenshotSetAttributes]
type AppScreenshotsResponse = Response[AppScreenshotAttributes]
type AppScreenshotResponse = SingleResponse[AppScreenshotAttributes]
type AppPreviewSetsResponse = Response[AppPreviewSetAttributes]
type AppPreviewSetResponse = SingleResponse[AppPreviewSetAttributes]
type AppPreviewsResponse = Response[AppPreviewAttributes]
type AppPreviewResponse = SingleResponse[AppPreviewAttributes]

// Device display types (for validation)
var ValidScreenshotDisplayTypes = []string{
    "APP_IPHONE_67", "APP_IPHONE_65", "APP_IPHONE_61", "APP_IPHONE_58", "APP_IPHONE_55",
    "APP_IPAD_PRO_129", "APP_IPAD_PRO_3RD_GEN_129", "APP_IPAD_PRO_11",
    "APP_APPLE_TV", "APP_APPLE_WATCH_ULTRA", "APP_APPLE_WATCH_SERIES_10",
    // ... add more from API docs
}

2. internal/asc/client_assets.go (~200-250 lines)

// Screenshot Sets
func (c *Client) GetAppScreenshotSets(ctx context.Context, localizationID string) (*AppScreenshotSetsResponse, error)
func (c *Client) CreateAppScreenshotSet(ctx context.Context, localizationID string, displayType string) (*AppScreenshotSetResponse, error)
func (c *Client) DeleteAppScreenshotSet(ctx context.Context, setID string) error

// Screenshots
func (c *Client) GetAppScreenshots(ctx context.Context, setID string) (*AppScreenshotsResponse, error)
func (c *Client) CreateAppScreenshot(ctx context.Context, setID string, fileName string, fileSize int) (*AppScreenshotResponse, error)
func (c *Client) UpdateAppScreenshot(ctx context.Context, screenshotID string, uploaded bool, checksum *Checksum) (*AppScreenshotResponse, error)
func (c *Client) DeleteAppScreenshot(ctx context.Context, screenshotID string) error

// Preview Sets (similar pattern)
func (c *Client) GetAppPreviewSets(ctx context.Context, localizationID string) (*AppPreviewSetsResponse, error)
func (c *Client) CreateAppPreviewSet(ctx context.Context, localizationID string, previewType string) (*AppPreviewSetResponse, error)
func (c *Client) DeleteAppPreviewSet(ctx context.Context, setID string) error

// Previews
func (c *Client) GetAppPreviews(ctx context.Context, setID string) (*AppPreviewsResponse, error)
func (c *Client) CreateAppPreview(ctx context.Context, setID string, fileName string, fileSize int, mimeType string) (*AppPreviewResponse, error)
func (c *Client) UpdateAppPreview(ctx context.Context, previewID string, uploaded bool, checksum *Checksum) (*AppPreviewResponse, error)
func (c *Client) DeleteAppPreview(ctx context.Context, previewID string) error

3. internal/asc/assets_upload.go (~150-200 lines)

// Upload helpers - reuse pattern from build upload
func UploadAsset(ctx context.Context, filePath string, operations []UploadOperation) error
func ValidateImageFile(path string) error  // check file exists, is regular file, reasonable size
func ComputeChecksum(path string, algorithm ChecksumAlgorithm) (*Checksum, error)

4. internal/asc/assets_output.go (~100-150 lines)

func printAppScreenshotSetsTable(resp *AppScreenshotSetsResponse) error
func printAppScreenshotSetsMarkdown(resp *AppScreenshotSetsResponse) error
func printAppScreenshotsTable(resp *AppScreenshotsResponse) error
func printAppScreenshotsMarkdown(resp *AppScreenshotsResponse) error
// Similar for previews

5. cmd/assets.go (~350-400 lines)

func AssetsCommand() *ffcli.Command  // Parent group

// Screenshots subcommands
func AssetsScreenshotsCommand() *ffcli.Command  // Nested group
func AssetsScreenshotsListCommand() *ffcli.Command
func AssetsScreenshotsUploadCommand() *ffcli.Command
func AssetsScreenshotsDeleteCommand() *ffcli.Command

// Previews subcommands  
func AssetsPreviewsCommand() *ffcli.Command  // Nested group
func AssetsPreviewsListCommand() *ffcli.Command
func AssetsPreviewsUploadCommand() *ffcli.Command
func AssetsPreviewsDeleteCommand() *ffcli.Command

// Upload command flags:
// --version-localization LOC_ID (required)
// --path ./screenshots (directory or single file)
// --device-type IPHONE_65 (required for upload)
// --output, --pretty

6. Register in cmd/commands.go
Add AssetsCommand() to RootCommand().Subcommands

Upload Flow (multi-step)

1. CreateAppScreenshotSet (if set for device type doesn't exist)
2. CreateAppScreenshot (reserves upload slot, returns uploadOperations)
3. Execute PUT requests to each uploadOperation URL with file chunks
4. UpdateAppScreenshot (uploaded: true, sourceFileChecksum) to commit
5. Poll for AssetDeliveryState.State == "COMPLETE"

API Endpoints Reference

Screenshot Sets:
GET    /v1/appStoreVersionLocalizations/{id}/appScreenshotSets → GetAppScreenshotSets
POST   /v1/appScreenshotSets                                    → CreateAppScreenshotSet
DELETE /v1/appScreenshotSets/{id}                               → DeleteAppScreenshotSet
GET    /v1/appScreenshotSets/{id}/appScreenshots                → GetAppScreenshots

Screenshots:
POST   /v1/appScreenshots                                       → CreateAppScreenshot
GET    /v1/appScreenshots/{id}                                  → GetAppScreenshot
PATCH  /v1/appScreenshots/{id}                                  → UpdateAppScreenshot
DELETE /v1/appScreenshots/{id}                                  → DeleteAppScreenshot

Preview Sets + Previews: Similar pattern with appPreviewSets/appPreviews

Safety Requirements

  • Use os.Lstat to check files before reading (prevent symlink attacks)
  • Validate file size before upload
  • Use existing secure file helpers from cmd/secure_open_unix.go

Testing

  • Run make test && make lint
  • Add upload flow tests with mocked HTTP responses
  • Test file validation (symlinks rejected, max size limits)
<!-- gh-comment-id:3795327516 --> @rudrankriyam commented on GitHub (Jan 24, 2026): @cursor ## Implementation Guide ### Codebase Context This feature involves multi-step upload flows. See `cmd/builds.go` and `internal/asc/client_builds.go` for the existing build upload pattern with upload operations. ### File Structure **1. `internal/asc/assets.go`** (~250-300 lines) ```go // Screenshot Set types type AppScreenshotSetAttributes struct { ScreenshotDisplayType string `json:"screenshotDisplayType"` // e.g., APP_IPHONE_65 } type AppScreenshotAttributes struct { FileSize int `json:"fileSize"` FileName string `json:"fileName"` SourceFileChecksum *Checksum `json:"sourceFileChecksum,omitempty"` ImageAsset *ImageAsset `json:"imageAsset,omitempty"` AssetToken string `json:"assetToken,omitempty"` AssetType string `json:"assetType,omitempty"` UploadOperations []UploadOperation `json:"uploadOperations,omitempty"` AssetDeliveryState *AssetDeliveryState `json:"assetDeliveryState,omitempty"` } type ImageAsset struct { TemplateURL string `json:"templateUrl"` Width int `json:"width"` Height int `json:"height"` } type AssetDeliveryState struct { State string `json:"state"` // AWAITING_UPLOAD, UPLOAD_COMPLETE, COMPLETE, FAILED Errors []ErrorDetail `json:"errors,omitempty"` } // Preview Set types (similar structure) type AppPreviewSetAttributes struct { PreviewType string `json:"previewType"` // e.g., IPHONE_65 } type AppPreviewAttributes struct { FileSize int `json:"fileSize"` FileName string `json:"fileName"` SourceFileChecksum *Checksum `json:"sourceFileChecksum,omitempty"` PreviewFrameTimeCode string `json:"previewFrameTimeCode,omitempty"` MimeType string `json:"mimeType,omitempty"` VideoURL string `json:"videoUrl,omitempty"` PreviewImage *ImageAsset `json:"previewImage,omitempty"` UploadOperations []UploadOperation `json:"uploadOperations,omitempty"` AssetDeliveryState *AssetDeliveryState `json:"assetDeliveryState,omitempty"` } // UploadOperation already exists in client.go - reuse it // type UploadOperation struct { ... } // Response types type AppScreenshotSetsResponse = Response[AppScreenshotSetAttributes] type AppScreenshotSetResponse = SingleResponse[AppScreenshotSetAttributes] type AppScreenshotsResponse = Response[AppScreenshotAttributes] type AppScreenshotResponse = SingleResponse[AppScreenshotAttributes] type AppPreviewSetsResponse = Response[AppPreviewSetAttributes] type AppPreviewSetResponse = SingleResponse[AppPreviewSetAttributes] type AppPreviewsResponse = Response[AppPreviewAttributes] type AppPreviewResponse = SingleResponse[AppPreviewAttributes] // Device display types (for validation) var ValidScreenshotDisplayTypes = []string{ "APP_IPHONE_67", "APP_IPHONE_65", "APP_IPHONE_61", "APP_IPHONE_58", "APP_IPHONE_55", "APP_IPAD_PRO_129", "APP_IPAD_PRO_3RD_GEN_129", "APP_IPAD_PRO_11", "APP_APPLE_TV", "APP_APPLE_WATCH_ULTRA", "APP_APPLE_WATCH_SERIES_10", // ... add more from API docs } ``` **2. `internal/asc/client_assets.go`** (~200-250 lines) ```go // Screenshot Sets func (c *Client) GetAppScreenshotSets(ctx context.Context, localizationID string) (*AppScreenshotSetsResponse, error) func (c *Client) CreateAppScreenshotSet(ctx context.Context, localizationID string, displayType string) (*AppScreenshotSetResponse, error) func (c *Client) DeleteAppScreenshotSet(ctx context.Context, setID string) error // Screenshots func (c *Client) GetAppScreenshots(ctx context.Context, setID string) (*AppScreenshotsResponse, error) func (c *Client) CreateAppScreenshot(ctx context.Context, setID string, fileName string, fileSize int) (*AppScreenshotResponse, error) func (c *Client) UpdateAppScreenshot(ctx context.Context, screenshotID string, uploaded bool, checksum *Checksum) (*AppScreenshotResponse, error) func (c *Client) DeleteAppScreenshot(ctx context.Context, screenshotID string) error // Preview Sets (similar pattern) func (c *Client) GetAppPreviewSets(ctx context.Context, localizationID string) (*AppPreviewSetsResponse, error) func (c *Client) CreateAppPreviewSet(ctx context.Context, localizationID string, previewType string) (*AppPreviewSetResponse, error) func (c *Client) DeleteAppPreviewSet(ctx context.Context, setID string) error // Previews func (c *Client) GetAppPreviews(ctx context.Context, setID string) (*AppPreviewsResponse, error) func (c *Client) CreateAppPreview(ctx context.Context, setID string, fileName string, fileSize int, mimeType string) (*AppPreviewResponse, error) func (c *Client) UpdateAppPreview(ctx context.Context, previewID string, uploaded bool, checksum *Checksum) (*AppPreviewResponse, error) func (c *Client) DeleteAppPreview(ctx context.Context, previewID string) error ``` **3. `internal/asc/assets_upload.go`** (~150-200 lines) ```go // Upload helpers - reuse pattern from build upload func UploadAsset(ctx context.Context, filePath string, operations []UploadOperation) error func ValidateImageFile(path string) error // check file exists, is regular file, reasonable size func ComputeChecksum(path string, algorithm ChecksumAlgorithm) (*Checksum, error) ``` **4. `internal/asc/assets_output.go`** (~100-150 lines) ```go func printAppScreenshotSetsTable(resp *AppScreenshotSetsResponse) error func printAppScreenshotSetsMarkdown(resp *AppScreenshotSetsResponse) error func printAppScreenshotsTable(resp *AppScreenshotsResponse) error func printAppScreenshotsMarkdown(resp *AppScreenshotsResponse) error // Similar for previews ``` **5. `cmd/assets.go`** (~350-400 lines) ```go func AssetsCommand() *ffcli.Command // Parent group // Screenshots subcommands func AssetsScreenshotsCommand() *ffcli.Command // Nested group func AssetsScreenshotsListCommand() *ffcli.Command func AssetsScreenshotsUploadCommand() *ffcli.Command func AssetsScreenshotsDeleteCommand() *ffcli.Command // Previews subcommands func AssetsPreviewsCommand() *ffcli.Command // Nested group func AssetsPreviewsListCommand() *ffcli.Command func AssetsPreviewsUploadCommand() *ffcli.Command func AssetsPreviewsDeleteCommand() *ffcli.Command // Upload command flags: // --version-localization LOC_ID (required) // --path ./screenshots (directory or single file) // --device-type IPHONE_65 (required for upload) // --output, --pretty ``` **6. Register in `cmd/commands.go`** Add `AssetsCommand()` to RootCommand().Subcommands ### Upload Flow (multi-step) ``` 1. CreateAppScreenshotSet (if set for device type doesn't exist) 2. CreateAppScreenshot (reserves upload slot, returns uploadOperations) 3. Execute PUT requests to each uploadOperation URL with file chunks 4. UpdateAppScreenshot (uploaded: true, sourceFileChecksum) to commit 5. Poll for AssetDeliveryState.State == "COMPLETE" ``` ### API Endpoints Reference ``` Screenshot Sets: GET /v1/appStoreVersionLocalizations/{id}/appScreenshotSets → GetAppScreenshotSets POST /v1/appScreenshotSets → CreateAppScreenshotSet DELETE /v1/appScreenshotSets/{id} → DeleteAppScreenshotSet GET /v1/appScreenshotSets/{id}/appScreenshots → GetAppScreenshots Screenshots: POST /v1/appScreenshots → CreateAppScreenshot GET /v1/appScreenshots/{id} → GetAppScreenshot PATCH /v1/appScreenshots/{id} → UpdateAppScreenshot DELETE /v1/appScreenshots/{id} → DeleteAppScreenshot Preview Sets + Previews: Similar pattern with appPreviewSets/appPreviews ``` ### Safety Requirements - Use `os.Lstat` to check files before reading (prevent symlink attacks) - Validate file size before upload - Use existing secure file helpers from `cmd/secure_open_unix.go` ### Testing - Run `make test && make lint` - Add upload flow tests with mocked HTTP responses - Test file validation (symlinks rejected, max size limits)
Author
Owner

@cursor[bot] commented on GitHub (Jan 24, 2026):

Summary:

  • Added asset types, client endpoints, upload helpers, and table/markdown output for screenshot/preview sets and assets.
  • Implemented asc assets CLI with list/upload/delete flows, safe file handling, checksuming, uploads, and delivery-state polling.
  • Added validation/output tests plus upload helper tests and client HTTP/request tests.

Tests:

  • make test
  • make lint

Notes:

  • Uploads compute MD5 checksums and enforce a 1GB safety cap in ValidateImageFile; preview uploads infer MIME type from the file extension (e.g., .mov, .mp4).

View PR
Open in Cursor Open in Web

<!-- gh-comment-id:3795327692 --> @cursor[bot] commented on GitHub (Jan 24, 2026): Summary: - Added asset types, client endpoints, upload helpers, and table/markdown output for screenshot/preview sets and assets. - Implemented `asc assets` CLI with list/upload/delete flows, safe file handling, checksuming, uploads, and delivery-state polling. - Added validation/output tests plus upload helper tests and client HTTP/request tests. Tests: - `make test` - `make lint` Notes: - Uploads compute MD5 checksums and enforce a 1GB safety cap in `ValidateImageFile`; preview uploads infer MIME type from the file extension (e.g., `.mov`, `.mp4`). <a href="https://github.com/rudrankriyam/App-Store-Connect-CLI/pull/103"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/view-pr-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/view-pr-light.svg"><img alt="View PR" src="https://cursor.com/view-pr-light.svg"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-11598b4b-0c2d-49d9-b28f-dc30493560d8"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-11598b4b-0c2d-49d9-b28f-dc30493560d8"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/App-Store-Connect-CLI#18
No description provided.