[GH-ISSUE #71] Add Signing & Provisioning (Bundle IDs, Certificates, Devices, Profiles) #16

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/71

Summary

Add Signing & Provisioning management: Bundle IDs, Capabilities, Certificates, Devices, and Profiles.

Current State (verified)

Repo search for bundle IDs, certificates, devices, and profiles returns no matching commands or client methods.

API Endpoints (App Store Connect OpenAPI)

Bundle IDs + Capabilities

  • GET|POST /v1/bundleIds
  • GET|PATCH|DELETE /v1/bundleIds/{id}
  • GET /v1/bundleIds/{id}/bundleIdCapabilities
  • POST /v1/bundleIdCapabilities
  • PATCH|DELETE /v1/bundleIdCapabilities/{id}

Certificates

  • GET|POST /v1/certificates
  • GET|PATCH|DELETE /v1/certificates/{id}

Devices

  • GET|POST /v1/devices
  • GET|PATCH /v1/devices/{id}

Profiles

  • GET|POST /v1/profiles
  • GET|DELETE /v1/profiles/{id}
  • GET /v1/profiles/{id}/bundleId
  • GET /v1/profiles/{id}/certificates
  • GET /v1/profiles/{id}/devices

Proposed CLI

asc bundle-ids list [--limit] [--paginate]
asc bundle-ids get --id BUNDLE_ID
asc bundle-ids create --identifier com.example.app --name "Example"
asc bundle-ids update --id BUNDLE_ID --name "New Name"
asc bundle-ids delete --id BUNDLE_ID --confirm

asc bundle-ids capabilities list --bundle BUNDLE_ID
asc bundle-ids capabilities add --bundle BUNDLE_ID --capability CAPABILITY_TYPE [--settings ...]
asc bundle-ids capabilities remove --id CAPABILITY_ID --confirm

asc certificates list [--certificate-type IOS_DISTRIBUTION|IOS_DEVELOPMENT|...] [--limit] [--paginate]
asc certificates create --certificate-type ... --csr ./cert.csr
asc certificates revoke --id CERT_ID --confirm

asc devices list [--platform IOS|MAC_OS|...] [--limit] [--paginate]
asc devices register --name "Device" --udid UDID --platform IOS
asc devices update --id DEVICE_ID --status ENABLED|DISABLED

asc profiles list [--profile-type IOS_APP_DEVELOPMENT|IOS_APP_STORE|...] [--limit] [--paginate]
asc profiles create --name "Profile" --bundle BUNDLE_ID --certificate CERT_ID[,CERT_ID...] --device DEVICE_ID[,DEVICE_ID...]
asc profiles get --id PROFILE_ID
asc profiles delete --id PROFILE_ID --confirm
asc profiles download --id PROFILE_ID --output ./profile.mobileprovision

Implementation Plan

  1. internal/asc/signing.go
  • Types for bundle IDs, capabilities, certificates, devices, profiles.
  • Client methods for list/get/create/update/delete; profile download payload handling.
  1. cmd/signing.go
  • New command groups: bundle-ids, certificates, devices, profiles.
  • Validations: required flags, enum values, confirm flags.
  1. Output helpers
  • Add table/markdown formatting for core types.
  1. Tests
  • CLI validation tests.
  • HTTP client tests for each endpoint.

Acceptance Criteria

  • End-to-end CRUD for bundle IDs, certificates, devices, profiles.
  • Bundle ID capabilities can be added/removed.
  • Profile download writes a .mobileprovision file safely.
  • 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/71 ## Summary Add Signing & Provisioning management: Bundle IDs, Capabilities, Certificates, Devices, and Profiles. ## Current State (verified) Repo search for bundle IDs, certificates, devices, and profiles returns no matching commands or client methods. ## API Endpoints (App Store Connect OpenAPI) Bundle IDs + Capabilities - `GET|POST /v1/bundleIds` - `GET|PATCH|DELETE /v1/bundleIds/{id}` - `GET /v1/bundleIds/{id}/bundleIdCapabilities` - `POST /v1/bundleIdCapabilities` - `PATCH|DELETE /v1/bundleIdCapabilities/{id}` Certificates - `GET|POST /v1/certificates` - `GET|PATCH|DELETE /v1/certificates/{id}` Devices - `GET|POST /v1/devices` - `GET|PATCH /v1/devices/{id}` Profiles - `GET|POST /v1/profiles` - `GET|DELETE /v1/profiles/{id}` - `GET /v1/profiles/{id}/bundleId` - `GET /v1/profiles/{id}/certificates` - `GET /v1/profiles/{id}/devices` ## Proposed CLI ``` asc bundle-ids list [--limit] [--paginate] asc bundle-ids get --id BUNDLE_ID asc bundle-ids create --identifier com.example.app --name "Example" asc bundle-ids update --id BUNDLE_ID --name "New Name" asc bundle-ids delete --id BUNDLE_ID --confirm asc bundle-ids capabilities list --bundle BUNDLE_ID asc bundle-ids capabilities add --bundle BUNDLE_ID --capability CAPABILITY_TYPE [--settings ...] asc bundle-ids capabilities remove --id CAPABILITY_ID --confirm asc certificates list [--certificate-type IOS_DISTRIBUTION|IOS_DEVELOPMENT|...] [--limit] [--paginate] asc certificates create --certificate-type ... --csr ./cert.csr asc certificates revoke --id CERT_ID --confirm asc devices list [--platform IOS|MAC_OS|...] [--limit] [--paginate] asc devices register --name "Device" --udid UDID --platform IOS asc devices update --id DEVICE_ID --status ENABLED|DISABLED asc profiles list [--profile-type IOS_APP_DEVELOPMENT|IOS_APP_STORE|...] [--limit] [--paginate] asc profiles create --name "Profile" --bundle BUNDLE_ID --certificate CERT_ID[,CERT_ID...] --device DEVICE_ID[,DEVICE_ID...] asc profiles get --id PROFILE_ID asc profiles delete --id PROFILE_ID --confirm asc profiles download --id PROFILE_ID --output ./profile.mobileprovision ``` ## Implementation Plan 1) `internal/asc/signing.go` - Types for bundle IDs, capabilities, certificates, devices, profiles. - Client methods for list/get/create/update/delete; profile download payload handling. 2) `cmd/signing.go` - New command groups: `bundle-ids`, `certificates`, `devices`, `profiles`. - Validations: required flags, enum values, confirm flags. 3) Output helpers - Add table/markdown formatting for core types. 4) Tests - CLI validation tests. - HTTP client tests for each endpoint. ## Acceptance Criteria - End-to-end CRUD for bundle IDs, certificates, devices, profiles. - Bundle ID capabilities can be added/removed. - Profile download writes a .mobileprovision file safely. - 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 is a large feature - consider splitting into 2-3 PRs:

  1. Bundle IDs + Capabilities
  2. Certificates + Devices
  3. Profiles

File Structure

1. internal/asc/signing.go (~300-400 lines)

// Bundle ID types
type BundleIDAttributes struct {
    Name       string   `json:"name"`
    Identifier string   `json:"identifier"`
    Platform   Platform `json:"platform"`
    SeedID     string   `json:"seedId,omitempty"`
}

type BundleIDCapabilityAttributes struct {
    CapabilityType string                 `json:"capabilityType"`
    Settings       []CapabilitySetting    `json:"settings,omitempty"`
}

type CapabilitySetting struct {
    Key     string                  `json:"key"`
    Name    string                  `json:"name,omitempty"`
    Options []CapabilityOption      `json:"options,omitempty"`
}

// Certificate types
type CertificateAttributes struct {
    Name               string `json:"name"`
    CertificateType    string `json:"certificateType"`
    DisplayName        string `json:"displayName,omitempty"`
    SerialNumber       string `json:"serialNumber,omitempty"`
    Platform           string `json:"platform,omitempty"`
    ExpirationDate     string `json:"expirationDate,omitempty"`
    CertificateContent string `json:"certificateContent,omitempty"` // Base64 DER
}

// Device types  
type DeviceAttributes struct {
    Name        string   `json:"name"`
    UDID        string   `json:"udid"`
    Platform    Platform `json:"platform"`
    Status      string   `json:"status"` // ENABLED, DISABLED
    DeviceClass string   `json:"deviceClass,omitempty"`
    Model       string   `json:"model,omitempty"`
    AddedDate   string   `json:"addedDate,omitempty"`
}

// Profile types
type ProfileAttributes struct {
    Name           string   `json:"name"`
    Platform       Platform `json:"platform,omitempty"`
    ProfileType    string   `json:"profileType"`
    ProfileState   string   `json:"profileState,omitempty"`
    ProfileContent string   `json:"profileContent,omitempty"` // Base64 encoded
    UUID           string   `json:"uuid,omitempty"`
    CreatedDate    string   `json:"createdDate,omitempty"`
    ExpirationDate string   `json:"expirationDate,omitempty"`
}

// Response types using generics from client_types.go:
type BundleIDsResponse = Response[BundleIDAttributes]
type BundleIDResponse = SingleResponse[BundleIDAttributes]
type CertificatesResponse = Response[CertificateAttributes]
type CertificateResponse = SingleResponse[CertificateAttributes]
type DevicesResponse = Response[DeviceAttributes]
type DeviceResponse = SingleResponse[DeviceAttributes]
type ProfilesResponse = Response[ProfileAttributes]
type ProfileResponse = SingleResponse[ProfileAttributes]

2. internal/asc/client_signing.go (~250-350 lines)

// Bundle IDs
func (c *Client) GetBundleIDs(ctx context.Context, opts ...BundleIDsOption) (*BundleIDsResponse, error)
func (c *Client) GetBundleID(ctx context.Context, id string) (*BundleIDResponse, error)
func (c *Client) CreateBundleID(ctx context.Context, attrs BundleIDCreateAttributes) (*BundleIDResponse, error)
func (c *Client) UpdateBundleID(ctx context.Context, id string, attrs BundleIDUpdateAttributes) (*BundleIDResponse, error)
func (c *Client) DeleteBundleID(ctx context.Context, id string) error
func (c *Client) GetBundleIDCapabilities(ctx context.Context, bundleID string) (*BundleIDCapabilitiesResponse, error)
func (c *Client) CreateBundleIDCapability(ctx context.Context, bundleID string, attrs BundleIDCapabilityCreateAttributes) (*BundleIDCapabilityResponse, error)
func (c *Client) DeleteBundleIDCapability(ctx context.Context, capabilityID string) error

// Certificates
func (c *Client) GetCertificates(ctx context.Context, opts ...CertificatesOption) (*CertificatesResponse, error)
func (c *Client) GetCertificate(ctx context.Context, id string) (*CertificateResponse, error)
func (c *Client) CreateCertificate(ctx context.Context, csrContent string, certType string) (*CertificateResponse, error)
func (c *Client) RevokeCertificate(ctx context.Context, id string) error

// Devices
func (c *Client) GetDevices(ctx context.Context, opts ...DevicesOption) (*DevicesResponse, error)
func (c *Client) GetDevice(ctx context.Context, id string) (*DeviceResponse, error)
func (c *Client) RegisterDevice(ctx context.Context, attrs DeviceCreateAttributes) (*DeviceResponse, error)
func (c *Client) UpdateDevice(ctx context.Context, id string, attrs DeviceUpdateAttributes) (*DeviceResponse, error)

// Profiles
func (c *Client) GetProfiles(ctx context.Context, opts ...ProfilesOption) (*ProfilesResponse, error)
func (c *Client) GetProfile(ctx context.Context, id string) (*ProfileResponse, error)
func (c *Client) CreateProfile(ctx context.Context, attrs ProfileCreateAttributes) (*ProfileResponse, error)
func (c *Client) DeleteProfile(ctx context.Context, id string) error

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

// Table/markdown formatters for each type
func printBundleIDsTable(resp *BundleIDsResponse) error
func printBundleIDsMarkdown(resp *BundleIDsResponse) error
func printCertificatesTable(resp *CertificatesResponse) error
func printCertificatesMarkdown(resp *CertificatesResponse) error
func printDevicesTable(resp *DevicesResponse) error
func printDevicesMarkdown(resp *DevicesResponse) error
func printProfilesTable(resp *ProfilesResponse) error
func printProfilesMarkdown(resp *ProfilesResponse) error

4. Command files (keep under 400 lines each per CLAUDE.md guidelines):

cmd/bundle_ids.go (~250 lines)

func BundleIDsCommand() *ffcli.Command
func BundleIDsListCommand() *ffcli.Command  
func BundleIDsGetCommand() *ffcli.Command
func BundleIDsCreateCommand() *ffcli.Command
func BundleIDsUpdateCommand() *ffcli.Command
func BundleIDsDeleteCommand() *ffcli.Command
func BundleIDsCapabilitiesCommand() *ffcli.Command // subcommand group

cmd/certificates.go (~200 lines)

func CertificatesCommand() *ffcli.Command
func CertificatesListCommand() *ffcli.Command
func CertificatesCreateCommand() *ffcli.Command  // --csr flag reads CSR file
func CertificatesRevokeCommand() *ffcli.Command  // requires --confirm

cmd/devices.go (~200 lines)

func DevicesCommand() *ffcli.Command
func DevicesListCommand() *ffcli.Command
func DevicesRegisterCommand() *ffcli.Command
func DevicesUpdateCommand() *ffcli.Command  // --status ENABLED|DISABLED

cmd/profiles.go (~250 lines)

func ProfilesCommand() *ffcli.Command
func ProfilesListCommand() *ffcli.Command
func ProfilesGetCommand() *ffcli.Command
func ProfilesCreateCommand() *ffcli.Command
func ProfilesDeleteCommand() *ffcli.Command
func ProfilesDownloadCommand() *ffcli.Command  // writes .mobileprovision safely

5. Register all in cmd/commands.go
Add to RootCommand().Subcommands:

  • BundleIDsCommand()
  • CertificatesCommand()
  • DevicesCommand()
  • ProfilesCommand()

Profile Download Safety

For profiles download, follow the secure file writing pattern from cmd/localizations.go:

// Use openNewFileNoFollow for safe file creation (no symlink attacks)
// See cmd/secure_open_unix.go for the implementation

API Endpoints Reference

Bundle IDs:
GET    /v1/bundleIds                                → GetBundleIDs
POST   /v1/bundleIds                                → CreateBundleID
GET    /v1/bundleIds/{id}                           → GetBundleID
PATCH  /v1/bundleIds/{id}                           → UpdateBundleID
DELETE /v1/bundleIds/{id}                           → DeleteBundleID
GET    /v1/bundleIds/{id}/bundleIdCapabilities      → GetBundleIDCapabilities
POST   /v1/bundleIdCapabilities                     → CreateBundleIDCapability
DELETE /v1/bundleIdCapabilities/{id}                → DeleteBundleIDCapability

Certificates:
GET    /v1/certificates                             → GetCertificates
POST   /v1/certificates                             → CreateCertificate
GET    /v1/certificates/{id}                        → GetCertificate
DELETE /v1/certificates/{id}                        → RevokeCertificate

Devices:
GET    /v1/devices                                  → GetDevices
POST   /v1/devices                                  → RegisterDevice
GET    /v1/devices/{id}                             → GetDevice
PATCH  /v1/devices/{id}                             → UpdateDevice

Profiles:
GET    /v1/profiles                                 → GetProfiles
POST   /v1/profiles                                 → CreateProfile
GET    /v1/profiles/{id}                            → GetProfile
DELETE /v1/profiles/{id}                            → DeleteProfile

Testing

  • Run make test && make lint before committing
  • Add HTTP client tests in internal/asc/client_http_test.go
  • Test profile download writes correct binary content (base64 decode profileContent)
<!-- gh-comment-id:3795327479 --> @rudrankriyam commented on GitHub (Jan 24, 2026): @cursor ## Implementation Guide ### Codebase Context This is a large feature - consider splitting into 2-3 PRs: 1. Bundle IDs + Capabilities 2. Certificates + Devices 3. Profiles ### File Structure **1. `internal/asc/signing.go`** (~300-400 lines) ```go // Bundle ID types type BundleIDAttributes struct { Name string `json:"name"` Identifier string `json:"identifier"` Platform Platform `json:"platform"` SeedID string `json:"seedId,omitempty"` } type BundleIDCapabilityAttributes struct { CapabilityType string `json:"capabilityType"` Settings []CapabilitySetting `json:"settings,omitempty"` } type CapabilitySetting struct { Key string `json:"key"` Name string `json:"name,omitempty"` Options []CapabilityOption `json:"options,omitempty"` } // Certificate types type CertificateAttributes struct { Name string `json:"name"` CertificateType string `json:"certificateType"` DisplayName string `json:"displayName,omitempty"` SerialNumber string `json:"serialNumber,omitempty"` Platform string `json:"platform,omitempty"` ExpirationDate string `json:"expirationDate,omitempty"` CertificateContent string `json:"certificateContent,omitempty"` // Base64 DER } // Device types type DeviceAttributes struct { Name string `json:"name"` UDID string `json:"udid"` Platform Platform `json:"platform"` Status string `json:"status"` // ENABLED, DISABLED DeviceClass string `json:"deviceClass,omitempty"` Model string `json:"model,omitempty"` AddedDate string `json:"addedDate,omitempty"` } // Profile types type ProfileAttributes struct { Name string `json:"name"` Platform Platform `json:"platform,omitempty"` ProfileType string `json:"profileType"` ProfileState string `json:"profileState,omitempty"` ProfileContent string `json:"profileContent,omitempty"` // Base64 encoded UUID string `json:"uuid,omitempty"` CreatedDate string `json:"createdDate,omitempty"` ExpirationDate string `json:"expirationDate,omitempty"` } // Response types using generics from client_types.go: type BundleIDsResponse = Response[BundleIDAttributes] type BundleIDResponse = SingleResponse[BundleIDAttributes] type CertificatesResponse = Response[CertificateAttributes] type CertificateResponse = SingleResponse[CertificateAttributes] type DevicesResponse = Response[DeviceAttributes] type DeviceResponse = SingleResponse[DeviceAttributes] type ProfilesResponse = Response[ProfileAttributes] type ProfileResponse = SingleResponse[ProfileAttributes] ``` **2. `internal/asc/client_signing.go`** (~250-350 lines) ```go // Bundle IDs func (c *Client) GetBundleIDs(ctx context.Context, opts ...BundleIDsOption) (*BundleIDsResponse, error) func (c *Client) GetBundleID(ctx context.Context, id string) (*BundleIDResponse, error) func (c *Client) CreateBundleID(ctx context.Context, attrs BundleIDCreateAttributes) (*BundleIDResponse, error) func (c *Client) UpdateBundleID(ctx context.Context, id string, attrs BundleIDUpdateAttributes) (*BundleIDResponse, error) func (c *Client) DeleteBundleID(ctx context.Context, id string) error func (c *Client) GetBundleIDCapabilities(ctx context.Context, bundleID string) (*BundleIDCapabilitiesResponse, error) func (c *Client) CreateBundleIDCapability(ctx context.Context, bundleID string, attrs BundleIDCapabilityCreateAttributes) (*BundleIDCapabilityResponse, error) func (c *Client) DeleteBundleIDCapability(ctx context.Context, capabilityID string) error // Certificates func (c *Client) GetCertificates(ctx context.Context, opts ...CertificatesOption) (*CertificatesResponse, error) func (c *Client) GetCertificate(ctx context.Context, id string) (*CertificateResponse, error) func (c *Client) CreateCertificate(ctx context.Context, csrContent string, certType string) (*CertificateResponse, error) func (c *Client) RevokeCertificate(ctx context.Context, id string) error // Devices func (c *Client) GetDevices(ctx context.Context, opts ...DevicesOption) (*DevicesResponse, error) func (c *Client) GetDevice(ctx context.Context, id string) (*DeviceResponse, error) func (c *Client) RegisterDevice(ctx context.Context, attrs DeviceCreateAttributes) (*DeviceResponse, error) func (c *Client) UpdateDevice(ctx context.Context, id string, attrs DeviceUpdateAttributes) (*DeviceResponse, error) // Profiles func (c *Client) GetProfiles(ctx context.Context, opts ...ProfilesOption) (*ProfilesResponse, error) func (c *Client) GetProfile(ctx context.Context, id string) (*ProfileResponse, error) func (c *Client) CreateProfile(ctx context.Context, attrs ProfileCreateAttributes) (*ProfileResponse, error) func (c *Client) DeleteProfile(ctx context.Context, id string) error ``` **3. `internal/asc/signing_output.go`** (~150-200 lines) ```go // Table/markdown formatters for each type func printBundleIDsTable(resp *BundleIDsResponse) error func printBundleIDsMarkdown(resp *BundleIDsResponse) error func printCertificatesTable(resp *CertificatesResponse) error func printCertificatesMarkdown(resp *CertificatesResponse) error func printDevicesTable(resp *DevicesResponse) error func printDevicesMarkdown(resp *DevicesResponse) error func printProfilesTable(resp *ProfilesResponse) error func printProfilesMarkdown(resp *ProfilesResponse) error ``` **4. Command files** (keep under 400 lines each per CLAUDE.md guidelines): `cmd/bundle_ids.go` (~250 lines) ```go func BundleIDsCommand() *ffcli.Command func BundleIDsListCommand() *ffcli.Command func BundleIDsGetCommand() *ffcli.Command func BundleIDsCreateCommand() *ffcli.Command func BundleIDsUpdateCommand() *ffcli.Command func BundleIDsDeleteCommand() *ffcli.Command func BundleIDsCapabilitiesCommand() *ffcli.Command // subcommand group ``` `cmd/certificates.go` (~200 lines) ```go func CertificatesCommand() *ffcli.Command func CertificatesListCommand() *ffcli.Command func CertificatesCreateCommand() *ffcli.Command // --csr flag reads CSR file func CertificatesRevokeCommand() *ffcli.Command // requires --confirm ``` `cmd/devices.go` (~200 lines) ```go func DevicesCommand() *ffcli.Command func DevicesListCommand() *ffcli.Command func DevicesRegisterCommand() *ffcli.Command func DevicesUpdateCommand() *ffcli.Command // --status ENABLED|DISABLED ``` `cmd/profiles.go` (~250 lines) ```go func ProfilesCommand() *ffcli.Command func ProfilesListCommand() *ffcli.Command func ProfilesGetCommand() *ffcli.Command func ProfilesCreateCommand() *ffcli.Command func ProfilesDeleteCommand() *ffcli.Command func ProfilesDownloadCommand() *ffcli.Command // writes .mobileprovision safely ``` **5. Register all in `cmd/commands.go`** Add to RootCommand().Subcommands: - `BundleIDsCommand()` - `CertificatesCommand()` - `DevicesCommand()` - `ProfilesCommand()` ### Profile Download Safety For `profiles download`, follow the secure file writing pattern from `cmd/localizations.go`: ```go // Use openNewFileNoFollow for safe file creation (no symlink attacks) // See cmd/secure_open_unix.go for the implementation ``` ### API Endpoints Reference ``` Bundle IDs: GET /v1/bundleIds → GetBundleIDs POST /v1/bundleIds → CreateBundleID GET /v1/bundleIds/{id} → GetBundleID PATCH /v1/bundleIds/{id} → UpdateBundleID DELETE /v1/bundleIds/{id} → DeleteBundleID GET /v1/bundleIds/{id}/bundleIdCapabilities → GetBundleIDCapabilities POST /v1/bundleIdCapabilities → CreateBundleIDCapability DELETE /v1/bundleIdCapabilities/{id} → DeleteBundleIDCapability Certificates: GET /v1/certificates → GetCertificates POST /v1/certificates → CreateCertificate GET /v1/certificates/{id} → GetCertificate DELETE /v1/certificates/{id} → RevokeCertificate Devices: GET /v1/devices → GetDevices POST /v1/devices → RegisterDevice GET /v1/devices/{id} → GetDevice PATCH /v1/devices/{id} → UpdateDevice Profiles: GET /v1/profiles → GetProfiles POST /v1/profiles → CreateProfile GET /v1/profiles/{id} → GetProfile DELETE /v1/profiles/{id} → DeleteProfile ``` ### Testing - Run `make test && make lint` before committing - Add HTTP client tests in `internal/asc/client_http_test.go` - Test profile download writes correct binary content (base64 decode profileContent)
Author
Owner

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

Thanks for the detailed guide! I’ll follow this structure and split the work into 3 PRs as suggested:

Plan

  1. PR 1 – Bundle IDs + Capabilities
    • Add internal/asc/signing.go types + internal/asc/client_signing.go bundle‑ID endpoints.
    • Add internal/asc/signing_output.go for bundle ID output.
    • Add cmd/bundle_ids.go (+ capabilities subcommands) and register in cmd/commands.go.
    • Add HTTP client tests in internal/asc/client_http_test.go.
  2. PR 2 – Certificates + Devices
    • Add types, client methods, output, and CLI commands.
  3. PR 3 – Profiles
    • Add types, client methods, output, CLI commands.
    • Implement safe download using openNewFileNoFollow as in cmd/localizations.go.
    • Add tests for base64 decode + file output.

I’ll keep command files <400 lines per CLAUDE.md, match the endpoint mapping you listed, and run make test && make lint before committing each PR.

Create PR
Open in Cursor Open in Web

<!-- gh-comment-id:3795327543 --> @cursor[bot] commented on GitHub (Jan 24, 2026): Thanks for the detailed guide! I’ll follow this structure and split the work into 3 PRs as suggested: **Plan** 1) **PR 1 – Bundle IDs + Capabilities** - Add `internal/asc/signing.go` types + `internal/asc/client_signing.go` bundle‑ID endpoints. - Add `internal/asc/signing_output.go` for bundle ID output. - Add `cmd/bundle_ids.go` (+ capabilities subcommands) and register in `cmd/commands.go`. - Add HTTP client tests in `internal/asc/client_http_test.go`. 2) **PR 2 – Certificates + Devices** - Add types, client methods, output, and CLI commands. 3) **PR 3 – Profiles** - Add types, client methods, output, CLI commands. - Implement **safe download** using `openNewFileNoFollow` as in `cmd/localizations.go`. - Add tests for base64 decode + file output. I’ll keep command files <400 lines per CLAUDE.md, match the endpoint mapping you listed, and run `make test && make lint` before committing each PR. <a href="https://github.com/rudrankriyam/App-Store-Connect-CLI/compare/main...cursor/bundle-id-capabilities-1434?expand=1&title=Bundle%20ID%20capabilities&body=Add%20App%20Store%20Connect%20API%20support%20for%20managing%20Bundle%20IDs%20and%20their%20capabilities.%0A%0A---%0A%3Ca%20href%3D%22https%3A%2F%2Fcursor.com%2Fbackground-agent%3FbcId%3Dbc-a16c7588-ba6e-49f6-a3cd-8f027bc014a7%22%3E%3Cpicture%3E%3Csource%20media%3D%22(prefers-color-scheme%3A%20dark)%22%20srcset%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-cursor-dark.svg%22%3E%3Csource%20media%3D%22(prefers-color-scheme%3A%20light)%22%20srcset%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-cursor-light.svg%22%3E%3Cimg%20alt%3D%22Open%20in%20Cursor%22%20src%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-cursor.svg%22%3E%3C%2Fpicture%3E%3C%2Fa%3E%26nbsp%3B%3Ca%20href%3D%22https%3A%2F%2Fcursor.com%2Fagents%3Fid%3Dbc-a16c7588-ba6e-49f6-a3cd-8f027bc014a7%22%3E%3Cpicture%3E%3Csource%20media%3D%22(prefers-color-scheme%3A%20dark)%22%20srcset%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-web-dark.svg%22%3E%3Csource%20media%3D%22(prefers-color-scheme%3A%20light)%22%20srcset%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-web-light.svg%22%3E%3Cimg%20alt%3D%22Open%20in%20Web%22%20src%3D%22https%3A%2F%2Fcursor.com%2Fopen-in-web.svg%22%3E%3C%2Fpicture%3E%3C%2Fa%3E"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/create-pr-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/create-pr-light.svg"><img alt="Create PR" src="https://cursor.com/create-pr-light.svg"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-a16c7588-ba6e-49f6-a3cd-8f027bc014a7"><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-a16c7588-ba6e-49f6-a3cd-8f027bc014a7"><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#16
No description provided.