diff --git a/.claude/skills/implement-feature/SKILL.md b/.claude/skills/implement-feature/SKILL.md index f96d397e..90b6e034 100644 --- a/.claude/skills/implement-feature/SKILL.md +++ b/.claude/skills/implement-feature/SKILL.md @@ -36,8 +36,8 @@ Implement features using architecture-first design, TDD, rich domain models, and • Register the route in RESTRoutes.swift • Verify _links is populated for the parent resource -4. FEATURE DOC - • Write docs/features/.md from the actual implementation +4. DOCS + • docs/features//README.md (user-facing, ≤200 lines), one CHANGELOG line, make docs ``` ## Phase 0: Architecture Design (MANDATORY) @@ -335,26 +335,32 @@ Per CLAUDE.md, **a feature is not complete until it is reachable via REST.** Ste } ``` -6. Add a REST test in `Tests/ASCCommandTests/Commands/Web/RESTRoutesTests.swift` that calls `execute(repo:affordanceMode: .rest)` and asserts `_links` + the resolved REST path. +6. **Advertise the resource** from `APIRoot.structuredAffordances` (`Sources/Domain/Shared/APIRoot.swift`) when it is a new top-level resource, so `GET /api/v1` lists it. -7. **Smoke-test the live server.** Build (`swift build`), restart (`asc web-server`), and `curl` the parent resource — confirm `_links` for each item points at the new endpoint. +7. Add a REST test in `Tests/ASCCommandTests/Commands/Web/RESTRoutesTests.swift` that calls `execute(repo:affordanceMode: .rest)` and asserts `_links` + the resolved REST path. -### Phase 5: Feature doc +8. **Smoke-test the live server.** Build (`swift build`), restart (`asc web-server`), and `curl` the parent resource — confirm `_links` for each item points at the new endpoint. -Write `docs/features/.md` from the actual implementation. The doc is derived from code — read the files, then write. Never write from memory. +**REST rules:** +- CLI flag and REST query-param names match: `--expired-only` ↔ `?expired-only=true`, both calling the same repository method. +- Helpers in `RESTRoutes.swift`: `restFormat(items)` (REST twin of `formatAgentItems(…, affordanceMode: .rest)`) and `jsonError(message, status:)` (from `Infrastructure/Web/ASCWebServer.swift`). +- `RESTPathResolver` maps affordance actions to HTTP: `list`/`get` → GET, `create`/`add` → POST to the parent's collection, `update` → PATCH, `delete`/`remove` → DELETE, anything else (e.g. `submit`) → POST `…/{id}/{action}`. +- Controllers are structs with dependencies injected at init; repositories are constructed once in `RESTRoutes.configure`, never per request. -Structure: -1. **CLI Usage** — one section per command, with flags table + examples + table-output sample -2. **REST Endpoints** — path table + query-param mapping + curl example -3. **Typical Workflow** — end-to-end bash script showing the happy path -4. **Architecture** — three-layer ASCII diagram + dependency note -5. **Domain Models** — every public struct/enum/protocol with fields, computed properties, and affordances -6. **File Map** — `Sources/` and `Tests/` trees + wiring files table (must list the REST controller) -7. **API Reference** — endpoint → SDK call → repository method table -8. **Testing** — one representative test snippet + `swift test` command -9. **Extending** — natural next steps with stub code +### Phase 5: Docs -Use `docs/features/screenshots.md` as the canonical reference example. +Docs follow `docs/documentation-design/README.md`: each fact has one home, and nothing repeats `--help` or the source. + +1. **`docs/features//README.md`** — user-facing, ≤200 lines, written from the actual implementation (read the code, never write from memory): + - frontmatter `description:` — one sentence, what + "Use when …", ≤250 chars (it becomes the docs index row) + - Quick start → Workflows → REST (paths + query-param mapping) → Gotchas → See also + - **No** flag tables (`docs/commands.md` is generated), architecture, domain model listings, file maps or test snippets. Non-obvious reasons (Apple caps, multi-call workarounds) go in Gotchas as one-liners. + - A topic that doesn't fit goes into `docs/features//.md` next to the README. + - Example: `docs/features/testflight/README.md`. +2. **`CHANGELOG.md`** — one bullet under `[Unreleased]`, ≤300 chars: starts with what the user types, says the effect (not the implementation), ends with `→ [docs](docs/features//README.md)` and the PR link. Implementation detail goes in the PR. +3. **`make docs`** — regenerates `docs/commands.md` and the `docs/README.md` index. Never edit those by hand. +4. **README.md** — only if the feature is a new row in its "What It Covers" table. +5. `make check-docs` — no broken links, within budgets. --- @@ -415,14 +421,14 @@ Use `docs/features/screenshots.md` as the canonical reference example. ### Phase 4: REST exposure - [ ] Controller added under `Sources/ASCCommand/Commands/Web/Controllers/` - [ ] Wired in `Sources/ASCCommand/Commands/Web/RESTRoutes.swift` +- [ ] New top-level resource advertised in `APIRoot.structuredAffordances` +- [ ] REST query-param names match the CLI flags - [ ] REST test in `RESTRoutesTests.swift` — calls `execute(repo:affordanceMode: .rest)`, asserts `"_links"` + resolved path - [ ] **Smoke-tested live server** — restarted and curl'd the parent resource; `_links` confirms the new endpoint URL -### Phase 5: Feature Doc -- [ ] `docs/features/.md` written from actual code (read files first) -- [ ] CLI commands documented with flags table + examples -- [ ] **REST Endpoints section** with path table + query-param mapping + curl example -- [ ] Domain models section matches actual struct fields -- [ ] File map reflects actual directory structure (must list REST controller) -- [ ] API reference table complete +### Phase 5: Docs +- [ ] `docs/features//README.md` with `description`, written from actual code, ≤200 lines +- [ ] **REST section** with path table + query-param mapping +- [ ] No flag tables, architecture, domain listings or file maps +- [ ] `make docs` run; `make check-docs` clean for the new files - [ ] CHANGELOG.md entry under `[Unreleased]` \ No newline at end of file diff --git a/.claude/skills/implement-feature/references/architecture-diagrams.md b/.claude/skills/implement-feature/references/architecture-diagrams.md index d0bfeddf..1f011a63 100644 --- a/.claude/skills/implement-feature/references/architecture-diagrams.md +++ b/.claude/skills/implement-feature/references/architecture-diagrams.md @@ -7,7 +7,7 @@ - [Command Tree](#command-tree) - [Agent-First Output Flow](#agent-first-output-flow) - [Component Tables](#component-tables) -- [Files Checklist](#files-checklist) +- [Files to Create/Modify](#files-to-createmodify) --- diff --git a/.claude/skills/improvement/SKILL.md b/.claude/skills/improvement/SKILL.md index dc46218d..6c12bb09 100644 --- a/.claude/skills/improvement/SKILL.md +++ b/.claude/skills/improvement/SKILL.md @@ -53,9 +53,9 @@ Make improvements to existing functionality using TDD and rich domain design. ┌─────────────────────────────────────────────────────────────┐ │ 4. UPDATE FEATURE DOC (if public surface changed) │ ├─────────────────────────────────────────────────────────────┤ -│ • Update docs/features/.md to match new behavior │ -│ • Only required when CLI flags, domain model fields, │ -│ affordances, or architecture changed │ +│ • Update docs/features//README.md if usage, │ +│ REST, or gotchas changed (flags: just run `make docs`) │ +│ • One CHANGELOG line under [Unreleased] │ │ • Derive from code — read files, then write │ └─────────────────────────────────────────────────────────────┘ ``` @@ -191,4 +191,4 @@ swift test # Must remain all green - [ ] Improvement implemented - [ ] New test PASSES - [ ] All existing tests still pass (`swift test`) -- [ ] `docs/features/.md` updated if CLI flags, domain model fields, affordances, or architecture changed \ No newline at end of file +- [ ] `docs/features//README.md` updated if usage, REST or gotchas changed; `make docs` if flags changed; one CHANGELOG line \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2ca51fb..580c2a59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,19 @@ env: SWIFT_VERSION: "6.2" jobs: + docs: + name: Docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check links, budgets and changelog + run: python3 scripts/check-docs.py --strict + + - name: Changelog release scripts + run: scripts/test-changelog-release.sh + test: name: Build & Test runs-on: macos-15 @@ -30,6 +43,12 @@ jobs: - name: Build run: swift build -c release + - name: Generated docs are up to date + run: | + python3 scripts/gen-docs.py .build/release/asc + git diff --exit-code docs/commands.md docs/README.md \ + || { echo "::error::Run 'make docs' and commit docs/commands.md and docs/README.md"; exit 1; } + - name: Test with coverage run: swift test --enable-code-coverage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b3c4a23..26f2bd5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,26 +97,9 @@ jobs: run: | VERSION="${{ steps.version.outputs.version }}" ./scripts/promote-changelog.sh "$VERSION" "$(date +%Y-%m-%d)" - # Sync fallback release notes into the newly promoted [VERSION] section - if [ "${{ steps.changelog.outputs.used_fallback }}" = "true" ]; then - awk -v ver="$VERSION" ' - $0 ~ ("^## \\[" ver "\\] - ") { in_ver=1; print; next } - in_ver && /[^[:space:]]/ && !/^---$/ { has_content=1 } - in_ver && /^---$/ { - if (!has_content) { - print "### Changed" - print "- Bug fixes and improvements." - print "" - } - in_ver=0 - } - in_ver && /^## \[/ { in_ver=0 } - { print } - ' CHANGELOG.md > CHANGELOG.tmp && mv CHANGELOG.tmp CHANGELOG.md - fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add CHANGELOG.md + git add CHANGELOG.md docs/changelog git diff --cached --quiet \ && echo "No CHANGELOG changes to commit" \ || git commit -m "chore(changelog): promote [Unreleased] to v${{ steps.version.outputs.version }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae2651b..42308982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,21 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added -- **Submit in-app purchases and subscriptions with an app version** — `asc versions submit --version-id --with-products` adds every in-app purchase and subscription that is `READY_TO_SUBMIT` with a submittable version (and its subscription group's version) to the app version's review submission and submits them together — the way first-time products must go to review — using only the public API and your API key, no iris web session. `--dry-run` lists what would be submitted and submits nothing. REST: `POST /api/v1/versions/:id/submit?with-products=true&dry-run=true` (the route `submitForReview` links pointed at now exists). See `docs/features/submit-with-products.md`. -- **Product versions** — `asc iap versions list --iap-id`, `asc subscriptions versions list --subscription-id` and `asc subscription-groups versions list --group-id` show each product's review versions and state; a submittable version offers `addToSubmission`. IAPs, subscriptions and groups gain a `listVersions` affordance. REST: `GET /api/v1/{iap,subscriptions,subscription-groups}/:id/versions`. -- **Build a review submission step by step** — `asc review-submissions create --app-id [--platform]` (opens or reuses the app's draft), `items add --submission-id` with one of `--version-id`, `--iap-version-id`, `--subscription-version-id`, `--subscription-group-version-id`, `items remove --item-id`, and `submit --submission-id`. REST: `POST /api/v1/apps/:appId/review-submissions`, `POST /api/v1/review-submissions/:id/items`, `DELETE /api/v1/review-submissions/items/:itemId`, `POST /api/v1/review-submissions/:id/submit`. -- **App pricing** — `asc apps price-points list --app-id [--territory USA]` lists the prices an app can be sold at (every page, ~800 per territory; the `0.0` point makes it free) and `asc apps prices set --app-id --base-territory --price-point-id` sets the app's price, which Apple equalizes worldwide. Fixes the "App is not eligible for submission until pricing has been set" refusal from the CLI. `App` gains a `listPricePoints` affordance. REST: `GET /api/v1/apps/:appId/price-points?territory=`, `POST /api/v1/apps/:appId/prices/set`. See `docs/features/app-pricing.md`. -- **Set up app availability** — `asc app-availability create --app-id (--territory X … | --all-territories) [--available-in-new-territories]` sets where an app is sold (App Store Connect's "Set Up Availability"), in one `POST /v2/appAvailabilities`. REST: `POST /api/v1/apps/:appId/availability`, plus `GET` for the existing read. -- **`STORAGE` performance metrics** — the new category Apple reports is mapped and usable with `perf-metrics list --metric-type STORAGE`. - ### Changed -- **Dependencies updated to their latest releases**, with `Package.swift` minimums raised to match: appstoreconnect-swift-sdk 4.4.3 (was 4.2.0), Hummingbird 2.27.0, swift-argument-parser 1.8.2, TauTUI 0.2.2, SweetCookieKit 0.5.3, Mockable 0.6.4 and the rest. The `hello-plugin` example pins Hummingbird 2.27.0 to match the host. -- **Refused submissions explain why** — when Apple refuses to add an item to, or submit, a review submission, the error lists the specific reasons from Apple's `associatedErrors` (missing device screenshots, content rights declaration, App Privacy answers, pricing) instead of only "please check associated errors". +- Docs reorganised: a short README, one folder per feature under `docs/features/`, and a full command reference generated from the binary (`docs/commands.md`). Releases before 0.18 moved to `docs/changelog/`. → [docs](docs/README.md) ([#29](https://github.com/tddworks/asc-cli/pull/29)) +- Refused review submissions now list Apple's specific reasons (missing screenshots, content rights, App Privacy, pricing) instead of only "please check associated errors". ([#27](https://github.com/tddworks/asc-cli/pull/27)) +- Dependencies updated to their latest releases, including appstoreconnect-swift-sdk 4.4.3 (was 4.2.0) and Hummingbird 2.27.0; `Package.swift` minimums raised to match. ([#27](https://github.com/tddworks/asc-cli/pull/27)) + +### Added +- `asc versions submit --with-products` submits every `READY_TO_SUBMIT` in-app purchase and subscription together with the app version, as first-time products require, with just your API key. `--dry-run` previews. → [docs](docs/features/submit-with-products/README.md) ([#27](https://github.com/tddworks/asc-cli/pull/27)) +- `asc review-submissions create`, `items add`, `items remove` and `submit` build a review submission step by step. → [docs](docs/features/review-submissions/README.md) ([#27](https://github.com/tddworks/asc-cli/pull/27)) +- `asc iap versions list`, `asc subscriptions versions list` and `asc subscription-groups versions list` show each product's review versions and state. REST: `GET /api/v1/{iap,subscriptions,subscription-groups}/:id/versions`. ([#27](https://github.com/tddworks/asc-cli/pull/27)) +- `asc apps price-points list` and `asc apps prices set` set the app's price or make it free, fixing the "not eligible for submission until pricing has been set" refusal. → [docs](docs/features/app-pricing/README.md) ([#28](https://github.com/tddworks/asc-cli/pull/28)) +- `asc app-availability create` sets the territories an app is sold in. REST: `POST /api/v1/apps/:appId/availability`. → [docs](docs/features/iap-subscription-availability/README.md) ([#28](https://github.com/tddworks/asc-cli/pull/28)) +- `asc perf-metrics list --metric-type STORAGE` reads Apple's new storage metrics. ([#27](https://github.com/tddworks/asc-cli/pull/27)) ### Fixed -- **`app-availability get` on an app that was never set up** — printed a raw 404; it now returns `{"data":[]}` and a hint with the `create` command (`getAppAvailability` returns `nil`). -- **`review-submissions items list` shows what each item points at** — items never asked Apple for their relationships (`include=`), so every item showed no linked type or id, even app versions. They now show `APP_STORE_VERSION`, the new product version types and the others, with the `getVersion` affordance for app versions. +- `asc app-availability get` on an app that was never set up returns `{"data":[]}` with a hint to run `create`, instead of a raw 404. ([#28](https://github.com/tddworks/asc-cli/pull/28)) +- `asc review-submissions items list` shows what each item points at (app version, product versions and the rest) instead of an empty link. ([#27](https://github.com/tddworks/asc-cli/pull/27)) --- @@ -40,14 +41,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.18.3] - 2026-09-22 ### Added -- **Product Page Optimization tests** — `asc experiments list|get|create|update|start|stop|delete`, `asc experiment-treatments list|create|update|delete`, and `asc experiment-treatment-localizations list|create|delete` manage App Store product page A/B tests (ASC API `appStoreVersionExperiments` v2). A test is app-scoped; `create` takes `--name`, `--platform` and `--traffic-proportion 1-100`; `start`/`stop` map to `PATCH { started: true|false }`. New `AppStoreVersionExperiment` (+ `AppStoreVersionExperimentState` with `isEditable`/`isPendingReview`/`isApproved`/`isFinished`, and `isRunning`/`canStart` on the model), `ExperimentTreatment` and `ExperimentTreatmentLocalization` domain types in `Domain/Apps/Experiments/`, backed by `ExperimentRepository`. Affordances are state-aware: `createTreatment`/`update`/`delete` only while editable, `start` only when approved and unstarted, `stop` only while running. `App` gains a `listExperiments` affordance for discovery. REST equivalents under `/api/v1/apps/:appId/experiments`, `/api/v1/experiments/:id[/start|/stop|/experiment-treatments]`, `/api/v1/experiment-treatments/:id[/experiment-treatment-localizations]` and `/api/v1/experiment-treatment-localizations/:id` via `ExperimentsController`. See `docs/features/product-page-optimization.md`. +- **Product Page Optimization tests** — `asc experiments list|get|create|update|start|stop|delete`, `asc experiment-treatments list|create|update|delete`, and `asc experiment-treatment-localizations list|create|delete` manage App Store product page A/B tests (ASC API `appStoreVersionExperiments` v2). A test is app-scoped; `create` takes `--name`, `--platform` and `--traffic-proportion 1-100`; `start`/`stop` map to `PATCH { started: true|false }`. New `AppStoreVersionExperiment` (+ `AppStoreVersionExperimentState` with `isEditable`/`isPendingReview`/`isApproved`/`isFinished`, and `isRunning`/`canStart` on the model), `ExperimentTreatment` and `ExperimentTreatmentLocalization` domain types in `Domain/Apps/Experiments/`, backed by `ExperimentRepository`. Affordances are state-aware: `createTreatment`/`update`/`delete` only while editable, `start` only when approved and unstarted, `stop` only while running. `App` gains a `listExperiments` affordance for discovery. REST equivalents under `/api/v1/apps/:appId/experiments`, `/api/v1/experiments/:id[/start|/stop|/experiment-treatments]`, `/api/v1/experiment-treatments/:id[/experiment-treatment-localizations]` and `/api/v1/experiment-treatment-localizations/:id` via `ExperimentsController`. See `docs/features/product-page-optimization/README.md`. --- ## [0.18.2] - 2026-07-16 ### Added -- **`asc iris resolution-center get --submission-id [--plain-text]`** — reads App Review's Resolution Center for a review submission: the reviewer's actual rejection message text plus structured rejection reasons (guideline section/description/code). This data has no official App Store Connect API surface (Apple's OpenAPI spec contains no `resolutionCenter*`/`reviewRejection*` paths); the command composes three iris private-API calls (`resolutionCenterThreads` → `resolutionCenterMessages?include=fromActor,rejections` → `reviewRejections`) behind cookie auth, mirroring the existing iris/official split (`asc iap submit` vs `asc iris iap-submissions`). `--plain-text` converts HTML message bodies to terminal-friendly text. New `ResolutionCenterDetail`/`ResolutionCenterMessage`/`ReviewRejectionReason` domain types and `IrisResolutionCenterRepository` in `Domain/Iris/ResolutionCenter/`. Discovery is wired via CAEOAS: `ReviewSubmission` (when `hasIssues`) and `ReviewSubmissionItem` (when `isRejected`) now expose a `getResolutionDetails` affordance pointing at the iris command — the official-API commands stay zero-iris. REST equivalent: `GET /api/v1/iris/review-submissions/:id/resolution-center?plain-text=true` via `IrisResolutionCenterController`. Attachments App Review adds to messages are listed in the detail (`attachments[]` with `fileName`/`fileSize`/`downloadUrl`) and downloadable with `--out `; downloads are gated to https on Apple/CDN hosts (`ResolutionCenterAttachment.isValidDownloadURL`). See `docs/features/resolution-center.md`. +- **`asc iris resolution-center get --submission-id [--plain-text]`** — reads App Review's Resolution Center for a review submission: the reviewer's actual rejection message text plus structured rejection reasons (guideline section/description/code). This data has no official App Store Connect API surface (Apple's OpenAPI spec contains no `resolutionCenter*`/`reviewRejection*` paths); the command composes three iris private-API calls (`resolutionCenterThreads` → `resolutionCenterMessages?include=fromActor,rejections` → `reviewRejections`) behind cookie auth, mirroring the existing iris/official split (`asc iap submit` vs `asc iris iap-submissions`). `--plain-text` converts HTML message bodies to terminal-friendly text. New `ResolutionCenterDetail`/`ResolutionCenterMessage`/`ReviewRejectionReason` domain types and `IrisResolutionCenterRepository` in `Domain/Iris/ResolutionCenter/`. Discovery is wired via CAEOAS: `ReviewSubmission` (when `hasIssues`) and `ReviewSubmissionItem` (when `isRejected`) now expose a `getResolutionDetails` affordance pointing at the iris command — the official-API commands stay zero-iris. REST equivalent: `GET /api/v1/iris/review-submissions/:id/resolution-center?plain-text=true` via `IrisResolutionCenterController`. Attachments App Review adds to messages are listed in the detail (`attachments[]` with `fileName`/`fileSize`/`downloadUrl`) and downloadable with `--out `; downloads are gated to https on Apple/CDN hosts (`ResolutionCenterAttachment.isValidDownloadURL`). See `docs/features/resolution-center/README.md`. --- @@ -56,7 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **`asc sales-reports summary --from --to `** — aggregates daily Sales reports across a date range into a single rollup with derived metrics: `downloads` (first installs of phone/Mac apps, excluding Apple Watch `3F` redownloads), `updates`, `inAppPurchases`, `payers` (distinct SKUs with non-zero customer price), `customerSpend` (per `Customer Currency`, since `Customer Price` is in customer-local currency and cross-currency summing is meaningless), `proceeds` (per `Currency of Proceeds`, the developer's actual payout). CLI-only like the other report commands. - **`asc sales-reports download --version `** — exposes Apple's `filter[version]` query parameter on `/v1/salesReports`. Previously omitted by `SDKReportRepository`, so Apple always returned its default schema. Invalid values now surface Apple's helpful `PARAMETER_ERROR.INVALID` message naming the latest supported version (e.g. `1_1` for `SALES/SUMMARY/DAILY`). `ReportRepository.downloadSalesReport` gains a trailing `version: String?` parameter; pass `nil` to keep the previous default behavior. -- **`asc review-submissions get --submission-id ` and `asc review-submissions items list --submission-id [--state ]`** — drill into a review submission to find *which* attached item Apple rejected. The new `items list` subcommand surfaces per-item state (`READY_FOR_REVIEW`/`ACCEPTED`/`APPROVED`/`REJECTED`/`REMOVED`) and the linked resource id (typically the rejected `AppStoreVersion`), so an agent can navigate from an `UNRESOLVED_ISSUES` submission to the offending version with one call. `ReviewSubmission` gains `getSubmission`/`listItems` affordances on every record and a conditional `listRejectedItems` affordance when `hasIssues == true`. New `ReviewSubmissionItem` + `ReviewSubmissionItemState` + `ReviewSubmissionItemLinkedResource` domain types in `Domain/Submissions/`; `SubmissionRepository` gains `getSubmission(id:)` and `listSubmissionItems(submissionId:)`. REST equivalents: `GET /api/v1/review-submissions/:id` and `GET /api/v1/review-submissions/:id/items?state=REJECTED` via `ReviewSubmissionsController`. Note: Apple's free-text rejection reasoning still lives only in App Store Connect's Resolution Center web UI — the public API exposes the state machine, not the narrative. See `docs/features/review-submissions.md`. +- **`asc review-submissions get --submission-id ` and `asc review-submissions items list --submission-id [--state ]`** — drill into a review submission to find *which* attached item Apple rejected. The new `items list` subcommand surfaces per-item state (`READY_FOR_REVIEW`/`ACCEPTED`/`APPROVED`/`REJECTED`/`REMOVED`) and the linked resource id (typically the rejected `AppStoreVersion`), so an agent can navigate from an `UNRESOLVED_ISSUES` submission to the offending version with one call. `ReviewSubmission` gains `getSubmission`/`listItems` affordances on every record and a conditional `listRejectedItems` affordance when `hasIssues == true`. New `ReviewSubmissionItem` + `ReviewSubmissionItemState` + `ReviewSubmissionItemLinkedResource` domain types in `Domain/Submissions/`; `SubmissionRepository` gains `getSubmission(id:)` and `listSubmissionItems(submissionId:)`. REST equivalents: `GET /api/v1/review-submissions/:id` and `GET /api/v1/review-submissions/:id/items?state=REJECTED` via `ReviewSubmissionsController`. Note: Apple's free-text rejection reasoning still lives only in App Store Connect's Resolution Center web UI — the public API exposes the state machine, not the narrative. See `docs/features/review-submissions/README.md`. ### Fixed - **`asc app-availability get` no longer crashes with `PARAMETER_ERROR.INVALID` ("maximum allowable limit is '50'")** — Apple caps `include=territoryAvailabilities` on `/v1/apps/{id}/appAvailabilityV2` at 50 entries, and silently truncates relationship includes regardless. `SDKAppAvailabilityRepository.getAppAvailability` now matches the iOS-SDK multi-call pattern documented in CLAUDE.md: one call to the parent for `availableInNewTerritories` (no `include`), then a second call to the dedicated `/v2/appAvailabilities/{id}/territoryAvailabilities` endpoint with `limit: 200` for the full territory list. Added a regression test that round-trips 175 territories. @@ -68,930 +69,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **`asc versions update` now accepts `--copyright`, `--release-type`, and `--earliest-release-date`** — closes the App Store submission gap where the version's copyright line, release type (`MANUAL` / `AFTER_APPROVAL` / `SCHEDULED`), and earliest release date could only be set via the web. The underlying `VersionRepository.updateVersion(...)` already supported these fields; the CLI now wires them through. `--version` is now optional, so any subset of these fields can be patched independently (e.g. `asc versions update --version-id v-1 --copyright "© 2026 Acme"`). ---- - -## [0.17.9] - 2026-05-09 - -### Added -- **`asc builds set-encryption-compliance --build-id --uses-non-exempt-encryption ` — set Apple's export-compliance answer on a build** — closes the second TestFlight-blocking gap: when an IPA is uploaded without `ITSAppUsesNonExemptEncryption` in Info.plist, ASC marks the build "Missing Compliance" and external testing is blocked. The new command PATCHes `/v1/builds/{id}` with `usesNonExemptEncryption` so the answer can be supplied post-upload from CI scripts that don't control the Info.plist. `Build` domain model now carries `usesNonExemptEncryption: Bool?` and a `isMissingEncryptionCompliance` semantic boolean (`true` when nil); usable builds in the missing state advertise a `setEncryptionCompliance` affordance with the ready-to-run command. Build's `tableRow` gains an "Encryption" column showing `uses` / `exempt` / `missing`. REST equivalent: `PATCH /api/v1/builds/:buildId/encryption-compliance` body `{"usesNonExemptEncryption": true|false}`. New `BuildRepository.updateBuildEncryptionCompliance(buildId:usesNonExemptEncryption:)`. See `docs/features/builds-upload.md`. -- **`asc beta-app-localizations {list,get,create,update,delete}` — TestFlight Beta App Description per locale** — closes the parity gap that blocked TestFlight submissions: Apple's `BetaAppLocalizations` resource holds the per-locale beta description, tester feedback email, marketing URL, and privacy policy URL shown in TestFlight before external testing can be enabled. Distinct from the existing `asc builds update-beta-notes` (which writes per-build "What to Test" notes) and `asc beta-review` (which writes review contact info). New `BetaAppLocalization` domain model + `@Mockable BetaAppLocalizationRepository` (Domain/Apps/TestFlight/), `SDKBetaAppLocalizationRepository` (Infrastructure) backed by `/v1/apps/{id}/betaAppLocalizations` (list), `/v1/betaAppLocalizations/{id}` (get/update/delete), and `/v1/betaAppLocalizations` (create). Affordances: `delete`, `get`, `listSiblings`, `update`. REST equivalents: `GET /api/v1/apps/:appId/beta-app-localizations` and `GET /api/v1/beta-app-localizations/:id` via `BetaAppLocalizationsController`. New REST path resolver entry for `beta-app-localizations` parented under `apps`. See `docs/features/beta-app-localizations.md`. - ---- - -## [0.17.8] - 2026-04-29 - -### Added -- **`asc iris iap-submissions delete --submission-id `** — iris-side dequeue of an IAP submission (`DELETE /iris/v1/inAppPurchaseSubmissions/{id}`). Iris-queued submissions don't round-trip through the public-SDK delete; they can only be removed via this iris-cookie-authed path. New `IrisClient.delete`, new `IrisInAppPurchaseSubmissionRepository.deleteSubmission(session:submissionId:)`. REST equivalent: `DELETE /api/v1/iris/iap-submissions/:submissionId` on `IrisIAPSubmissionsController`. **`removeFromNextVersion` affordance now resolves end-to-end through iris** (CLI: `asc iris iap-submissions delete --submission-id `; REST: `DELETE /api/v1/iris/iap-submissions/`). `RESTPathResolver.resourcePath` now converts spaces in segment names to slashes so multi-word commands like `iris iap-submissions` produce clean URL segments. Removed the short-lived public-SDK alias `POST /api/v1/iap/:iapId/unsubmit` (incorrect — public DELETE doesn't accept iris-queued submissions). -- **`removeFromNextVersion` affordance on queued IAPs** — when an IAP is in `READY_TO_SUBMIT` and Apple's iris listing reports `submitWithNextAppStoreVersion: true` (queued to ride along with the next app version), `InAppPurchase.structuredAffordances` surfaces `removeFromNextVersion` → `asc iap unsubmit --submission-id ` and **suppresses** the `submit` affordance (mutually exclusive — Apple rejects re-submits on already-queued IAPs). The dequeue uses the existing public-SDK `DELETE /v1/inAppPurchaseSubmissions/{id}` (Apple's iris flow keys the submission resource by parent IAP id, so `--submission-id ` works). Underpinned by a new best-effort iris enrichment: `IrisSDKInAppPurchaseStateRepository` calls `GET /iris/v1/apps/:appId/inAppPurchasesV2?fields[inAppPurchases]=submitWithNextAppStoreVersion&filter[state]=READY_TO_SUBMIT`, mapping each IAP to its queued bit. `SDKInAppPurchaseRepository.listInAppPurchases` accepts an optional `irisFlagsProvider` closure; the factory wires it from `IrisCookieProvider` + `IrisSDKInAppPurchaseStateRepository`. CI scripts using API-key auth keep their existing JSON output (flag stays `false`, no `removeFromNextVersion` affordance). New `InAppPurchase.submitWithNextAppStoreVersion: Bool` field, encoded only when `true` to avoid snapshot churn. New `IrisInAppPurchaseStateRepository` `@Mockable` Domain protocol. - -### Changed -- **`InAppPurchase` submission affordances reorganized into iris-queue family vs. public-SDK direct submit.** Two inverse pairs, all gated on `state == .readyToSubmit`: - - `addToNextVersion` (NEW) → `asc iris iap-submissions create --iap-id ` — queues the IAP to ride along with the next App Store version submission. Always available for ready, not-yet-queued IAPs. - - `removeFromNextVersion` → `asc iap unsubmit --submission-id ` — dequeues. Mutually exclusive with the others; emitted only when iris listing reports `submitWithNextAppStoreVersion: true`. - - `submit` → `asc iap submit --iap-id ` — public-SDK direct submit (standalone review). Now emitted **only** when `isFirstTimeSubmission == false` (Apple rejects the direct path for the first IAP for an app). - - Established-app ready IAPs see **both** `submit` and `addToNextVersion`; agent picks based on release strategy. - - Replaces the previous behavior where `submit` quietly auto-dispatched between iris and public SDK paths — keys are now explicit about what each call does. New `InAppPurchaseState.hasBeenApproved` semantic boolean. New `iris iap-submissions` REST route registered with the path resolver so `addToNextVersion` `_links` resolve to `/api/v1/iris/iap//submissions`. - -### Added -- **`asc iris iap-submissions create --iap-id ` — IAP submission via the iris private API** — closes the parity gap with the public SDK's `POST /v1/inAppPurchaseSubmissions`, which has no `attributes` field and therefore can't carry `submitWithNextAppStoreVersion`. The new command posts to `POST /iris/v1/inAppPurchaseSubmissions` with that attribute set, which is required for first-time IAP submissions (Apple binds the first IAP to the next App Store version). Defaults to `--with-next-version`; pass `--no-with-next-version` to opt out. Lives under `asc iris …` because it needs iris cookies (either browser cookies or a session from `asc iris auth login`). Surfaced via `IrisStatus.affordances.submitIAP` so `asc iris status` advertises the path when authenticated. Existing `asc iap submit` stays untouched — CI scripts using key-auth keep working unchanged. See `docs/features/iap-subscriptions/submission-iris-parity.md`. -- **REST: `POST /api/v1/iris/iap/:iapId/submissions`** — body `{"submitWithNextAppStoreVersion": true}` (default true); returns the submission resource with `_links.viewIAP` pointing back at the public IAP endpoint. - -### Fixed -- **iris 2FA `verify-code` no longer 401s with a valid code** — `IdmsaAPIClient.applyHeaders` was sending `X-Apple-OAuth-Client-Id`, `X-Apple-OAuth-Redirect-URI`, `X-Apple-OAuth-Response-Mode`, `X-Apple-OAuth-Response-Type`, `Origin`, and `Referer` on `signin/init` and `signin/complete`. None of `XcodesOrg/XcodesApp`, `XcodesOrg/XcodesLoginKit`, or the `rorkai/App-Store-Connect-CLI` Go reference send those — they taint the SRP session so the post-409 `GET /appleauth/auth` returns 401, rotates `scnt`, and the subsequent `POST /verify/trusteddevice/securitycode` is rejected as out-of-sequence. Header set is now narrowed to `Accept`, `Content-Type`, `X-Requested-With`, `X-Apple-Widget-Key`, `scnt`, `X-Apple-ID-Session-Id` (+ `X-Apple-HC` on `signin/complete`). `signinComplete` also flips to `?isRememberMeEnabled=false` with body `rememberMe: false` to match the same references — `true` puts Apple in the "trust this browser" path, which mismatches a CLI session. - -### Added -- **`asc iris auth login` / `verify-code` / `logout` — Apple ID SRP login for the iris private API** — until now iris features (`asc iris apps list`, `asc iris status`) only worked when the user had logged into App Store Connect in their browser; we'd borrow cookies from there. Headless CI had no path. The new command performs a full Apple SRP-6a handshake against `idmsa.apple.com`, handles 2FA (trusted-device push or phone code), then persists the resulting session to `~/.asc/iris/session.json` (0600 perms). New `CompositeIrisCookieProvider` chains SRP-stored cookies before browser cookies, so any existing iris command immediately picks up the SRP session without code changes elsewhere. See `docs/features/iris/iris-srp-login.md`. - - `asc iris auth login --apple-id user@example.com` — prompts for password on stdin, runs SRP, persists session on success or pending-2FA state on 409. - - `asc iris auth verify-code 123456` — submits the 2FA code, calls `2sv/trust`, fetches `olympus/v1/session` for team metadata, persists the full session. - - `asc iris auth logout` — deletes the persisted session. - - `ASC_IRIS_DEBUG=1` — dumps every idmsa request/response to stderr with `a`/`m1`/`m2` redacted, so failures self-capture safely-shareable test data. - - Built on `adam-fowler/swift-srp` (Apache 2.0) for RFC 5054 group-2048 constants, BigNum, and key generation. Apple's PBKDF2-derived `x` is layered on top in `AppleSRPClient`. PBKDF2-HMAC-SHA256 implementation cross-checked against RFC 7914 §11 vectors. - - **Caveat**: while every layer is unit-tested with mocked HTTP and synthesized vectors, real-world correctness against Apple's specific `M1` shape gets validated only on the first real login attempt. If Apple's protocol shifts, expect churn here. - ---- - -## [0.17.7] - 2026-04-29 - -### Added -- **REST `POST` / `PATCH` / `DELETE` for subscription group localizations** — `SubscriptionGroupLocalizationsController` previously exposed only `GET`, so `POST /api/v1/subscription-groups/:groupId/subscription-group-localizations` (the call the AppNexus web UI emits when creating a new locale) returned 404. Now wired: - - `POST /api/v1/subscription-groups/:groupId/subscription-group-localizations` — body `{locale, name, customAppName?}`. - - `PATCH /api/v1/subscription-group-localizations/:localizationId` — body `{name?, customAppName?}`. - - `DELETE /api/v1/subscription-group-localizations/:localizationId` — returns `{"deleted": true}`. -- **`uploadReviewScreenshot` and `uploadImage` affordances on `InAppPurchase` and `Subscription`** — until now an agent looking at an IAP or subscription with no review screenshot had no `_links` path to discover the upload command. The four new affordances close that gap (CLI surface, REST `_links`, REST endpoints). -- **REST upload endpoints (raw body)** — `POST /api/v1/iap/:iapId/review-screenshot`, `POST /api/v1/iap/:iapId/images`, `POST /api/v1/subscriptions/:subscriptionId/review-screenshot`, `POST /api/v1/subscriptions/:subscriptionId/images`. Send the image bytes as the request body (Content-Type `image/png`, `image/jpeg`, etc.); body is spooled to a temp file and uploaded through the existing `uploadReviewScreenshot` / `uploadImage` repository methods. 20MB body ceiling. Same path as the `GET` collection — verb selects read vs. upload. - -### Fixed -- **`uploadImage` / `uploadReviewScreenshot` now wait for ASC processing before returning** — ASC's PATCH-commit response carries an empty `imageAsset` because processing is async; clients were getting `{templateURL: "", width: 0, height: 0}` and had no signal of when the asset URL would appear. Both `SDKInAppPurchaseReviewRepository` and `SDKSubscriptionReviewRepository` now mirror the iOS SDK pattern (`AppStoreSdk` `verifyImageUpload` / `verifyReviewScreenshotUpload`): after the commit PATCH, poll `GET /v1/inAppPurchaseImages/{id}` (or the screenshot equivalent) until `state ∈ {PREPARE_FOR_SUBMISSION, WAITING_FOR_REVIEW, APPROVED}` for images / `assetDeliveryState == COMPLETE` for screenshots. Defaults: 15 attempts × 2s = 30s ceiling; failure states (`FAILED`, `REJECTED`) throw immediately. The mapper additionally treats empty `templateURL` / zero dimensions as nil so any leftover not-ready shape is omitted from JSON via `encodeIfPresent` rather than leaking. Result: `POST /api/v1/iap/:iapId/images` and the three sibling upload endpoints return a record whose `imageAsset` is fully populated and ready to render — no client-side polling needed. - -### Changed -- **`upload` action resolves to bare collection path in REST** — `RESTPathResolver` now treats `upload` like `get/update/delete` (no extra suffix) so an upload `_links` href is `/api/v1/iap/:iapId/review-screenshot` (POST), not `/...review-screenshot/upload`. Standard REST: the verb carries the intent, the path names the resource. -- **`reviewNote` now read-side on `InAppPurchase` and `Subscription`** — the field was previously write-only via `asc iap update --review-note` / `asc subscriptions update --review-note`. List/get responses now expose `reviewNote: String?` so an agent (or web UI like AppNexus's IAP/Subscription review tabs) can read the existing value without a separate fetch. SDK mappers thread `attributes.reviewNote` from `InAppPurchaseV2` and `Subscription` SDK responses; nil values are omitted from JSON output via `encodeIfPresent`. Existing JSON snapshots stay byte-identical when no review note is set. - ---- - -## [0.17.6] - 2026-04-28 - -### Fixed -- **`PATCH /api/v1/iap/{iapId}/availability` returned 404** — the route was never registered, so the web frontend's "save territories" call failed. `IAPAvailabilityController` now serves `PATCH` with body `{territoryIds, availableInNewTerritories}` and routes through `repo.createAvailability` (ASC's `POST /v1/inAppPurchaseAvailabilities` upserts — there is no separate update endpoint). - ---- - -## [0.17.5] - 2026-04-28 - -### Added -- **Production / sandbox split for offer codes** — `InAppPurchaseOfferCode` and `SubscriptionOfferCode` now expose `productionCodeCount` and `sandboxCodeCount` (Apple returns these alongside the existing total). Visible in `asc iap-offer-codes list`, `asc subscription-offer-codes list`, and on the existing `GET /api/v1/iap/:iapId/offer-codes` and `GET /api/v1/subscriptions/:subscriptionId/offer-codes` endpoints (flow through Codable). -- **`environment` on one-time-use code batches** — `InAppPurchaseOfferCodeOneTimeUseCode` and `SubscriptionOfferCodeOneTimeUseCode` now carry `environment: OfferCodeEnvironment?` (PRODUCTION or SANDBOX). Apple separates redemption by environment; sandbox batches redeem against sandbox tester accounts and have a smaller per-quarter ceiling. -- **`--environment` flag on one-time-codes create** — `asc iap-offer-code-one-time-codes create --environment sandbox` and `asc subscription-offer-code-one-time-codes create --environment sandbox`. Default is `production`, matching prior behaviour. Maps to the SDK's `Attributes(environment:)` field on the create-request body so sandbox batches can be generated from CLI. -- **REST endpoints for one-time-codes** — `GET/POST /api/v1/iap-offer-codes/:offerCodeId/one-time-codes`, `PATCH /api/v1/iap-offer-code-one-time-codes/:oneTimeCodeId`, and the subscription mirrors. POST body accepts `{numberOfCodes, expirationDate, environment?}` so REST clients can generate sandbox redemption batches without dropping to CLI. Each batch row's `_links` resolves `listOneTimeCodes` to the nested parent path. -- **`createOfferCode` affordance + POST endpoints** — `InAppPurchase` and `Subscription` now advertise `createOfferCode` in `_links`, mirroring the iOS app's "+ New offer code" affordance. `POST /api/v1/iap/:iapId/offer-codes` accepts `{name, customerEligibilities[]}`; `POST /api/v1/subscriptions/:subscriptionId/offer-codes` accepts `{name, duration, mode, periods, customerEligibilities[], offerEligibility}`. Previously these were CLI-only. -- **Per-territory `prices` on offer-code create (IAP + Subscription)** — both CLI and REST now accept the full territory price list at creation time, matching ASC's actual contract. CLI: `--price =` (repeatable, paid) and `--free-territory ` (repeatable). REST: `prices: [{territory, pricePointId?}]` in POST body — omit `pricePointId` for free. New shared `OfferCodePriceInput` domain type. SDK adapters compose the `included[InAppPurchaseOfferPriceInlineCreate]` / `included[SubscriptionOfferCodePriceInlineCreate]` payload with `${local-price-N}` placeholder ids referenced from `relationships.prices.data` — same pattern as the existing IAP price-schedule create. **Fixes a long-standing bug**: previous `createOfferCode` calls sent an empty `prices` relationship, so every offer code created via asc-cli was missing per-territory pricing (and ASC's `prices` is read-only post-create — there was no recovery path). -- **`--auto-renew` on subscription offer-code create** — CLI flag (default `true`) and REST body field `isAutoRenewEnabled` (also accepts `autoRenew`). Setting it `false` creates a non-renewing/one-time offer; ASC only accepts `--mode FREE_TRIAL` in that case. Previously not exposed. - ---- - -## [0.1.74] - 2026-04-27 - -### Added -- **HATEOAS `_links` for IAPs and Subscriptions** — `GET /api/v1/apps/{id}/iap` and `GET /api/v1/subscription-groups/{id}/subscriptions` now embed `_links` per item so an agent can navigate to localizations, availability, offer codes, price points, review screenshots, promotional offers, win-back offers (subscriptions only), and intro offers without knowing the URL conventions. -- **REST controllers for IAP details** — `GET /api/v1/iap/:id/localizations`, `/availability`, `/offer-codes`, `/price-points`. Each returns the agent-first `data: [...]` envelope with `_links` already populated. -- **REST controllers for Subscription details** — `GET /api/v1/subscriptions/:id/localizations`, `/availability`, `/offer-codes`, `/introductory-offers`. -- **`InAppPurchasePriceSchedule` enriched with `baseTerritory` + `territoryPrices`** — `GET /api/v1/iap/:id/price-schedule` now returns the developer-set base territory plus all auto-equalized per-territory prices (currency + customerPrice + proceeds) so the iOS Pricing tab has data to render. Composes three ASC API calls under the hood: schedule (with base territory), manual prices, and equalizations. -- **IAP equalizations primitive** — `asc iap-equalizations list --price-point-id [--limit N]` and `GET /api/v1/iap-price-points/:id/equalizations`. Exposes `GET /v1/inAppPurchasePricePoints/{id}/equalizations` (~175 territory prices auto-derived from a manual base price). -- **Subscription price schedule** — new `SubscriptionPriceSchedule` domain type with `territoryPrices` and `price(for:)` lookup (no base territory — subscriptions are per-territory). `asc subscription-price-schedule get --subscription-id ` and `GET /api/v1/subscriptions/:id/price-schedule`. Composes `GET /v1/subscriptions/{id}/prices` + equalizations. -- **Subscription equalizations primitive** — `asc subscription-equalizations list --price-point-id ` and `GET /api/v1/subscription-price-points/:id/equalizations`. -- **Multi-territory subscription `setPrices` (batch)** — `asc subscriptions prices set-batch --subscription-id --price USA=spp-1 --price JPN=spp-2 ...`. Mirrors iOS `setPrices(prices:)`. Returns the post-write schedule. -- **Subscription promotional images** — `asc subscription-images list|upload|delete` and `GET /api/v1/subscriptions/:id/images`. New `SubscriptionPromotionalImage` domain type with `delete` suppression while `state.isPendingReview` (mirrors IAP images). -- **`asc subscriptions update --period `** — change billing period (ONE_WEEK, ONE_MONTH, …, ONE_YEAR) on an existing subscription. Mirrors the iOS app's `subscription.update(subscriptionPeriod:)`. -- **`setPrice` affordance + `POST /api/v1/iap/{id}/prices/set`** — IAPs now advertise how to set or change their base price/territory. Calling with a new `baseTerritory` is how the iOS app implements "Change Base Territory" — ASC replaces the schedule and re-equalizes other territories. -- **`setPrices` affordance + `POST /api/v1/subscriptions/{id}/prices/set-batch`** — subscriptions now advertise the batch multi-territory price-set operation. Body accepts `{ prices: [{territory, pricePointId, startDate?, preserveCurrentPrice?}] }` (preferred) or a flat `{ "USA": "spp-1", "JPN": "spp-2" }` shorthand. -- **Cursor pagination for IAP/Subscription price points** — `listPricePoints` now accepts `limit` + `cursor` and returns `PaginatedResponse { data, nextCursor, totalCount }`. CLI: `asc iap price-points list --iap-id X --limit 200 --cursor `. REST: `GET /api/v1/iap/{id}/price-points?territory=CHN&limit=200&cursor=` returns `{ data, nextCursor, totalCount }`. Frontends loop until `nextCursor` is absent. Mirrors iOS's `loadPricePoints(territory:limit:cursor:) → PaginatedResult`. - -### Fixed -- **`POST/PATCH /api/v1/iap/{id}/localizations` returned bare 500 on ASC validation failures + dropped over-limit content** — when the description exceeded ASC's 45-character cap (or name exceeded 30), Hummingbird's default error path returned 500 with no body, browsers showed "Failed to fetch", and the user lost the typed text. The controller now (1) trims `name` to 30 grapheme clusters and `description` to 45 before sending, matching the iOS app's `LocaleCard` `limit:` constants, and (2) catches any remaining ASC error (e.g., invalid locale) and surfaces it as `400 Bad Request` with the underlying message so the UI can render a meaningful inline error. -- **IAP/Subscription availability returned only ~10 territories instead of all ~175** — the parent endpoint's `include=availableTerritories` truncates the relationship to a single page. `getAvailability` now issues two parallel calls (attributes + dedicated `/availableTerritories?limit=200`) so the full territory list reaches the frontend's Availability tab. Matches the iOS SDK's `fetchAvailability` composition. -- **`SubscriptionGroup._links` was empty** — `GET /api/v1/apps/{id}/subscription-groups` items now embed populated `_links` for `listSubscriptions`, `listLocalizations`, `createSubscription`, `createLocalization`, `update`, `delete`. Migrated `SubscriptionGroup` from raw `affordances` to `structuredAffordances` so `apiLinks` auto-derives, and registered `_subscriptionGroupLocalizationRoutes` in `RESTPathResolver.ensureInitialized` so the nested localizations path resolves. -- **`/api/v1/subscription-groups/{id}/subscriptions` returned 404** — the `_links.listSubscriptions` URL had no controller. Added `SubscriptionsController` and wired it in `RESTRoutes.swift`. Each subscription's `_links` already advertise the full child surface (localizations, availability, price-schedule, offers, etc.). -- **Review screenshots and promotional images returned no image URL** — the responses included `id`/`fileName`/`fileSize`/`assetState` but nothing renderable. Added `imageAsset: ImageAsset?` to `SubscriptionReviewScreenshot`, `InAppPurchaseReviewScreenshot`, `SubscriptionPromotionalImage`, `InAppPurchasePromotionalImage` — populated from the SDK's `attributes.imageAsset` (templateUrl + width + height). Frontend calls `imageAsset.url(maxSize:format:)` to substitute the `{w}/{h}/{f}` placeholders and produce a real CDN URL. -- **`SubscriptionPrice._links` was empty** — migrated `SubscriptionPrice` from raw `affordances` to `structuredAffordances` so `apiLinks` auto-derives. `listPricePoints` now resolves to `/api/v1/subscriptions/{id}/price-points` over REST. -- **`POST /iap/{id}/prices/set` returned 500** — the inline manual-price payload omitted the `inAppPurchaseV2` back-reference and used a plain `"p1"` placeholder, both of which ASC rejects. The request now mirrors the iOS app shape: `${local-manual-price-1}` placeholder + `inAppPurchaseV2` relationship pointing back at the IAP. Also wrapped `IAPPricesController` in try/catch so future failures log the underlying ASC error to stderr and return a JSON error body instead of an empty 500. -- **`POST /api/v1/apps/{id}/iap` returned 404** — the route wasn't registered. `IAPController` now serves `POST` (create — accepts `referenceName`, `productId`, and `inAppPurchaseType` in either CLI-style `non-consumable` or raw enum `NON_CONSUMABLE`), `PATCH /iap/{id}` (update), and `DELETE /iap/{id}`. Mirrors `VersionsController`'s POST/PATCH conventions. -- **`POST /api/v1/apps/{id}/subscription-groups` returned 404** — same fix for subscription groups. `SubscriptionGroupsController` now serves `POST` (create — `referenceName`), `PATCH /subscription-groups/{id}` (rename), and `DELETE /subscription-groups/{id}`. -- **`GET /api/v1/iap/{id}/availability` returned 500 for newly-created IAPs** — ASC returns 404 until the developer creates the availability resource. `getAvailability(iapId:)` and `getAvailability(subscriptionId:)` now return `Optional` and tolerate the 404 by returning nil; the SDK adapter mirrors iOS's `refreshTerritoryStatuses` 404 tolerance. -- **Availability tab showed empty list on fresh IAP/subscription** — iOS shows all 175 territories preselected as the default UX; the web frontend got `data: []` and rendered nothing. `IAPAvailabilityController` and `SubscriptionAvailabilityController` now synthesize a default-all-territories availability (with `isAvailableInNewTerritories: true`) by fetching `/v1/territories` whenever no resource exists. Frontend renders the same 175-territory grid iOS does. - -### Changed -- **`RESTPathResolver` resolves singleton-under-parent `get` to nested path** — when an action is not `list`/`create`, the singularized own-id is missing, and a registered route's parent param matches one in `params`, the resolver now returns the nested `/parent/{id}/segment` path. This corrects `_links.getReviewScreenshot` for IAP and Subscription (was `/api/v1/iap-review-screenshot/{id}` → now `/api/v1/iap/{id}/review-screenshot`) and similar singletons (availability, age-rating). -- **`AgeRatingController`** now serves the canonical nested path `/api/v1/app-infos/{appInfoId}/age-rating` matching the resolver's `_links`. The flat `/api/v1/age-rating/{id}` remains for back-compat. - ---- - -## [0.17.3] - 2026-04-26 - -### Added -- **State-aware affordances on the new aggregates** — affordances no longer suggest illegal next actions: - - `PromotedPurchase` exposes `state.isLocked` (true while `WAITING_FOR_REVIEW` / `IN_REVIEW`); `update` and `delete` are suppressed while locked, so an agent following affordances won't issue a 409 against App Review. - - `InAppPurchasePromotionalImage` suppresses `delete` while `state.isPendingReview` is true. - - `InAppPurchaseReviewScreenshot.AssetState` and `SubscriptionReviewScreenshot.AssetState` gain `isComplete` / `hasFailed` semantic booleans. `delete` is offered once the asset is reachable (`uploadComplete` / `complete` / `failed`); while `awaitingUpload`, only `upload` is offered as the recovery path. -- **IAP & Subscription review screenshots + IAP promotional images** — closes the upload-heavy feature parity gap. New CLI commands implement the standard ASC reserve → upload chunks → commit-with-MD5 protocol on top of `URLSession`: - - `asc iap-review-screenshot get|upload|delete` — single review screenshot per IAP. `get` returns an empty `data: []` array when no screenshot is present, mirroring CAEOAS conventions. - - `asc iap-images list|upload|delete` — 1024×1024 promotional images for an IAP, with state semantic booleans (`isApproved`, `isPendingReview`). - - `asc subscription-review-screenshot get|upload|delete` — single review screenshot per subscription. - - New domain types: `InAppPurchaseReviewScreenshot`, `InAppPurchasePromotionalImage` (with `ImageState` enum), `SubscriptionReviewScreenshot`. New repository protocols `InAppPurchaseReviewRepository` + `SubscriptionReviewRepository`, both `@Mockable`. - - `InAppPurchase` now advertises `getReviewScreenshot` + `listImages`; `Subscription` advertises `getReviewScreenshot`. -- **Promoted purchases** — App Store product page promoted slot CRUD. New CLI commands under `asc promoted-purchases`: - - `list --app-id `, `create --app-id (--iap-id | --subscription-id ) [--visible|--hidden] [--enabled|--disabled]`, `update --promoted-id ...`, `delete --promoted-id `. - - New `PromotedPurchase` domain type carrying `appId` + either `inAppPurchaseId` or `subscriptionId` (mutually exclusive at create time, validated in the command). State enum `PromotedPurchaseState` with raw values matching ASC plus `isLocked` / `isApproved` semantic booleans. - - Backed by `GET/POST /v1/apps/{id}/promotedPurchases` and `PATCH/DELETE /v1/promotedPurchases/{id}`. -- **Win-back offers** — full CRUD with eligibility rules, priority, promotion intent, and per-territory pricing. New CLI command tree under `asc win-back-offers`: - - `list --subscription-id `, `delete --offer-id `, `update --offer-id [--priority HIGH|NORMAL] [--start-date ...] [--end-date ...] [--paid-months n] [--since-min n] [--since-max n] [--wait-months n] [--promotion-intent ...]`, `prices list --offer-id `. - - `create --subscription-id --reference-name --offer-id --duration --mode --periods --paid-months --since-min --since-max --start-date --priority HIGH|NORMAL [--end-date ...] [--wait-months n] [--promotion-intent ...] [--price USA=spp-1 --price GBR=spp-2 ...]` — bypasses the generated SDK's incomplete `WinBackOfferPriceInlineCreate` (missing relationships) by encoding the body manually with type-erased `AnyCodable`. - - Domain types: `WinBackOffer` (with `customerEligibilityTimeSinceLastSubscribed` min/max, `WinBackOfferPriority`, `WinBackOfferPromotionIntent`), `WinBackOfferPrice`, `WinBackOfferPriceInput`. `Subscription` advertises `listWinBackOffers`. -- **Subscription promotional offers** — CRUD plus per-territory inline price creation. New CLI commands under `asc subscription-promotional-offers`: - - `list --subscription-id `, `delete --offer-id `, `prices list --offer-id `. - - `create --subscription-id --name --offer-code --duration --mode --periods [--price USA=spp-1 ...]` — uses `${newPromoOfferPrice-N}` 1-based local IDs in the `included` array, matching the ASC web UI request shape. - - Domain types: `SubscriptionPromotionalOffer`, `SubscriptionPromotionalOfferPrice`, shared `PromotionalOfferPriceInput`. `Subscription` advertises `createPromotionalOffer` + `listPromotionalOffers`. -- **Subscription group localizations** — per-locale display name and Custom App Name for a subscription group. New CLI command tree under `asc subscription-group-localizations`: - - `list --group-id `, `create --group-id --locale --name [--custom-app-name ]`, `update --localization-id [--name ] [--custom-app-name ]`, and `delete --localization-id `. - - New `SubscriptionGroupLocalization` domain type ({id, groupId, locale, name?, customAppName?, state?}) with `listSiblings`/`update`/`delete` affordances. New `SubscriptionGroupLocalizationRepository` `@Mockable` protocol, SDK adapter on `/v1/subscriptionGroupLocalizations`. - - `SubscriptionGroup` now advertises `createLocalization`/`listLocalizations` so an agent navigating from a group can discover the localization tree. -- **Offer code prices + one-time code values** — completes the offer-code feature so an agent can read back per-territory pricing and download distributable redemption codes: - - `asc iap-offer-codes prices list --offer-code-id ` and `asc subscription-offer-codes prices list --offer-code-id ` — returns each price with `territory` and `pricePointId` (IAP) or `subscriptionPricePointId` (Subscription). Backed by `GET /v1/inAppPurchaseOfferCodes/{id}/prices` and `GET /v1/subscriptionOfferCodes/{id}/prices`. - - `asc iap-offer-code-one-time-codes values --one-time-code-id ` and `asc subscription-offer-code-one-time-codes values --one-time-code-id ` — fetches the raw CSV body of one-time-use redemption codes for distribution. Backed by `GET /v1/.../oneTimeUseCodes/{id}/values` (`Request`). - - Two new domain types: `InAppPurchaseOfferCodePrice { id, offerCodeId, territory?, pricePointId? }` and `SubscriptionOfferCodePrice { id, offerCodeId, territory?, subscriptionPricePointId? }`. Each advertises a `listPrices` affordance that points back at its parent offer code. - - Repository protocols extended with `listPrices(offerCodeId:)` and `fetchOneTimeUseCodeValues(oneTimeCodeId:) -> String`; SDK adapters implement them on top of the appstoreconnect-swift-sdk. -- **Subscription pricing parity** — subscriptions now match IAP for browsing tiers and committing per-territory prices. Subscriptions don't have a base territory (Apple auto-equalizes), so the model has `proceedsYear2` and `prices set` is per-territory rather than per-base. New CLI commands (mirrored under `asc subscriptions`): - - `asc subscriptions price-points list --subscription-id [--territory ]` — list `SubscriptionPricePoint`s with optional territory filter. Each result advertises `setPrice` only when a territory is attached. - - `asc subscriptions prices set --subscription-id --territory --price-point-id [--start-date YYYY-MM-DD] [--preserve-current-price]` — POST `/v1/subscriptionPrices` to commit a price. - - New domain models: `SubscriptionPricePoint` (id, subscriptionId, territory, customerPrice, proceeds, **proceedsYear2**) and `SubscriptionPrice` (id, subscriptionId). - - New `SubscriptionPriceRepository` `@Mockable` protocol with `listPricePoints` + `setPrice`; SDK adapter wires `GET /v1/subscriptions/{id}/pricePoints` and `POST /v1/subscriptionPrices`. -- **IAP & Subscription lifecycle parity** — symmetric update/delete/unsubmit across the in-app-purchase and subscription aggregates so an agent can drive the full lifecycle, not just create-then-submit. New CLI commands: - - `asc iap-localizations update --localization-id [--name ] [--description ]` and `asc iap-localizations delete --localization-id `. - - `asc subscription-localizations update --localization-id [--name ] [--description ]` and `asc subscription-localizations delete --localization-id `. - - `asc iap update --iap-id [--reference-name ] [--review-note ] [--family-sharable | --not-family-sharable]`, `asc iap delete --iap-id `, and `asc iap unsubmit --submission-id ` (the SDK lacks a generated DELETE, so it goes through a manual `Request`). - - `asc subscriptions update --subscription-id [--name ] [--family-sharable | --not-family-sharable] [--group-level ] [--review-note ]`, `asc subscriptions delete --subscription-id `, and `asc subscriptions unsubmit --submission-id ` (manual DELETE same as IAP). - - `asc subscription-groups update --group-id --reference-name ` and `asc subscription-groups delete --group-id `. - - `asc subscription-offers delete --offer-id ` to drop an introductory offer. -- Affordance updates so the new commands surface in the JSON output of every list/create/submit response: `InAppPurchase` now advertises `update`/`delete`, `Subscription` advertises `update`/`delete`, `SubscriptionGroup` advertises `update`/`delete`, both localization models advertise `update`/`delete`, both submission models advertise `unsubmit`, and `SubscriptionIntroductoryOffer` advertises `delete`. - ---- - -## [0.17.2] - 2026-04-25 - -### Added -- **Plugin update workflow (Sparkle-style)** — check for and apply plugin updates via CLI or REST: - - `asc plugins updates` (CLI) and `GET /api/v1/plugins/updates` (REST) — list every installed plugin where the marketplace has a newer version. Each entry is a `PluginUpdate { name, installedVersion, latestVersion, repositoryURL?, downloadURL? }` with affordances pointing at `asc plugins update --name X` (CLI) and `POST /api/v1/plugins/:name/update` (REST). - - `asc plugins update --name X` (CLI) and `POST /api/v1/plugins/:name/update` (REST) — uninstall the named plugin and reinstall the latest marketplace version. Returns the freshly installed `Plugin`. - - `Plugin.affordances` adds `checkUpdate → asc plugins updates` for installed plugins so frontends can wire a "Check for updates" entry without hard-coding the path. - - New `PluginRepository.listOutdated()` and `update(name:)` methods on the repository protocol; implemented in `PluginMarketRepository` by zipping `listInstalled()` with `listAvailable()` on `name`. -- **Plugins REST install/uninstall/search** — `PluginsController` now mirrors the full `asc plugins` CLI: - - `POST /api/v1/plugins` — install a plugin from the marketplace. Body: `{ "name": "Hello.plugin" }`. Returns the installed `Plugin` with `isInstalled: true`. - - `DELETE /api/v1/plugins/:name` — uninstall by name (or slug). Returns `204 No Content`. - - `GET /api/v1/plugins/market?q=` — search the marketplace. Without `q`, behaves like the existing list. With `q`, calls `PluginRepository.searchAvailable`. Same response shape as `GET /api/v1/plugins/market`. - - These endpoints back the install/uninstall/search affordances already advertised on `Plugin.affordances` — frontends following affordances no longer hit a 404. -- **Auth REST endpoints** — `asc auth` is now drivable from a web client. New `AuthController` exposes: - - `POST /api/v1/auth/accounts` (login) — body `{ keyId, issuerId, privateKeyPEM, name?, vendorNumber? }`. Saves to `~/.asc/credentials.json` and marks active. Returns the new `AuthStatus`. - - `GET /api/v1/auth/accounts` (list) — returns all saved `ConnectAccount` records. - - `GET /api/v1/auth/accounts/active` (check) — returns the active account's `AuthStatus`. - - `PATCH /api/v1/auth/accounts/active` (use) — body `{ name }` switches the active account, returns updated `AuthStatus`. - - `PATCH /api/v1/auth/accounts/:name` (update) — body `{ vendorNumber }` updates a stored account. - - `DELETE /api/v1/auth/accounts/active` and `DELETE /api/v1/auth/accounts/:name` (logout) — return `204 No Content`. - - **Security:** the controller writes API key PEMs to disk via `AuthStorage`. Run `asc web-server` bound to loopback only when this controller is enabled; the request body is sensitive. - - `AuthStatus` and `ConnectAccount` now conform to `Presentable` so REST responses share the `{"data":[…]}` shape used elsewhere. - ---- - -## [0.17.1] - 2026-04-24 - -### Added -- **`createVersion` affordance on `App`** — every app response now advertises `asc versions create --app-id ` (REST: `POST /api/v1/apps/{appId}/versions`). Frontends driven by affordances (e.g. the command center UI) can use the presence of this key to enable a "Create Version" action without hard-coding capabilities. -- **`updateVersion` affordance on editable `AppStoreVersion`** — versions in `prepareForSubmission` state expose `asc versions update --version-id ` (REST: `PATCH /api/v1/versions/{id}`). Live and pending versions omit it, giving the UI a state-aware signal for the edit dialog. -- **`asc versions update --version-id --version `** — new CLI command to update an existing App Store version's version string. Backed by new `VersionRepository.updateVersion(id:versionString:)` which maps to the ASC SDK `AppStoreVersionUpdateRequest`. -- **`POST /api/v1/apps/{appId}/versions`** and **`PATCH /api/v1/versions/{versionId}`** — REST endpoints for version create/update on `VersionsController`. Request body: `{ "versionString": "...", "platform": "IOS" }` for create, `{ "versionString": "..." }` for update. -- **`createLocalization` affordance on `AppInfo`** — every app-info response now advertises the locale-add endpoint. Unblocks the frontend's "+ Add Locale" button, which previously had no link to POST against. -- **`POST /api/v1/app-infos/{appInfoId}/localizations`** — create an `AppInfoLocalization` via REST. Body: `{ "locale": "fr-FR", "name": "..." }`. Returns the new row with its `_links` (`updateLocalization`, `delete`, `listLocalizations`). -- **`PATCH /api/v1/app-info-localizations/{localizationId}`** — update `name`, `subtitle`, `privacyPolicyUrl`, `privacyChoicesUrl`, `privacyPolicyText`. Missing keys mean "don't change"; sending an empty string clears the field. Fixes the `404` the frontend was seeing when saving per-locale name / subtitle / privacy URLs. -- **`DELETE /api/v1/app-info-localizations/{localizationId}`** — delete a locale, returns `204 No Content`. Backs the "trash" button on the localization row. -- **`App.contentRightsDeclaration` + `ContentRightsDeclaration` enum** — apps now carry the third-party-content declaration (`USES_THIRD_PARTY_CONTENT` / `DOES_NOT_USE_THIRD_PARTY_CONTENT`). Field is optional and omitted from JSON when unset. This is the ASC-accurate mapping — the declaration lives on `App`, not on `AppInfo`. -- **`asc apps update --app-id --content-rights-declaration `** + **`PATCH /api/v1/apps/{appId}`** — update content rights declaration via CLI or REST. Backed by new `AppRepository.updateContentRights(appId:declaration:)` which maps to the ASC SDK `AppUpdateRequest`. -- **`updateContentRights` affordance on `App`** — every app response advertises the new PATCH endpoint so frontends can wire the declaration switch without hard-coding the URL. -- **`PATCH /api/v1/age-rating/{declarationId}`** — update an age rating declaration via REST. Body accepts any subset of the `AgeRatingDeclarationUpdate` fields (boolean flags, `ContentIntensity` values, `kidsAgeBand`, `ageRatingOverride`, `koreaAgeRatingOverride`). Fixes the `404` the frontend was seeing when following the `update` `_link` on an `AgeRatingDeclaration` response. Matches the affordance key already advertised by `AgeRatingDeclaration.structuredAffordances`. - ---- - -## [0.17.0] - 2026-04-21 - -### Added -- **`GET /api/v1/apps?include=icon`** — enriches each app in the response with its primary build's `iconAsset` (`templateUrl`, `width`, `height`). Icon fetch is opt-in to keep the default list-apps path fast. Without `?include=icon`, behaviour is unchanged. Template URL placeholders (`{w}`, `{h}`, `{f}`) can be substituted client-side to render at any size (e.g. `120x120bb.png`). -- **Domain types `ImageAsset`, `App.iconAsset`** — new optional `iconAsset` on `App` (omitted from JSON when nil), new `ImageAsset` value type under `Domain/Shared/` with `url(maxSize:format:)` helper. Populated from SDK `Build.iconAssetToken` via `/v1/apps/{id}/appStoreVersions?include=build` -- **`AppRepository.fetchAppIcon(appId:)`** — returns `ImageAsset?` by joining the latest app version to its build. Returns `nil` when no version has an attached build -- **`GET /api/v1/apps/{appId}/app-infos`** — new REST route backed by `AppInfoRepository`. Previously the affordance on `App` advertised this path but the controller returned 404. `AppInfo` is now `Presentable` and uses `structuredAffordances`, so responses include `_links` to app-info localizations, age rating, and the enclosing app-infos list -- **`AppInfo.appStoreState` / `AppInfo.state`** — app-infos responses now include lifecycle fields (`appStoreState` uses legacy ASC version states like `READY_FOR_SALE`; `state` uses the newer `AppInfo.State` enum like `READY_FOR_DISTRIBUTION`, `PREPARE_FOR_SUBMISSION`). Agents can use these to pick the live-version app-info vs the version-being-prepared. Computed booleans `isLive` and `isEditable` expose the common decisions -- **`AppInfo.appStoreAgeRating`** — computed App Store age rating (e.g. `FOUR_PLUS`, `NINE_PLUS`, `TWELVE_PLUS`, `SEVENTEEN_PLUS`) now surfaced on app-infos responses. This is what the App Store listing page displays; cheaper than fetching the full `ageRatingDeclaration` when only the label is needed -- **`asc app-categories get --category-id `** + **`GET /api/v1/app-categories/{id}`** — fetch a single App Store category by ID. `AppInfo` responses now include `getPrimaryCategory`, `getSecondaryCategory`, and four subcategory affordances (conditional on the respective ID being set), each pointing at the new endpoint. Backed by new `AppCategoryRepository.getCategory(id:)` -- **`GET /api/v1/app-categories`** — list categories exposed at the REST layer (previously CLI-only). Optional `?platform=` query param mirrors the CLI flag -- **`GET /api/v1/app-infos/{appInfoId}/localizations`** — new REST route backed by `AppInfoRepository.listLocalizations`. `AppInfoLocalization` is now `Presentable` with `structuredAffordances` including update/delete links -- **Unified REST path resolver** — removed the global `{param → segment}` alias table from `RESTPathResolver`. New single rule: for resource actions (get/update/delete/submit/…), the command name IS the REST segment. CLI flag aliases (e.g. `--localization-id`, `--product-id`) are presentation concerns and no longer drive REST routing. Fixes ambiguity where the same CLI flag belonged to multiple resources -- **`GET /api/v1/age-rating/{appInfoId}`** — fetch the age rating declaration for an app-info. Previously only `asc age-rating get` worked; the REST route was advertised via `_links.getAgeRating` but returned 404. `AgeRatingDeclaration` is now `Presentable` with `structuredAffordances` -- **`PATCH /api/v1/app-infos/{appInfoId}`** — update app-info categories (primary, secondary, up to two subcategories each) via REST. JSON body accepts `primaryCategoryId`, `primarySubcategoryOneId`, `primarySubcategoryTwoId`, `secondaryCategoryId`, `secondarySubcategoryOneId`, `secondarySubcategoryTwoId` — any subset. Returns the updated `AppInfo` with `_links` that now include `getPrimaryCategory` / `getSecondaryCategory` pointing at the new values -- **`GET /api/v1/version-localizations/{localizationId}/screenshot-sets`** and **`GET /api/v1/screenshot-sets/{setId}/screenshots`** — screenshot-related `_links` on `AppStoreVersionLocalization` and `AppScreenshotSet` responses now resolve to working REST endpoints. `AppStoreVersionLocalization`, `AppScreenshotSet`, and `AppScreenshot` now use `structuredAffordances` (replacing raw `affordances`); the latter two also gained `Presentable` conformance - -### Changed -- **REST controllers split by resource (SRP/OCP refactor)** — the former `AppsController` was a god controller accumulating 15+ routes across 8 unrelated resources (apps, versions, localizations, screenshot-sets, screenshots, builds, testflight, reviews, iap, subscription-groups, app-infos, app-info-localizations, app-categories, age-rating). Now split into 13 focused controllers, one per REST resource type — `AppsController`, `VersionsController`, `VersionLocalizationsController`, `ScreenshotSetsController`, `ScreenshotsController`, `BuildsController`, `TestFlightController`, `CustomerReviewsController`, `IAPController`, `SubscriptionGroupsController`, `AppInfosController`, `AppCategoriesController`, `AgeRatingController`. Each takes only the 1–2 repositories it actually needs. `RESTRoutes.configure` composes them with one line per resource. Adding a new resource no longer requires editing an existing controller - -### Fixed -- **`RESTPathResolver.ensureInitialized` race** — the `initialized` flag was being set *before* the domain route registrations ran, so concurrent callers could observe `initialized == true` while the `routes` dictionary was still empty. Now uses a separate `initLock` and sets the flag only after all `_*Routes` registrations complete. Fixes intermittent test failures where screenshot/localization `_links` resolved to top-level paths under parallel test execution -- **`include=primaryCategory,…` on app-infos request** — added to `SDKAppInfoRepository.listAppInfos` alongside `fields[appInfos]`. The ASC API returns only relationship `links` with `fields[]` alone; `include=` is required to populate `relationships.primaryCategory.data.id` so the mapper can surface `primaryCategoryId` etc. - -### Fixed -- **`AppInfo.primaryCategoryId` / secondary categories missing from `/v1/apps/{id}/appInfos`** — the ASC API returns relationship `data` sparsely unless the client requests the explicit sparse fieldset. `SDKAppInfoRepository.listAppInfos` now passes `fields[appInfos]=primaryCategory,primarySubcategoryOne,primarySubcategoryTwo,secondaryCategory,secondarySubcategoryOne,secondarySubcategoryTwo`, so the mapped `AppInfo` carries the category IDs - ---- - -## [0.16.9] - 2026-04-20 - -### Added -- **`asc review-submissions list`** — list App Store review submissions for an app. Required `--app-id`; optional `--state ` (e.g. `WAITING_FOR_REVIEW,IN_REVIEW,READY_FOR_REVIEW` or `UNRESOLVED_ISSUES`) and `--limit`. Backed by `SubmissionRepository.listSubmissions(appId:states:limit:)` -- **`asc certificates list` filtering flags** — `--limit` (server-side), `--expired-only` (client-side, drops unexpired certs), `--before ` (client-side, keeps certs with expirationDate strictly before the cutoff) -- **REST endpoint `GET /api/v1/apps/{appId}/review-submissions`** — new `ReviewSubmissionsController` in the `asc web-server`. Supports `?state=…&limit=…` query params; mirrors the CLI. **No fleet route** — Apple's OpenAPI spec marks `filter[app]` as required, so review submissions can only be listed per-app -- **REST endpoint `GET /api/v1/certificates` query params** — now honours `?type=&limit=&expired-only=&before=` (previously ignored). `before` accepts both full ISO8601 (`2026-11-01T00:00:00Z`) and date-only (`2026-11-01`, interpreted as midnight UTC). Same filter semantics as the CLI -- **REST endpoint `GET /api/v1/builds`** — fleet listing with optional `?app-id=&platform=&version=&limit=` query params. `/api/v1/apps/{appId}/builds` still works for the nested form - -### Changed -- **`CertificateRepository.listCertificates`** signature now takes `(certificateType:limit:)` — forwards `limit` to the SDK -- **`ReviewSubmission` REST links** — affordance migrated from raw `affordances` dictionary to `structuredAffordances`, so REST responses now render `_links` correctly (e.g. `/api/v1/apps/{appId}/versions`) - ---- - -## [0.1.68] - 2026-04-14 - -### Added -- **`asc testflight groups create`** — create external or internal TestFlight beta groups (`--internal` flag toggles internal; `--public-link-enabled` / `--feedback-enabled` for external groups) -- **`asc init` review contact flags** — `--contact-first-name`, `--contact-last-name`, `--contact-phone`, `--contact-email` save review contact info to `.asc/project.json` for reuse across versions -- **`ProjectConfig` review contact fields** — optional `contactFirstName`, `contactLastName`, `contactPhone`, `contactEmail` with `hasReviewContact` computed property -- **`setReviewContact` / `updateReviewContact` affordances** — `ProjectConfig` now suggests setting review contact when missing - ---- - -## [0.1.67] - 2026-04-13 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.66] - 2026-04-09 - -### Added -- **`ThemeDesign` domain model** — composes from Gallery-native types (`GalleryPalette` + `[Decoration]`) for structured theme output. Generated by AI once, applied deterministically to all screenshots -- **`ThemeDesignApplier`** — re-renders through `GalleryHTMLRenderer.renderScreen()` pipeline with overridden palette and merged decorations (no HTML patching) -- **`GalleryPalette.textColor`** — optional explicit text color, overrides the auto-detect heuristic -- **`Decoration.label()` shape** — text/emoji decorative elements (e.g. `Decoration(shape: .label("✨"), ...)`) -- **`DecorationAnimation`** — float, drift, pulse, spin, twinkle animations for decorations -- **`ScreenLayout.withDecorations()`** — creates a copy with additional decorations -- **`GalleryHTMLRenderer.renderDecorations()`** — renders `ScreenLayout.decorations` (previously dead code) using `cqi` units -- **`buildDesignContext()` on `ScreenTheme`** — prompt method that instructs AI to return `ThemeDesign` JSON -- **`design()` on `ThemeProvider`/`ThemeRepository`** — generate a ThemeDesign from AI in one call -- **`--design-only` / `--apply-design` CLI flags** — batch theme workflow -- **REST endpoints** — `POST /app-shots/themes/design` and `POST /app-shots/themes/apply-design` - -### Changed -- **`GalleryHTMLRenderer` refactored to Mustache templates** — all HTML extracted from Swift into 7 `.mustache` template files using [swift-mustache](https://github.com/hummingbird-project/swift-mustache). The renderer only builds context dictionaries; all HTML, CSS colors (via CSS custom properties in `theme-vars.mustache`), and keyframe animations live in templates. Templates are pre-compiled at startup via `MustacheLibrary` for performance. Preview rendering is cached per template ID. -- **`DecorationShape.displayCharacter`** — computed property on the model instead of renderer logic -- **`GalleryPalette.isLight` + `headlineColor`** — theme detection and text color derivation moved from renderer to palette -- **`Decoration` extended** — new optional fields: `color`, `background`, `borderRadius`, `animation` -- **Theme selection no longer requires auto-compose** — clicking a theme applies immediately to slides with existing preview HTML via `ThemeDesign` (1 AI call for design, then deterministic apply to all slides) -- **Blitz plugin: `design()` implemented** — generates `ThemeDesign` via compose bridge `mode: "design"`, enabling the fast design→apply-design flow - ---- - -## [0.1.65] - 2026-04-06 - -### Changed -- **`Presentable` protocol** — domain models own their table headers and row values; eliminates `headers:`/`rowMapper:` boilerplate from 37 list commands -- **REST controllers with DI** — routes rewritten as controller structs (`AppsController`, `CodeSigningController`, etc.) with injected repo dependencies; repos created once at server startup, not per request -- **No more `Command.parse([])` in REST layer** — controllers call domain repos and operations directly; CLI and REST are equal thin adapters -- **`AffordanceRegistry` uses structured `Affordance`** — plugin affordances now render to both CLI commands and REST `_links` (previously CLI-only) -- **`ScreenshotTemplate` domain operations** — `apply(content:)`, `renderFragment(content:)` as rich domain methods; `ThemedPage` value type for page wrapping -- **`ScreenshotTemplate` Codable includes `previewHTML` and `deviceCount`** — REST consumers get preview data without special handling - ---- - -## [0.1.64] - 2026-04-05 - -### Added -- **REST API with HATEOAS** — `GET /api/v1` entry point plus 12 resource endpoints (apps, versions, builds, testflight, certificates, bundle-ids, devices, profiles, simulators, plugins, territories) calling domain repositories directly (in-process, no subprocess), returning JSON with `_links` for agent navigation -- **`APIRoot` model** — HATEOAS entry point at `GET /api/v1` listing all available top-level resources with navigable `_links` -- **Structured `Affordance` type** — single source of truth for both CLI commands and REST links; models define affordances once, rendered to either format by `OutputFormatter` -- **`APILink` and `AffordanceMode`** — domain types supporting dual-mode affordance rendering (`.cli` → `"affordances"`, `.rest` → `"_links"`) -- **`RESTPathResolver`** — resolves CLI command + params into REST API paths using a route table covering 35+ resource types across the full domain hierarchy -- **HTML-to-PNG export** — `--preview image` option on `asc app-shots templates apply` and `asc app-shots themes apply` renders composed HTML to PNG via WebKit, with `--image-output` for custom output path -- **`HTMLRenderer` protocol** — `@Mockable` domain protocol for HTML-to-image rendering, implemented by `WebKitHTMLRenderer` using WKWebView snapshot - ---- - -## [0.1.63] - 2026-04-02 - -### Added -- **Plugin Marketplace** — `asc plugins market list` and `asc plugins market search --query X` to browse and search plugins from [tddworks/asc-registry](https://github.com/tddworks/asc-registry) -- **Multi-source plugin registry** — `PluginSource` protocol with `GitHubPluginSource` implementation; composable sources in `PluginMarketRepository` -- **`asc plugins install --name X`** — download and install `.plugin` bundles from the marketplace -- **`asc plugins uninstall --name X`** — remove installed plugin bundles (matches by slug or registry ID) -- **Plugins page in Command Center** — web UI with Installed and Marketplace tabs, stats bar, install/uninstall with loading spinners -- **Enriched plugin manifest** — `manifest.json` now supports `description`, `author`, `repositoryURL`, `categories`; installed plugins display the same rich info as marketplace listings -- **Centralized plugin affordance merging** — `AffordanceRegistry` affordances merged automatically in `OutputFormatter` via `WithPluginAffordances`; domain models no longer need manual registry calls -- **Example plugin** — `examples/hello-plugin/` demonstrates `AffordanceRegistry`, server routes, and UI affordance handlers -- **Plugin registry repo** — [tddworks/asc-registry](https://github.com/tddworks/asc-registry) with `registry.json` and release assets for ASC Pro + Hello Plugin - -### Changed -- **Unified `Plugin` model** — merged `Plugin` and `MarketPlugin` into a single model with `isInstalled`, `downloadURL?`, `slug?` fields -- **Refactored plugin system** — replaced event-based script plugins with dylib `.plugin` bundle management -- **Removed event plugin system** — deleted `PluginEvent`, `PluginEventPayload`, `PluginResult`, `PluginRunner`, `PluginEventBus` and all infrastructure -- **Removed event bus from commands** — `BuildsUpload` and `VersionsSubmit` no longer emit plugin events -- **Apps page affordance buttons** — plugin affordances (e.g. `greet` from Hello Plugin) render as clickable buttons via `appAffordanceHandlers` registry - ---- - -## [0.1.62] - 2026-04-02 - -### Added -- **`asc web-server`** — Swift/Hummingbird HTTP+WebSocket server replacing Node.js. Single binary, zero external dependencies, HTTPS with self-signed cert -- **Plugin architecture** — `.plugin` bundles in `~/.asc/plugins/` extend the CLI with routes, commands, affordances, and UI. Dylibs built with `dynamic_lookup` (~300KB) -- **`AffordanceRegistry`** — plugins extend domain model affordances at runtime (e.g. pro plugin adds `stream` to booted simulators) -- **Affordance-driven UI** — web app renders buttons from CAEOAS affordances, not hardcoded features. Plugins register handlers via `window.simAffordanceHandlers` -- **Command Center Simulators page** — device list with search/filter, stats cards, affordance-driven actions - ---- - -## [0.1.61] - 2026-03-31 - -### Added -- **Screenshot image URLs** — `asc screenshots list` now returns `imageUrl` for each screenshot, so you can view and render real App Store images directly from CLI output -- **Screenshot platform filtering** — filter and browse screenshots by platform (iPhone, iPad, etc.) in the web UI -- **Simulator screenshot capture** — capture and browse simulator screenshots in the web gallery - ---- - -## [0.1.60] - 2026-03-31 - -### Added -- **`asc simulators list [--booted]`** — list available iOS simulators with state-aware affordances -- **`asc simulators boot --udid `** — boot a simulator -- **`asc simulators shutdown --udid `** — shut down a simulator -- **`asc simulators list/boot/shutdown`** — manage local iOS simulators with state-aware affordances - ---- - -## [0.1.59] - 2026-03-30 - -### Added -- **`asc builds list --platform`** — filter builds by platform (ios, macos, tvos, visionos) -- **`asc builds list --version`** — filter builds by marketing version (e.g. 1.0.0) -- **`asc builds next-number`** — get the next build number for a version/platform, ideal for CI/CD automation (resolves #13) -- **Build platform field** — builds now include platform from preReleaseVersion -- **Build number populated** — `buildNumber` field now maps from `Build.attributes.version` (was always nil) - -### Fixed -- **Global options (`--pretty`, `--output`, `--timeout`) now accepted by all commands** — previously 10 leaf commands (`builds add-beta-group`, `builds remove-beta-group`, `builds uploads delete`, `versions set-build`, `reviews responses delete`, `app-clip-experiences delete`, `app-clip-experience-localizations delete`, `analytics-reports delete`, `auth logout`, `auth use`) rejected these flags with "Unknown option" - -### Changed -- **`asc builds list`** — table output now includes Build Number and Platform columns -- **Build version semantics** — `version` field now holds the marketing version (from PreReleaseVersion) when available; `buildNumber` holds the build string -- **Builds sorted by newest first** — list results default to `-uploadedDate` sort order - ---- - -## [0.1.58] - 2026-03-23 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.57] - 2026-03-23 - -### Changed -- **`asc web-server`** — `/command-center/`, `/console/`, and `/` now redirect (302) to the hosted apps at `asccli.app/command-center` and `asccli.app/console` instead of serving local files that no longer exist; startup banner updated to show hosted URLs - ---- - -## [0.1.56] - 2026-03-21 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.55] - 2026-03-21 - -### Added -- **Iris private API support** — new `asc iris` command namespace for cookie-based App Store Connect private API access -- **`asc iris status`** — check iris cookie session availability (browser or environment source) -- **`asc iris apps list`** — list apps via iris `/v1/appBundles` endpoint -- **`asc iris apps create`** — create a new app with initial version, supports multi-platform (`--platforms IOS MAC_OS`) -- **Cookie-based authentication** — auto-extracts `myacinfo` and session cookies from Chrome/Safari/Firefox via SweetCookieKit, with `ASC_IRIS_COOKIES` env var fallback for CI/CD -- **SweetCookieKit dependency** — browser cookie extraction for iris authentication - ---- - -## [0.1.53] - 2026-03-18 - -### Changed -- **`asc web` → `asc web-server`** — renamed command to reflect its role as a local API proxy, no longer serves static files -- **Removed Hummingbird dependency** — web server replaced with embedded Node.js proxy (`apps/server.js`), reducing binary size and build dependencies -- **Unified web apps under `apps/`** — moved dashboard from `Sources/ASCCommand/Resources/web/` to `apps/asc-web-command-center/`, console from `homepage/asc-web-console/` to `apps/asc-web-console/` -- **Dual-mode API detection** — both web apps auto-detect local proxy (tries relative `/api/run`, then `localhost:8420`, then falls back to mock mode) - -### Added -- **SPM build plugin (`Plugins/EmbedServerJS/`)** — auto-generates Swift source from `apps/server.js` at build time, embedding the API proxy in the binary with `apps/server.js` as single source of truth -- **Command Center nav link on homepage** — added "Command Center" navigation to homepage template with translations in all 12 languages -- **GitHub Pages deployment for web apps** — deploy workflow now copies `apps/asc-web-*` to homepage for static hosting - -### Removed -- `WebServer.swift` (Hummingbird-based static file server) -- `homepage/asc-web-console/server.py` (Python dev server) -- Hummingbird package dependency - ---- - -## [0.1.52] - 2026-03-18 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.51] - 2026-03-17 - -### Added -- `asc web` management dashboard — complete redesign with professional light/dark theme, Plus Jakarta Sans typography, and self-contained CSS (no Tailwind CDN dependency) -- App context selector in sidebar — select an app from the Apps page and all other sections (Versions, Builds, TestFlight, Reviews, IAP, Subscriptions) use that app context automatically -- Rich domain models in web UI — every entity carries parent IDs, semantic booleans (`isLive`, `isEditable`, `isPending`, `isUsable`), and state-aware CAEOAS affordances matching the Swift Domain layer exactly -- Mock/CLI dual data mode — web UI auto-detects the `asc web` backend; falls back to built-in mock data for offline development and demos; toggle via header button -- Dark mode with system preference detection and localStorage persistence -- 15 management pages: Command Center, Apps, Versions, Builds, TestFlight, Submissions, App Info, Screenshots, Reviews, In-App Purchases, Subscriptions, Reports, Code Signing, Xcode Cloud, Users & Roles -- Command Center with release pipeline timeline, quick actions, and stats overview -- Command log modal showing every CLI command executed by the UI -- Standalone development server (`apps/asc-web-command-center/server.js`) for working on the web UI outside of `asc web` - ---- - -## [0.1.50] - 2026-03-15 - -### Added -- `asc territories list` — list all available App Store territories with currency codes -- `asc iap-availability get --iap-id ` — get territory availability for an in-app purchase (includes currency per territory) -- `asc iap-availability create --iap-id --available-in-new-territories --territory USA --territory CHN` — create territory availability for an IAP -- `asc subscription-availability get --subscription-id ` — get territory availability for a subscription (includes currency per territory) -- `asc subscription-availability create --subscription-id --available-in-new-territories --territory USA` — create territory availability for a subscription -- `Territory` domain model with id and currency, shared across availability responses -- `getAvailability` affordance on `InAppPurchase` and `Subscription` models for CAEOAS navigation -- `listTerritories` affordance on availability models for territory discovery -- `asc app-availability get --app-id ` — get per-territory availability with `isAvailable`, `contentStatuses` (blocking reasons), `releaseDate`, and `isPreOrderEnabled` for every territory - ---- - -## [0.1.49] - 2026-03-14 - -### Added -- `asc skills list` — list available skills from the asc-cli repository via `npx skills add tddworks/asc-cli --list` -- `asc skills install --name ` — install a specific skill; `--all` to install all available skills -- `asc skills installed` — show skills installed in `~/.claude/skills/` with CAEOAS affordances -- `asc skills uninstall --name ` — remove an installed skill from `~/.claude/skills/` -- `asc skills check` — check for available skill updates via `npx skills check` -- `asc skills update` — update installed skills via `npx skills update` -- Auto-update checker — non-blocking skill update check on every `asc` command (24h cooldown, CI-aware, disable with `ASC_SKIP_SKILL_CHECK=true`) -- `--app-id` flag on `asc app-wall submit` — accepts App Store Connect app IDs directly, auto-constructs App Store URLs (repeatable, combinable with `--app` and `--developer-id`) - ---- - -## [0.1.48] - 2026-03-13 - -### Changed -- `--developer` on `asc app-wall submit` is now optional — when omitted, the homepage card uses the iTunes artist name instead -- `asc beta-review submissions list --build-id ` — list beta app review submissions for a build -- `asc beta-review submissions create --build-id ` — submit a build for beta (TestFlight external) review -- `asc beta-review submissions get --submission-id ` — get a specific beta review submission -- `asc beta-review detail get --app-id ` — get beta review contact info and demo account details -- `asc beta-review detail update --detail-id ` — update beta review contact info, demo account, and notes - ---- - -## [0.1.47] - 2026-03-13 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.46] - 2026-03-13 - -### Added -- `asc builds archive --scheme ` — archive and export Xcode projects locally via `xcodebuild`, auto-detecting workspace/project from the current directory; supports `--platform`, `--configuration`, `--export-method` (app-store/ad-hoc/development/enterprise), and `--output-dir` -- `--upload` flag on `asc builds archive` chains the exported IPA/PKG directly into App Store Connect upload, combining archive + export + upload in a single command - ---- - -## [0.1.45] - 2026-03-12 - -### Changed -- Subscription offer codes: `asc subscription-offer-codes list/create/update`, `asc subscription-offer-code-custom-codes list/create/update`, `asc subscription-offer-code-one-time-codes list/create/update` — manage offer codes, custom redeemable codes, and one-time use code batches for auto-renewable subscriptions -- IAP offer codes: `asc iap-offer-codes list/create/update`, `asc iap-offer-code-custom-codes list/create/update`, `asc iap-offer-code-one-time-codes list/create/update` — manage offer codes, custom redeemable codes, and one-time use code batches for in-app purchases -- `InAppPurchase` and `Subscription` affordances now include `listOfferCodes` for direct navigation to offer codes - ---- - -## [0.1.44] - 2026-03-12 - -### Added -- Power & performance metrics: `asc perf-metrics list --app-id ` and `asc perf-metrics list --build-id ` — download launch time, hang rate, memory, disk, battery, termination, and animation metrics with `--metric-type` filter -- Diagnostic signatures: `asc diagnostics list --build-id ` — list hang, disk write, and launch diagnostic hotspots with `--diagnostic-type` filter -- Diagnostic logs: `asc diagnostic-logs list --signature-id ` — view call stack metadata for a specific diagnostic signature - ---- - -## [0.1.43] - 2026-03-11 - -### Added -- HTML screenshot generation: `asc app-shots html --plan composition-plan.json` — deterministic App Store screenshot generation with real device mockup frames, no AI or API keys needed -- CompositionPlan format: normalized 0-1 coordinates, multiple devices per slide, text overlays with alignment, gradient backgrounds, per-screen color themes -- Device mockup system: bundled iPhone 17 Pro Max frame with `mockups.json` config; users can add custom mockups to `~/.asc/mockups/` -- TextOverlay `textAlign` property: supports `left`, `center`, `right` alignment for text positioning in composition plans -- Client-side PNG export via html-to-image CDN in generated HTML pages - ---- - -## [0.1.42] - 2026-03-11 - -### Added -- Customer reviews: `asc reviews list --app-id ` and `asc reviews get --review-id ` — list and view customer reviews with rating, title, body, reviewer nickname, and territory -- Review responses: `asc review-responses create --review-id --response-body "text"`, `asc review-responses get --review-id `, `asc review-responses delete --response-id ` — manage developer responses to customer reviews - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.41] - 2026-03-10 - -### Added -- Sales reports: `asc sales-reports download` with support for 10 report types, 5 sub-types, and 4 frequencies -- Finance reports: `asc finance-reports download` with financial and finance detail report types -- Gzip decompression and TSV parsing infrastructure for App Store Connect report downloads -- Analytics reports: `asc analytics-reports request/list/delete/reports/instances/segments` — multi-step analytics workflow with 5 report categories and 3 granularity levels -- `asc auth login --vendor-number ` — save vendor number with account credentials -- `asc auth update --vendor-number ` — add or update vendor number on an existing account -- Vendor number auto-resolution for `sales-reports download` and `finance-reports download` — `--vendor-number` is now optional when saved on the active account - ---- - -## [0.1.40] - 2026-03-09 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.39] - 2026-03-08 - -### Added -- `asc app-clips list --app-id ` — list App Clips for an app; `AppClip` carries `appId` (injected), `bundleId?`; affordances: `listAppClips`, `listExperiences` -- `asc app-clip-experiences list --app-clip-id ` — list default experiences for an App Clip; `AppClipDefaultExperience` carries `appClipId` (injected), `action?` (OPEN/VIEW/PLAY); affordances: `delete`, `listExperiences`, `listLocalizations` -- `asc app-clip-experiences create --app-clip-id [--action OPEN|VIEW|PLAY]` — create a default experience -- `asc app-clip-experiences delete --experience-id ` — delete a default experience -- `asc app-clip-experience-localizations list --experience-id ` — list localizations; `AppClipDefaultExperienceLocalization` carries `experienceId` (injected), `locale`, `subtitle?`; affordances: `delete`, `listLocalizations` -- `asc app-clip-experience-localizations create --experience-id --locale [--subtitle "..."]` — create a localization -- `asc app-clip-experience-localizations delete --localization-id ` — delete a localization -- `AppClipRepository` `@Mockable` protocol with 7 methods covering all CRUD operations -- `asc game-center detail get --app-id ` — get Game Center configuration for an app; `GameCenterDetail` carries `appId` (injected), `isArcadeEnabled`; affordances: `getDetail`, `listAchievements`, `listLeaderboards` -- `asc game-center achievements list --detail-id ` — list Game Center achievements; `GameCenterAchievement` carries `gameCenterDetailId` (injected), `referenceName`, `vendorIdentifier`, `points`, `isShowBeforeEarned`, `isRepeatable`, `isArchived`; affordances: `listAchievements`, `delete` -- `asc game-center achievements create --detail-id --reference-name --vendor-identifier --points [--show-before-earned] [--repeatable]` — create a new achievement -- `asc game-center achievements delete --achievement-id ` — delete an achievement -- `asc game-center leaderboards list --detail-id ` — list Game Center leaderboards; `GameCenterLeaderboard` carries `gameCenterDetailId` (injected), `referenceName`, `vendorIdentifier`, `scoreSortType` (ASC/DESC), `submissionType` (BEST_SCORE/MOST_RECENT_SCORE), `isArchived`; affordances: `listLeaderboards`, `delete` -- `asc game-center leaderboards create --detail-id --reference-name --vendor-identifier --score-sort-type ASC|DESC [--submission-type BEST_SCORE|MOST_RECENT_SCORE]` — create a new leaderboard -- `asc game-center leaderboards delete --leaderboard-id ` — delete a leaderboard -- `ScoreSortType` and `LeaderboardSubmissionType` domain enums -- `GameCenterRepository` `@Mockable` protocol with 7 methods covering all CRUD operations - ---- - -## [0.1.38] - 2026-03-07 - -### Added -- `asc xcode-cloud products list [--app-id ]` — list Xcode Cloud products; `XcodeCloudProduct` carries `appId` (injected from relationship), `name`, `productType`; affordances: `listWorkflows`, `listProducts` -- `asc xcode-cloud workflows list --product-id ` — list CI workflows for a product; `XcodeCloudWorkflow` carries `productId` (injected), `name`, `description`, `isEnabled`, `isLockedForEditing`; affordances: `listBuildRuns`, `listWorkflows` always, `startBuild` only when `isEnabled` -- `asc xcode-cloud builds list --workflow-id ` — list build runs for a workflow; `XcodeCloudBuildRun` carries `workflowId` (injected), `number`, `executionProgress` (PENDING/RUNNING/COMPLETE), `completionStatus` (SUCCEEDED/FAILED/ERRORED/CANCELED/SKIPPED), `startReason`; semantic booleans: `isPending`, `isRunning`, `isComplete`, `isSucceeded`, `hasFailed` -- `asc xcode-cloud builds get --build-run-id ` — get a specific build run by ID -- `asc xcode-cloud builds start --workflow-id [--clean]` — start a new build run; `--clean` flag performs a clean build removing derived data - ---- - -## [0.1.37] - 2026-03-06 - -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.36] - 2026-03-05 - -### Added -- `asc users list/update/remove` — manage App Store Connect team members; list users filtered by role, update a member's roles (replaces all current roles), or revoke access immediately; `TeamMember` carries `username`, `firstName`, `lastName`, `roles`, `isAllAppsVisible`, `isProvisioningAllowed`; affordances: `remove`, `updateRoles` (pre-filled with current roles) -- `asc user-invitations list/invite/cancel` — manage pending team invitations; invite a new member with one or more roles (`--role DEVELOPER --role APP_MANAGER`); `UserInvitationRecord` carries `email`, `roles`, `expirationDate`; affordance: `cancel`; supports 13 roles; `--all-apps-visible` flag on invite grants access to all apps -- `asc auth list` — list all saved App Store Connect accounts with active status and per-account affordances (`use`, `logout`) -- `asc auth use ` — switch the active App Store Connect account; all subsequent commands use the newly active account -- `asc auth login --name ` — optional `--name` flag to save credentials under a human-readable alias (defaults to `"default"`); first saved account becomes active automatically; subsequent logins with new names are saved alongside existing accounts; account names must not contain spaces (use hyphens or underscores) - -### Changed -- `asc auth logout` — now accepts optional `--name ` flag; removes the named account or the active account if `--name` is omitted -- `asc auth check` — now shows the account `name` field when credentials come from a saved account (omitted for environment-variable credentials) -- `~/.asc/credentials.json` — upgraded to multi-account format `{ "active": "name", "accounts": { ... } }`; old single-credential files are auto-migrated to a `"default"` named account on first use - ---- - -## [0.1.35] - 2026-03-04 - -### Added -- `asc app-wall submit` — submit your app to the community app wall at asccli.app by opening a GitHub pull request against `tddworks/asc-cli`; forks the repo, adds your entry to `homepage/apps.json`, and creates a PR automatically; supports `--developer-id` (auto-fetches all your App Store apps), `--app` (specific App Store URLs, repeatable), `--github`, `--x`; GitHub token resolved from `--github-token` flag, `$GITHUB_TOKEN`, or `gh auth token` - ---- - -## [0.1.34] - 2026-03-02 - -### Fixed -- `asc app-shots translate` — no longer regenerates the visual design (background, colors, device mockup, layout) from the original plan specs; now sends a simple "edit this image, translate only the text overlays" prompt so the existing generated screenshot is preserved exactly - ---- - -## [0.1.33] - 2026-03-02 - -### Added -- `asc init` — initialise project context by saving the app ID, name, and bundle ID to `.asc/project.json` in the current directory; supports `--app-id` (direct), `--name` (search by name), or auto-detect from `.xcodeproj` bundle identifier; output includes CAEOAS affordances for common next steps - ---- - -## [0.1.32] - 2026-03-01 +## Older releases -### Changed -- Bug fixes and improvements. - ---- - -## [0.1.31] - 2026-03-01 - -### Added -- **Plugin system** — users can install executable plugins in `~/.asc/plugins//` to extend the CLI with custom event handlers (e.g., Slack/Telegram notifications) -- `asc plugins list` — list all installed plugins with name, version, enabled status, and subscribed events -- `asc plugins install ` — install a plugin from a local directory containing `manifest.json` and a `run` executable -- `asc plugins uninstall --name ` — remove an installed plugin -- `asc plugins enable --name ` — re-enable a disabled plugin -- `asc plugins disable --name ` — disable a plugin without removing it -- `asc plugins run --name --event ` — manually invoke a plugin for testing; supports `--app-id`, `--version-id`, `--build-id` payload flags -- Auto-event emission: `asc builds upload` fires `build.uploaded` after a successful upload; `asc versions submit` fires `version.submitted` after a successful submission -- Plugin protocol: plugins receive a JSON event payload on stdin and write a `{"success": bool, "message": "..."}` result to stdout -- `version-review-detail get --version-id ` — fetch the App Store review contact info and demo account settings for a version -- `asc version-review-detail update --version-id [flags]` — upsert review info (creates if none exists, patches if already set); supports `--contact-first-name`, `--contact-last-name`, `--contact-phone`, `--contact-email`, `--demo-account-required`, `--demo-account-name`, `--demo-account-password`, `--notes` -- `notes` field added to `AppStoreReviewDetail` domain model -- `getReviewDetail` affordance added to `AppStoreVersion` for agent navigation - ---- - -## [0.1.30] - 2026-03-01 - -### Added -- `asc age-rating get --app-info-id ` — fetch the full age rating declaration for an app info, including all content intensity ratings, boolean flags, kids age band, and region overrides -- `asc age-rating update --declaration-id [flags]` — update individual age rating fields via PATCH; supports all 9 boolean flags (`--advertising`, `--gambling`, `--loot-box`, etc.) and 13 intensity ratings (`--violence-realistic`, `--profanity`, `--sexual-content`, etc.) plus `--kids-age-band`, `--age-rating-override`, and `--korea-age-rating-override` -- `getAgeRating` affordance added to `AppInfo` for agent navigation -- `--privacy-choices-url` and `--privacy-policy-text` flags on `asc app-info-localizations update` — expose the two remaining updatable privacy fields from the App Store Connect API -- `asc app-info-localizations delete --localization-id ` — remove a per-locale metadata entry; `delete` affordance added to `AppInfoLocalization` -- `asc app-infos update --app-info-id [--primary-category] [--primary-subcategory-one] [--primary-subcategory-two] [--secondary-category] [--secondary-subcategory-one] [--secondary-subcategory-two]` — set or update all 6 category relationship fields on an AppInfo; `updateCategories` affordance added to `AppInfo` -- `asc app-categories list [--platform IOS|MAC_OS|TV_OS]` — list all available App Store categories and subcategories; returns a flat list combining top-level (`data[]`) and subcategories (`included[]`) from the API; `AppCategory` domain model with `parentId` for subcategory identification - ---- - -## [0.1.29] - 2026-03-01 - -### Added -- `--style-reference ` flag on `asc app-shots generate` and `asc app-shots translate` — pass any PNG/JPEG as a visual style guide; Gemini replicates the reference's colors, typography, gradients, and layout patterns without copying its content; the reference image is sent as the first part in the Gemini request followed by an explicit style-guide instruction - ---- - -## [0.1.28] - 2026-02-28 - -### Added -- `--device-type` flag on `asc app-shots generate` and `asc app-shots translate` — accepts named App Store display type constants (`APP_IPHONE_69`, `APP_IPHONE_67`, `APP_IPAD_PRO_129`, etc.) and automatically sets the correct `--output-width`/`--output-height`; overrides explicit dimension flags when both are provided; all 16 device types supported across iPhone, iPad, Apple TV, Mac, and Apple Vision Pro - -### Fixed -- `asc app-shots generate` and `asc app-shots translate` now upscale Gemini output to the correct App Store dimensions using CoreGraphics. Gemini returns ~704×1520; the required iPhone 6.9" size is 1320×2868. New `--output-width` (default `1320`) and `--output-height` (default `2868`) flags control the target size for all other device types. - ---- - -## [0.1.27] - 2026-02-28 - -### Added -- `asc app-shots translate` — one-shot localization of generated screenshots; reads the English plan + existing `screen-{n}.png` files, sends them to Gemini with per-locale translation instructions, writes `{output-dir}/{locale}/screen-{n}.png`; supports multiple locales in one invocation (`--to zh --to ja --to ko`); locales processed in parallel via `TaskGroup` -- `asc app-shots generate` — AI-powered App Store screenshot generation using Gemini; reads a `ScreenPlan` JSON + screenshot images, calls Gemini image generation API, writes `screen-{index}.png` files; `--plan` defaults to `.asc/app-shots/app-shots-plan.json`, `--output-dir` defaults to `.asc/app-shots/output`, screenshots auto-discovered from plan directory when not provided — zero-argument happy path: `asc app-shots generate` -- `asc app-shots config` — persistent Gemini API key management; `--gemini-api-key` saves to `~/.asc/app-shots-config.json`, bare invocation shows current key + source (file/env), `--remove` deletes it; `generate` resolves key from flag → env var → stored config -- `ScreenPlan`, `ScreenConfig`, `ScreenTone`, `LayoutMode`, `ScreenColors`, `AppShotsConfig` domain models -- `ScreenshotGenerationRepository` + `AppShotsConfigStorage` `@Mockable` protocols -- `GeminiScreenshotGenerationRepository` — native Gemini `generateContent` API with `responseModalities: ["TEXT","IMAGE"]`, parallel TaskGroup generation -- `FileAppShotsConfigStorage` — saves Gemini API key to `~/.asc/app-shots-config.json` - ---- - -## [0.1.25] - 2026-02-27 - -### Added -- `asc subscriptions submit` — submit a subscription for App Store review; `submit` affordance on `Subscription` appears only when `state == READY_TO_SUBMIT` -- `asc subscription-offers list` — list introductory offers for a subscription -- `asc subscription-offers create` — create a `FREE_TRIAL`, `PAY_AS_YOU_GO`, or `PAY_UP_FRONT` introductory offer; validates that `--price-point-id` is provided for paid modes -- `SubscriptionIntroductoryOffer` domain model with `SubscriptionOfferDuration` and `SubscriptionOfferMode` enums; `requiresPricePoint` semantic boolean on `SubscriptionOfferMode` -- `createIntroductoryOffer` and `listIntroductoryOffers` CAEOAS affordances on `Subscription` -- `asc iap list` — list in-app purchases (consumable, non-consumable, non-renewing subscription) for an app -- `asc iap create` — create a new in-app purchase with reference name, product ID, and type -- `asc iap-localizations list` — list localizations for an in-app purchase -- `asc iap-localizations create` — create a per-locale name and description for an in-app purchase -- `asc subscription-groups list` — list subscription groups for an app -- `asc subscription-groups create` — create a new subscription group -- `asc subscriptions list` — list subscriptions within a group -- `asc subscriptions create` — create a subscription with period (ONE_WEEK–ONE_YEAR), family sharing, and group level -- `asc subscription-localizations list` — list per-locale metadata for a subscription -- `asc subscription-localizations create` — create a per-locale name and description for a subscription -- `asc iap submit` — submit an in-app purchase for App Store review -- `asc iap price-points list` — list available price tiers for an IAP, optionally filtered by territory -- `asc iap prices set` — set the price schedule for an IAP (base territory + auto-pricing for all others) -- `InAppPurchaseSubmission`, `InAppPurchasePricePoint`, `InAppPurchasePriceSchedule` domain models with CAEOAS affordances -- `InAppPurchaseState` and `SubscriptionState` enums with semantic booleans (`isApproved`, `isLive`, `isEditable`, `isPendingReview`) -- CAEOAS affordances on all new models linking to sibling and child commands -- `asc app-preview-sets list` — list App Store video preview sets for a version localization -- `asc app-preview-sets create` — create a new preview set for a specific device type -- `asc app-previews list` — list video previews in a preview set -- `asc app-previews upload` — upload a video file (`.mp4`, `.mov`, `.m4v`) to a preview set using a 3-step upload flow (reserve → PUT chunks → PATCH with MD5) -- `PreviewType` enum with 16 device cases (iPhone, iPad, Mac, Apple TV, Apple Vision Pro) -- `AppPreview.VideoDeliveryState` with 5 states including `PROCESSING` (unique to video) -- Renamed `--group-id` to `--beta-group-id` in all `asc testflight testers` commands for consistency with `asc builds add-beta-group` - ---- - -## [0.1.24] - 2026-02-27 - ---- - -## [0.1.23] - 2026-02-26 - ---- - -## [0.1.22] - 2026-02-26 - ---- - -## [0.1.21] - 2026-02-26 - ---- - -## [0.1.20] - 2026-02-26 - ---- - -## [0.1.9] - 2026-02-25 - ---- - -## [0.1.8] - 2026-02-25 - ---- - -## [0.1.7] - 2026-02-25 - ---- - -## [0.1.6] - 2026-02-24 - ---- - -## [0.1.5] - 2026-02-24 - ---- - -## [0.1.4] - 2026-02-24 - -### Added -- **Persistent Auth Login**: Save API key credentials to `~/.asc/credentials.json` so environment variables are not required on every session. - - `asc auth login --key-id --issuer-id --private-key-path ` — save credentials to disk - - `asc auth logout` — remove saved credentials - - `asc auth check` — verify credentials and show their source (`file` or `environment`) - - Credential resolution order: `~/.asc/credentials.json` → environment variables (fully backwards-compatible) - - `asc auth check` now outputs JSON with an `affordances` field (same agent-first format as all other commands) - -### Technical -- Added `AuthStorage` `@Mockable` protocol (`save`, `load`, `delete`) in Domain -- Added `AuthStatus` domain model (`keyID`, `issuerID`, `source: CredentialSource`) with `AffordanceProviding` -- Added `CredentialSource` enum (`.file` / `.environment`) as `Codable` String raw value -- `AuthCredentials` gains `Codable` conformance for JSON file serialization -- `FileAuthStorage` (Infrastructure) reads/writes `~/.asc/credentials.json` using `JSONEncoder`/`JSONDecoder` -- `FileAuthProvider` (Infrastructure) implements `AuthProvider` backed by `FileAuthStorage` -- `CompositeAuthProvider` (Infrastructure) tries file credentials first, falls back to `EnvironmentAuthProvider` -- `ClientProvider` updated to use `CompositeAuthProvider` — all existing commands gain file-based auth transparently - ---- - -## [0.1.3] - 2026-02-24 - -### Added -- **Version Localization Update**: Set What's New text, description, keywords, marketing URL, support URL, and promotional text for any locale directly from the CLI. - - `asc version-localizations update --localization-id --whats-new "text"` — update What's New - - `asc version-localizations update --localization-id --description "text" --keywords "a,b,c"` — update other fields - - All content fields are optional — only provided fields are sent to the API -- **App Info Localizations**: Manage per-locale app metadata (name, subtitle, privacy policy) directly from the CLI. Each locale's App Store listing information is now fully writable. - - `asc app-infos list --app-id ` — list AppInfo records for an app - - `asc app-info-localizations list --app-info-id ` — list all locale entries - - `asc app-info-localizations create --app-info-id --locale --name ` — add a new locale - - `asc app-info-localizations update --localization-id [--name] [--subtitle] [--privacy-policy-url]` — patch one or more fields - -### Technical -- Introduced `VersionLocalizationRepository` (`@Mockable` protocol) with `listLocalizations`, `createLocalization`, `updateLocalization` — separate from `ScreenshotRepository` which now handles only screenshot sets and images -- Added `SDKLocalizationRepository` implementing `VersionLocalizationRepository`; converts `URL?` ↔ `String?` for `marketingURL`/`supportURL` SDK fields -- `AppStoreVersionLocalization` gains 6 optional text fields with nil-safe Codable (`encodeIfPresent`) — nil fields are omitted from JSON output -- `AppStoreVersionLocalization.affordances` now includes `updateLocalization` command -- `ScreenshotsImport` updated to accept `localizationRepo` + `screenshotRepo` as separate parameters -- Added `AppInfo` and `AppInfoLocalization` domain models carrying parent IDs for agent navigation -- Added `AppInfoRepository` `@Mockable` protocol with `listAppInfos`, `listLocalizations`, `createLocalization`, `updateLocalization` -- Added `SDKAppInfoRepository` mapping `privacyPolicyURL`/`privacyChoicesURL` SDK fields; extracts `appInfoId` from PATCH response relationships -- Updated `App.affordances` to include `listAppInfos` command -- Added `docs/features/version-localizations.md`, `docs/features/app-info-localizations.md` - ---- - -## [0.1.1] - 2026-02-22 - -### Added -- **App Store Version Management**: Browse and create versions for any app across all platforms (iOS, macOS, tvOS, watchOS, visionOS). - - `asc versions list --app-id ` - - `asc versions create --app-id --version --platform ` -- **Localization Management**: List and create App Store version localizations. - - `asc version-localizations list --version-id ` - - `asc version-localizations create --version-id --locale ` -- **Screenshot Sets**: List and create screenshot sets for a localization (one set per display type: iPhone 6.7", iPad Pro 12.9", Mac, etc.). - - `asc screenshot-sets list --localization-id ` - - `asc screenshot-sets create --localization-id --display-type ` -- **Screenshots**: List and upload screenshots within a set. - - `asc screenshots list --set-id ` — with file size, dimensions, and delivery state - - `asc screenshots upload --set-id --file ` — three-step ASC flow (reserve → S3 upload → commit) handled automatically -- **Submit for Review**: Submit an App Store version for review in one command. Reuses an existing open review submission if one is already pending. - - `asc versions submit --version-id ` -- **TestFlight**: List beta groups and testers. - - `asc testflight groups [--app-id ]` - - `asc testflight testers --group-id ` -- **TUI Screenshot Browser**: Interactive terminal UI now supports the full screenshot hierarchy — app → version → platform → locale → screenshot set. -- **Agent-First Output (CAEOAS)**: JSON responses include an `affordances` field with ready-to-run follow-up commands so AI agents can navigate the full resource tree without knowing the command structure. - ```json - { "data": [{ "id": "...", "affordances": { "listVersions": "asc versions list --app-id ..." } }] } - ``` -- **Homebrew Tap**: Install via Homebrew with native architecture binaries (arm64 and x86_64). - ```bash - brew install asc-tools/tap/asc - ``` -- **Homepage**: Static landing page at [asccli.app](https://asccli.app) with English, Chinese, and Japanese localizations. - -### Fixed -- Submission flow now correctly includes the app relationship in the version request, preventing API 422 errors. -- Submission flow reuses an unresolved (open) review submission instead of always creating a new one. - -### Technical -- `AppStoreVersion`, `AppStoreVersionState`, `AppStorePlatform` models with semantic booleans (`isLive`, `isEditable`, `isPending`) -- `AppStoreVersionLocalization`, `AppScreenshotSet`, `AppScreenshot` models; all carry parent IDs -- `ScreenshotDisplayType` enum with 39 display types, device categories, and human-readable names -- `AffordanceProviding` protocol; `OutputFormatter.formatAgentItems()` merges affordances into JSON -- `SubmissionRepository` orchestrating 4-step review submission API flow -- `ReviewSubmission` domain model with `ReviewSubmissionState` and `isWaitingForReview` -- `SequencedStubAPIClient` for multi-step API flow tests -- `APIClient` protocol abstraction enabling `StubAPIClient` for infrastructure tests -- Automated GitHub Actions release workflow with binary artifacts and Homebrew formula update -- CI/CD Codecov coverage reporting - ---- - -## [0.1.0] - 2026-02-10 - -### Added -- **Apps**: List and get apps on your App Store Connect account. - - `asc apps list` -- **Builds**: List builds for an app with processing state, version, and expiry. - - `asc builds list [--app-id ]` -- **TUI**: Interactive terminal UI (`asc tui`) for browsing apps and builds without remembering IDs. -- **Auth Check**: Verify environment credentials are valid. - - `asc auth check` -- **Output Formats**: All commands support `--output json` (default), `--output table`, and `--output markdown`. Use `--pretty` for formatted JSON. -- **Environment Auth**: Configure via `ASC_KEY_ID`, `ASC_ISSUER_ID`, and `ASC_PRIVATE_KEY_PATH` (or `ASC_PRIVATE_KEY` for inline PEM). - -### Technical -- Three-layer architecture: `ASCCommand → Infrastructure → Domain` -- `appstoreconnect-swift-sdk` adapter with clean domain model separation -- `App`, `Build`, `BetaGroup`, `BetaTester` domain models (`Sendable`, `Equatable`, `Identifiable`, `Codable`) -- `AppRepository`, `BuildRepository`, `TestFlightRepository` `@Mockable` protocols -- `ClientFactory` + `ClientProvider` dependency injection wiring -- Apple `@Testing` macro test suite following Chicago School TDD - ---- +[0.17](docs/changelog/0.17.md) · [0.16](docs/changelog/0.16.md) · [0.1](docs/changelog/0.1.md) [Unreleased]: https://github.com/tddworks/asc-cli/compare/v0.18.4...HEAD [0.18.4]: https://github.com/tddworks/asc-cli/compare/v0.18.3...v0.18.4 @@ -999,67 +79,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.18.2]: https://github.com/tddworks/asc-cli/compare/v0.18.1...v0.18.2 [0.18.1]: https://github.com/tddworks/asc-cli/compare/v0.18.0...v0.18.1 [0.18.0]: https://github.com/tddworks/asc-cli/compare/v0.17.9...v0.18.0 -[0.17.9]: https://github.com/tddworks/asc-cli/compare/v0.17.8...v0.17.9 -[0.17.8]: https://github.com/tddworks/asc-cli/compare/v0.17.7...v0.17.8 -[0.17.7]: https://github.com/tddworks/asc-cli/compare/v0.1.74...v0.17.7 -[0.1.74]: https://github.com/tddworks/asc-cli/compare/v0.17.3...v0.1.74 -[0.17.3]: https://github.com/tddworks/asc-cli/compare/v0.17.2...v0.17.3 -[0.17.2]: https://github.com/tddworks/asc-cli/compare/v0.17.1...v0.17.2 -[0.17.1]: https://github.com/tddworks/asc-cli/compare/v0.17.0...v0.17.1 -[0.17.0]: https://github.com/tddworks/asc-cli/compare/v0.16.9...v0.17.0 -[0.16.9]: https://github.com/tddworks/asc-cli/compare/v0.1.68...v0.16.9 -[0.1.68]: https://github.com/tddworks/asc-cli/compare/v0.1.67...v0.1.68 -[0.1.67]: https://github.com/tddworks/asc-cli/compare/v0.1.66...v0.1.67 -[0.1.66]: https://github.com/tddworks/asc-cli/compare/v0.1.65...v0.1.66 -[0.1.65]: https://github.com/tddworks/asc-cli/compare/v0.1.64...v0.1.65 -[0.1.64]: https://github.com/tddworks/asc-cli/compare/v0.1.63...v0.1.64 -[0.1.63]: https://github.com/tddworks/asc-cli/compare/v0.1.62...v0.1.63 -[0.1.62]: https://github.com/tddworks/asc-cli/compare/v0.1.61...v0.1.62 -[0.1.61]: https://github.com/tddworks/asc-cli/compare/v0.1.60...v0.1.61 -[0.1.60]: https://github.com/tddworks/asc-cli/compare/v0.1.59...v0.1.60 -[0.1.59]: https://github.com/tddworks/asc-cli/compare/v0.1.58...v0.1.59 -[0.1.58]: https://github.com/tddworks/asc-cli/compare/v0.1.57...v0.1.58 -[0.1.57]: https://github.com/tddworks/asc-cli/compare/v0.1.56...v0.1.57 -[0.1.56]: https://github.com/tddworks/asc-cli/compare/v0.1.55...v0.1.56 -[0.1.55]: https://github.com/tddworks/asc-cli/compare/v0.1.53...v0.1.55 -[0.1.53]: https://github.com/tddworks/asc-cli/compare/v0.1.52...v0.1.53 -[0.1.52]: https://github.com/tddworks/asc-cli/compare/v0.1.51...v0.1.52 -[0.1.51]: https://github.com/tddworks/asc-cli/compare/v0.1.50...v0.1.51 -[0.1.50]: https://github.com/tddworks/asc-cli/compare/v0.1.49...v0.1.50 -[0.1.49]: https://github.com/tddworks/asc-cli/compare/v0.1.48...v0.1.49 -[0.1.48]: https://github.com/tddworks/asc-cli/compare/v0.1.47...v0.1.48 -[0.1.47]: https://github.com/tddworks/asc-cli/compare/v0.1.46...v0.1.47 -[0.1.46]: https://github.com/tddworks/asc-cli/compare/v0.1.45...v0.1.46 -[0.1.45]: https://github.com/tddworks/asc-cli/compare/v0.1.43...v0.1.45 -[0.1.43]: https://github.com/tddworks/asc-cli/compare/v0.1.42...v0.1.43 -[0.1.42]: https://github.com/tddworks/asc-cli/compare/v0.1.41...v0.1.42 -[0.1.41]: https://github.com/tddworks/asc-cli/compare/v0.1.40...v0.1.41 -[0.1.40]: https://github.com/tddworks/asc-cli/compare/v0.1.39...v0.1.40 -[0.1.39]: https://github.com/tddworks/asc-cli/compare/v0.1.38...v0.1.39 -[0.1.38]: https://github.com/tddworks/asc-cli/compare/v0.1.37...v0.1.38 -[0.1.37]: https://github.com/tddworks/asc-cli/compare/v0.1.36...v0.1.37 -[0.1.36]: https://github.com/tddworks/asc-cli/compare/v0.1.35...v0.1.36 -[0.1.35]: https://github.com/tddworks/asc-cli/compare/v0.1.34...v0.1.35 -[0.1.34]: https://github.com/tddworks/asc-cli/compare/v0.1.33...v0.1.34 -[0.1.33]: https://github.com/tddworks/asc-cli/compare/v0.1.32...v0.1.33 -[0.1.32]: https://github.com/tddworks/asc-cli/compare/v0.1.31...v0.1.32 -[0.1.31]: https://github.com/tddworks/asc-cli/compare/v0.1.30...v0.1.31 -[0.1.30]: https://github.com/tddworks/asc-cli/compare/v0.1.29...v0.1.30 -[0.1.29]: https://github.com/tddworks/asc-cli/compare/v0.1.28...v0.1.29 -[0.1.28]: https://github.com/tddworks/asc-cli/compare/v0.1.27...v0.1.28 -[0.1.27]: https://github.com/tddworks/asc-cli/compare/v0.1.25...v0.1.27 -[0.1.25]: https://github.com/tddworks/asc-cli/compare/v0.1.24...v0.1.25 -[0.1.24]: https://github.com/tddworks/asc-cli/compare/v0.1.23...v0.1.24 -[0.1.23]: https://github.com/tddworks/asc-cli/compare/v0.1.22...v0.1.23 -[0.1.22]: https://github.com/tddworks/asc-cli/compare/v0.1.21...v0.1.22 -[0.1.21]: https://github.com/tddworks/asc-cli/compare/v0.1.20...v0.1.21 -[0.1.20]: https://github.com/tddworks/asc-cli/compare/v0.1.9...v0.1.20 -[0.1.9]: https://github.com/tddworks/asc-cli/compare/v0.1.8...v0.1.9 -[0.1.8]: https://github.com/tddworks/asc-cli/compare/v0.1.7...v0.1.8 -[0.1.7]: https://github.com/tddworks/asc-cli/compare/v0.1.6...v0.1.7 -[0.1.6]: https://github.com/tddworks/asc-cli/compare/v0.1.5...v0.1.6 -[0.1.5]: https://github.com/tddworks/asc-cli/compare/v0.1.4...v0.1.5 -[0.1.4]: https://github.com/tddworks/asc-cli/compare/v0.1.3...v0.1.4 -[0.1.3]: https://github.com/tddworks/asc-cli/compare/v0.1.1...v0.1.3 -[0.1.1]: https://github.com/tddworks/asc-cli/compare/v0.1.0...v0.1.1 -[0.1.0]: https://github.com/tddworks/asc-cli/releases/tag/v0.1.0 diff --git a/CLAUDE.md b/CLAUDE.md index b5c39a14..b96e2d42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. It holds only what applies to every task; everything else is linked. ## TDD is non-negotiable (read this first) @@ -13,274 +13,59 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 3. Run `swift test` (or a `--filter`'d subset) and **observed the test fail** — compile error counts as a failing test only if the assertion is the reason it can't compile (e.g. missing symbol the test names). 4. Reported the red result back to the user (one line is fine: "test X fails with: "). -Only after step 4 may you write code under `Sources/`. The full workflow, naming rules, and framework details are in the [Testing](#testing) section. +Only after step 4 may you write code under `Sources/`. **If you skip the gate, you are violating the project's primary rule.** Treat this the same as committing secrets or force-pushing main. -## Commands +### Testing rules -```bash -# Build -swift build # Debug build -swift build -c release # Release build +Chicago School — state-based, not interaction-based: tests verify what domain objects return and compute, not how they call collaborators. Red → green → refactor, every time. -# Test -swift test # All tests -swift test --filter 'AppTests' # Tests matching a pattern -swift test --enable-code-coverage # With coverage +- Name tests after the user's expectation, with backticks: `` func `version is live when state is readyForSale`() ``. +- Assert exact output values (`"READY_FOR_SALE"`, `"expired": true`), never "is non-empty" or "doesn't throw". Command tests assert the full JSON string. +- Implement just enough to pass — no extra fields, no speculative branches. +- Difficult to test = design problem. Never modify a test to make it pass; if it fails unexpectedly, the spec was wrong. +- `@Testing` (not XCTest); `@Mockable` protocols with `given().willReturn()`; shared test data in `Tests/DomainTests/TestHelpers/MockRepositoryFactory.swift`. -# Run -swift run asc -make run ARGS="apps list" +## Commands + +```bash +swift build # debug build (-c release for release) +swift test # all tests +swift test --filter 'AppTests' # tests matching a pattern +swift test --enable-code-coverage # with coverage +make run ARGS="apps list" # run the CLI +make docs # regenerate docs/commands.md + docs/README.md +make check-docs # broken links, doc size budgets ``` ## Architecture -Three strict layers with a unidirectional dependency flow: `ASCCommand → Infrastructure → Domain` +Three strict layers, dependencies pointing one way: `ASCCommand → Infrastructure → Domain`. ``` Sources/ -├── Domain/ # Pure value types, @Mockable protocols — zero I/O -├── Infrastructure/# Implements Domain protocols via appstoreconnect-swift-sdk -└── ASCCommand/ # CLI entry point, output formatting, TUI -``` - -### Domain Layer - -All models are `public struct` + `Sendable` + `Equatable` + `Codable`. The JSON encoding is the public schema. Models with optional text fields use custom `Codable` with `encodeIfPresent` to omit nil values from JSON output. - -**Design rules:** -- Every model carries its **parent ID** (e.g. `AppStoreVersion.appId`, `AppScreenshot.setId`) — the App Store Connect API doesn't return parent IDs, so Infrastructure injects them -- State enums expose **semantic booleans** (`isLive`, `isEditable`, `isPending`, `isComplete`) for agent decision-making -- All repositories and providers are `@Mockable` protocols - -### Infrastructure Layer - -Adapts `appstoreconnect-swift-sdk` to Domain protocols. The critical pattern: mappers always inject the parent ID from the request parameter into every mapped response object. - -### ASCCommand Layer - -- `ASC.swift` — `@main` entry, registers all subcommands -- `GlobalOptions.swift` — `--output` (default: json), `--pretty`, `--timeout` -- `OutputFormatter.swift` — JSON/table/markdown rendering; `formatAgentItems()` merges affordances -- `ClientProvider.swift` — factory wiring auth → authenticated repositories -- `Commands/Web/` — `asc web-server` serves the REST API; `RESTRoutes.configure` composes `*Controller` structs (Hummingbird). Every new list/read command **must** also be exposed here (see "REST exposure" below) - -## Key Design Patterns - -### CAEOAS (Commands As the Engine Of Application State) - -CLI equivalent of REST HATEOAS. Every response includes an `affordances` field with ready-to-run CLI commands so an AI agent can navigate without knowing the command tree. Affordances are **state-aware** — e.g. `submitForReview` only appears when `isEditable == true`. - -All domain models implement `AffordanceProviding`: -```swift -protocol AffordanceProviding { - var affordances: [String: String] { get } -} -``` - -`OutputFormatter.formatAgentItems()` merges affordances into the encoded JSON output. - -### REST exposure (every feature must ship both CLI and REST) - -The `asc web-server` command exposes the same functionality as the CLI over HTTP so an agent can drive it as a REST service. **A feature is not complete until it is reachable via REST.** - -Required steps when adding a new list/read command (or any command that returns data an agent might want over HTTP): - -1. **Make the domain model `Presentable`** — `tableHeaders` + `tableRow`. Needed because `restFormat` is `` -2. **Use `structuredAffordances` (not raw `affordances`)** on the model — the REST renderer derives `_links` from `Affordance` values; returning a plain `[String: String]` leaves `_links` empty -3. **Give the command an `affordanceMode` parameter** — `func execute(repo:…, affordanceMode: AffordanceMode = .cli)` forwards to `formatter.formatAgentItems(items, affordanceMode: affordanceMode)`. The same `execute` runs for both `cli` and `rest` modes -4. **Add/extend a controller** under `Sources/ASCCommand/Commands/Web/Controllers/` — inject the repository, register a `group.get("/…")` route, parse query params via `request.uri.queryParameters` (use the same names as the CLI flags, e.g. `?state=&limit=&expired-only=&before=`), call the repository, return `try restFormat(items)` -5. **Wire the controller** in `Sources/ASCCommand/Commands/Web/RESTRoutes.swift` (construct with `factory.make…Repository(authProvider: auth)`) -6. **Advertise the resource** from `APIRoot.structuredAffordances` so `GET /api/v1` lists the new top-level resource -7. **Add a REST test** in `Tests/ASCCommandTests/Commands/Web/RESTRoutesTests.swift` — call `execute(repo:affordanceMode: .rest)` and assert the output contains `"_links"` and the resolved REST paths -8. **CLI and REST query-param names must match** — if the CLI uses `--expired-only`, the REST query param is `?expired-only=true`; both go through the same repository method - -Shared helpers (all in `Sources/ASCCommand/Commands/Web/RESTRoutes.swift`): -- `restFormat(items)` — REST equivalent of `formatter.formatAgentItems(items, affordanceMode: .rest)` -- `jsonError(message, status:)` — JSON error response (lives in `Infrastructure/Web/ASCWebServer.swift`; `import Infrastructure`) - -Affordance actions map to REST by `RESTPathResolver`: `list`/`get` → GET, `create` and `add` → POST to the parent's collection, `update` → PATCH, `delete` and `remove` → DELETE on the resource, anything else (e.g. `submit`) → POST `…/{id}/{action}`. - -Controllers are structs with dependencies injected at init (Hummingbird pattern). Repositories are constructed once in `RESTRoutes.configure`, never per request. - -### Resource Hierarchy - -Commands mirror the App Store Connect API hierarchy exactly: -``` -App → AppStoreVersion → AppStoreVersionLocalization → AppScreenshotSet → AppScreenshot -App → AppInfo → AppInfoLocalization -App → AppInfo → AgeRatingDeclaration -AppCategory (top-level, not nested under App) -App → CustomerReview → CustomerReviewResponse -App → Build → BetaBuildLocalization -App → BuildUpload -App → TestFlight (BetaGroup → BetaTester) -App → CiProduct (XcodeCloud) → CiWorkflow → CiBuildRun -AppStoreVersion → VersionReadiness -AppStoreVersion → AppStoreReviewDetail -CodeSigning: BundleID → Profile -App → AppStoreVersionExperiment → ExperimentTreatment → ExperimentTreatmentLocalization -App → PerformanceMetric (via perfPowerMetrics) -Build → PerformanceMetric (via perfPowerMetrics) -Build → DiagnosticSignatureInfo → DiagnosticLogEntry -InAppPurchase | Subscription | SubscriptionGroup → ProductVersion -App → ReviewSubmission → ReviewSubmissionItem → (AppStoreVersion | ProductVersion | …) -``` - -Domain folders are nested to mirror the resource hierarchy: -``` -Domain/ -├── Apps/ → App, AppRepository -│ ├── Versions/ → AppStoreVersion, AppStoreVersionState, VersionReadiness, -│ │ │ VersionRepository, ReviewDetailRepository, -│ │ │ AppStoreReviewDetail, ReviewDetailUpdate -│ │ └── Localizations/ → AppStoreVersionLocalization, VersionLocalizationRepository -│ │ └── ScreenshotSets/ → AppScreenshotSet, ScreenshotDisplayType, ScreenshotRepository -│ │ └── Screenshots/ → AppScreenshot -│ ├── AppInfos/ → AppInfo, AppInfoLocalization, AppInfoRepository, -│ │ AppCategory, AppCategoryRepository, -│ │ AgeRatingDeclaration, AgeRatingDeclarationRepository -│ ├── Reviews/ → CustomerReview, CustomerReviewResponse, ReviewResponseState, -│ │ CustomerReviewRepository -│ ├── Builds/ → Build, BuildUpload, BetaBuildLocalization, -│ │ BuildRepository, BuildUploadRepository, BetaBuildLocalizationRepository -│ ├── Experiments/ → AppStoreVersionExperiment, AppStoreVersionExperimentState, -│ │ ExperimentTreatment, ExperimentTreatmentLocalization, ExperimentRepository -│ ├── Pricing/ → PricingRepository -│ ├── ProductVersions/ → ProductVersion, ProductVersionKind, ProductVersionState, -│ │ ProductVersionRepository (IAP/subscription/group review versions) -│ ├── TestFlight/ → BetaGroup, BetaTester, TestFlightRepository -│ └── Performance/ → PerformanceMetric, PerformanceMetricCategory, DiagnosticSignatureInfo, -│ DiagnosticType, DiagnosticLogEntry, PerfMetricsRepository, DiagnosticsRepository -├── CodeSigning/ → BundleID, Certificate, Device, Profile + their repositories -│ ├── BundleIDs/ → BundleID, BundleIDRepository -│ ├── Certificates/ → Certificate, CertificateRepository -│ ├── Devices/ → Device, DeviceRepository -│ └── Profiles/ → Profile, ProfileRepository -├── Submissions/ → ReviewSubmission, ReviewSubmissionState, ReviewSubmissionItem, -│ ReviewItemTarget, ReviewSubmissionError, SubmissionRepository, -│ SubmissionPlanner/SubmissionPlan (versions submit --with-products) -├── Auth/ → AuthCredentials, AuthProvider, AuthStatus, AuthStorage, CredentialSource, AuthError -├── Projects/ → ProjectConfig, ProjectConfigStorage -├── Skills/ → Skill, SkillCheckResult, SkillConfig, SkillRepository, SkillConfigStorage -└── Shared/ → AffordanceProviding, APIError, OutputFormat, PaginatedResponse -``` -Infrastructure and test folders mirror this exact structure. - -### Project Context (`.asc/project.json`) - -`asc init` saves the app ID, name, and bundle ID to `.asc/project.json` in the current directory: - -```bash -asc init # auto-detect from *.xcodeproj bundle ID -asc init --name "X" # search by name -asc init --app-id +├── Domain/ # Pure value types, @Mockable protocols — zero I/O +├── Infrastructure/ # Implements Domain protocols via appstoreconnect-swift-sdk +└── ASCCommand/ # CLI entry (ASC.swift), output formatting, REST server (Commands/Web/), TUI ``` -`FileProjectConfigStorage` (Infrastructure) reads/writes `.asc/project.json` relative to cwd. `ProjectConfig` (Domain) carries `appId`, `appName`, `bundleId` + CAEOAS affordances. - -## Testing - -We follow the Chicago School of TDD — state-based, not interaction-based. Tests verify what domain objects return and compute, not how they call collaborators. The non-negotiable rule and pre-implementation gate live at the [top of this file](#tdd-is-non-negotiable-read-this-first); everything below is the framework-specific detail. - -**Red → green → refactor**, in that order, every time: - -1. **Think from the user's mental model.** Describe the behaviour as the user would: "a version is live when its state is `readyForSale`", "submit is only available when the version is editable". Don't describe internal calls. -2. **Write the test.** Name it after the user's expectation. Assert exact output values (e.g. `"IOS"`, `"READY_FOR_SALE"`, `"expired": true`) — not "is non-empty" or "doesn't throw". -3. **Run the test and confirm it fails (red).** If it passes before you wrote any code, it isn't testing new behaviour — fix the test. -4. **Implement just enough code to make it pass (green).** No extra fields, no speculative branches. -5. **Refactor while green.** Tests stay passing throughout. - -**Hard rules:** - -- Difficult to test = design problem, not a testing exception. Refactor the design, don't skip the test. -- Never modify a test to make it pass. If a test fails unexpectedly, the spec (step 1) was wrong — fix the thinking, not the assertion. -- No "I'll add tests after". Production code without a preceding red test is a defect, even if it works. - -**Framework:** - -- `@Testing` macro (not XCTest). -- `@Mockable` on protocols; `given().willReturn()` in tests. -- Test names use backticks: `` func `version is live when state is readyForSale`() ``. -- Shared test data: `Tests/DomainTests/TestHelpers/MockRepositoryFactory.swift`. - -## Two Localization Types - -The codebase has two distinct localization concepts with separate repositories: - -| Type | Domain folder | Repository | Commands | Data | -|------|--------------|------------|----------|------| -| `AppStoreVersionLocalization` | `Domain/Localizations/` | `VersionLocalizationRepository` | `asc version-localizations *` | whatsNew, description, keywords, screenshots | -| `AppInfoLocalization` | `Domain/AppInfos/` | `AppInfoRepository` | `asc app-info-localizations *` | name, subtitle, privacyPolicyUrl, privacyChoicesUrl, privacyPolicyText | - -`ScreenshotRepository` (in `Domain/ScreenshotSets/`) handles screenshot sets and screenshot images — **no localization methods**. - -## Documentation - -After every code change — new feature, improvement, or bug fix — update all affected docs before considering the task done. +Domain folders mirror the App Store Connect resource hierarchy (`Domain/Apps/Versions/Localizations/…`); Infrastructure and `Tests/` mirror Domain exactly. Look at the tree rather than a list here. -### What to update +**Rules every change follows:** +- Domain models are `public struct` + `Sendable` + `Equatable` + `Codable`; the JSON encoding is the public schema. Optional text fields use `encodeIfPresent` so nil is omitted. +- Every model carries its **parent ID** (`AppStoreVersion.appId`, `AppScreenshot.setId`). The API doesn't return parent IDs, so Infrastructure mappers inject them from the request. +- State enums expose **semantic booleans** (`isLive`, `isEditable`, `isPending`) for agent decisions. +- **CAEOAS:** every model provides `structuredAffordances` — ready-to-run next commands, state-aware (e.g. `submitForReview` only when `isEditable`). The CLI `affordances` and REST `_links` both derive from it. +- **CLI and REST ship together:** a command that returns data is also exposed by `asc web-server`. A feature isn't done until it is reachable over REST. +- `AppStoreVersionLocalization` (`asc version-localizations`: whatsNew, description, keywords) and `AppInfoLocalization` (`asc app-info-localizations`: name, subtitle, privacy URLs) are different resources with different repositories. Don't mix them up. -| Change type | Files to update | -|-------------|-----------------| -| New feature / command | `docs/features/.md` (create, include a **REST Endpoints** section), `CHANGELOG.md` ([Unreleased]), `README.md` (feature list + CLI examples), `skills/` (relevant skill files), `Sources/ASCCommand/Commands/Web/Controllers/` (new or extended controller), `Sources/ASCCommand/Commands/Web/RESTRoutes.swift` (wire controller), `Sources/Domain/Shared/APIRoot.swift` (advertise new top-level resource), `Tests/ASCCommandTests/Commands/Web/RESTRoutesTests.swift` (REST test) | -| Improvement / enhancement | `docs/features/.md` (update affected sections), `CHANGELOG.md` ([Unreleased]) | -| Bug fix | `CHANGELOG.md` ([Unreleased]) | -| Architecture / API change | `CLAUDE.md` (update architecture / patterns sections), `docs/features/.md` | -| Auth / config change | `CLAUDE.md` (Authentication section), `README.md` | +Why it's built this way: [docs/design.md](docs/design.md). -### Per-file rules - -**`docs/features/.md`** — write from actual code (read files first, never from memory). Structure: -1. CLI Usage — flags table + examples + output samples (json + table) -2. REST Endpoints — path table + query-param mapping (CLI flag → REST query) + curl example -3. Typical Workflow — end-to-end bash script showing the happy path -4. Architecture — three-layer ASCII diagram + dependency note -5. Domain Models — every public struct/enum/protocol with fields, computed properties, affordances -6. File Map — `Sources/` and `Tests/` trees + wiring files table (must list the REST controller) -7. API Reference — endpoint → SDK call → repository method -8. Testing — representative test snippet + `swift test` command -9. Extending — natural next steps with stub code - -**`CHANGELOG.md`** — add entry under `[Unreleased]` using Keep a Changelog format: -- `### Added` for new features/commands -- `### Changed` for improvements to existing behaviour -- `### Fixed` for bug fixes - -**`README.md`** — update the feature/command table and any usage examples that changed. - -**`skills/`** — always use the `/skill-creator` skill to create or update feature skills - -Key skills to keep in sync: -- `implement-feature/SKILL.md` — workflow + checklist -- `asc-cli/references/commands.md` — command reference -- Feature-specific skills (`asc-testflight`, `asc-beta-review`, `asc-builds-upload`, `asc-code-signing`, `asc-check-readiness`, `asc-app-previews`, `asc-app-shots`, `asc-review-detail`, `asc-plugins`, `asc-experiments`, etc.) - -**`CLAUDE.md`** — update when architecture patterns, file locations, or design rules change. - ---- - -## Authentication - -**Option A — Persistent login (recommended):** - -```bash -asc auth login --key-id --issuer-id --private-key-path ~/.asc/AuthKey_XXXXXX.p8 [--vendor-number ] -asc auth update --vendor-number # add vendor number to existing account -asc auth logout # remove saved credentials -asc auth check # verify credentials; shows source: "file" or "environment" -``` - -Credentials saved to `~/.asc/credentials.json`. Vendor number (optional) is used by `sales-reports` and `finance-reports` commands — auto-resolved from the active account when `--vendor-number` is omitted. - -**Option B — Environment variables:** - -```bash -export ASC_KEY_ID="YOUR_KEY_ID" -export ASC_ISSUER_ID="YOUR_ISSUER_ID" -export ASC_PRIVATE_KEY_PATH="~/.asc/AuthKey_XXXXXX.p8" -# OR use ASC_PRIVATE_KEY with the PEM content directly -``` +## When you are… -**Resolution order:** `~/.asc/credentials.json` → environment variables, handled by `CompositeAuthProvider` in Infrastructure. `EnvironmentAuthProvider` is the fallback. \ No newline at end of file +- **adding a feature** → `implement-feature` skill (architecture approval, TDD phases, REST exposure checklist, docs) +- **improving an existing one** → `improvement` skill +- **touching docs** → [docs/documentation-design](docs/documentation-design/README.md). In short: new feature = `docs/features//README.md` + one CHANGELOG line + `make docs`; fix = one CHANGELOG line. Never hand-edit `docs/commands.md` or `docs/README.md`. +- **working with auth or credentials** → [docs/features/asc-auth](docs/features/asc-auth/README.md) (`~/.asc/credentials.json` first, then `ASC_KEY_ID` / `ASC_ISSUER_ID` / `ASC_PRIVATE_KEY_PATH` env vars, via `CompositeAuthProvider`) +- **working with iris (private API)** → [docs/features/iris](docs/features/iris/README.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..396e1848 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing + +## Build & test + +Requires Swift 6.2+ on macOS 14+. + +```bash +git clone https://github.com/tddworks/asc-cli.git && cd asc-cli +swift build # debug build +swift test # all tests +swift test --filter 'AppTests' # a subset +make run ARGS="apps list" # run the CLI +swift format --in-place --recursive Sources Tests + +swift build -c release && cp .build/release/asc /usr/local/bin/ # install from source +``` + +## How the code is organised + +Three layers, dependencies pointing one way: `ASCCommand → Infrastructure → Domain`. + +``` +Sources/ +├── Domain/ # Pure value types, @Mockable protocols — zero I/O +├── Infrastructure/ # SDK adapters (appstoreconnect-swift-sdk), parent ID injection +└── ASCCommand/ # CLI commands, REST server, output formatting, TUI +``` + +Why it's built this way (CAEOAS, rich domain models, parent IDs): [docs/design.md](docs/design.md). + +## Rules + +- **Tests first.** Write a failing test before any production code (Chicago-school, state-based, `@Testing`). See the gate at the top of [CLAUDE.md](CLAUDE.md). +- **CLI and REST ship together.** A command that returns data is also exposed by `asc web-server`. The step-by-step checklist is in the [implement-feature skill](.claude/skills/implement-feature/SKILL.md). + +## Docs + +Docs follow [docs/documentation-design](docs/documentation-design/README.md). In short: + +| Change | Update | +|---|---| +| New feature | `docs/features//README.md` with a `description`, one CHANGELOG line, `make docs` | +| New or changed flag | Nothing by hand; `make docs` regenerates [docs/commands.md](docs/commands.md) | +| Improvement | The affected feature doc section, one CHANGELOG line | +| Bug fix | One CHANGELOG line | + +`make check-docs` reports broken links and size budgets. + +## Dependencies + +[appstoreconnect-swift-sdk](https://github.com/AvdLee/appstoreconnect-swift-sdk) · [swift-argument-parser](https://github.com/apple/swift-argument-parser) · [Hummingbird](https://github.com/hummingbird-project/hummingbird) · [TauTUI](https://github.com/steipete/TauTUI) · [Mockable](https://github.com/Kolos65/Mockable) · [SweetCookieKit](https://github.com/steipete/SweetCookieKit) diff --git a/Makefile b/Makefile index dbe7ee9d..68350bca 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test lint format clean dev run +.PHONY: build test lint format clean dev run docs check-docs build: swift build @@ -22,3 +22,10 @@ dev: build run: swift run asc $(ARGS) + +# Regenerate docs/commands.md and docs/README.md (never edit those by hand) +docs: build + python3 scripts/gen-docs.py .build/debug/asc + +check-docs: + python3 scripts/check-docs.py diff --git a/README.md b/README.md index 07621f59..b55f0271 100644 --- a/README.md +++ b/README.md @@ -5,599 +5,34 @@ [![CI](https://github.com/tddworks/asc-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/tddworks/asc-cli/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/tddworks/asc-cli/graph/badge.svg?token=v0k1Vzubrx)](https://codecov.io/gh/tddworks/asc-cli) [![Swift](https://img.shields.io/badge/Swift-6.2-orange)](https://swift.org) -[![Platform](https://img.shields.io/badge/macOS-15%2B-blue)](https://www.apple.com/macos/) +[![Platform](https://img.shields.io/badge/macOS-14%2B-blue)](https://www.apple.com/macos/) A CLI for App Store Connect — automate builds, releases, TestFlight, subscriptions, and screenshots from your terminal or CI pipeline. Outputs structured JSON so AI agents can drive the full release workflow. ## Quick Start -```bash -brew install asccli - -asc auth login \ - --key-id YOUR_KEY_ID \ - --issuer-id YOUR_ISSUER_ID \ - --private-key-path ~/.asc/AuthKey_XXXXXX.p8 \ - --name personal # optional alias; defaults to "default" - -asc apps list # find your app ID -asc init --app-id # pin it — skip --app-id on every future command -``` - -### First-time IAPs and subscriptions: submit them with the app version - -Apple requires first-time in-app purchases and subscriptions to go to review together with an app version. The public API supports this through product versions, so your API key is enough: - -```bash -asc versions submit --version-id --with-products --dry-run # see what would go to review -asc versions submit --version-id --with-products # app version + every READY_TO_SUBMIT product -``` - -See [docs/features/submit-with-products.md](docs/features/submit-with-products.md) for the step-by-step `review-submissions create / items add / submit` commands. - -### Optional: sign in to iris (web UI parity) - -Some App Store Connect web features have no public API. Sign in once with your Apple ID and `asc iap list` will also offer the iris submission routes for each IAP: - -```bash -asc iris auth login --apple-id you@example.com --interactive -# Apple ID password: •••••••• -# Two-factor authentication required. -# 2FA code: 123456 -# → session saved to ~/.asc/iris/session.json (~30 days) - -asc iris status # verify; shows source: "srpLogin" -``` - -Now `asc iap list --app-id ` enriches each IAP with the right submission affordance — `addToNextVersion` for queue-via-iris, `removeFromNextVersion` to dequeue, `submit` for established-app standalone review. The agent reads one affordance and runs it; no need to learn iris-vs-sdk. - -`asc iris auth logout` to clear the session. CI scripts using the API key alone keep working unchanged — iris is purely additive. - -## Features - -| Category | What you can do | -| --- | --- | -| **Apps & Versions** | List apps, set the app's price (or make it free), create versions, link builds, submit for App Store review — with first-time IAPs and subscriptions in the same submission (`--with-products`) | -| **Builds** | Archive Xcode projects, export IPA/PKG, upload to App Store Connect, distribute to TestFlight, update beta notes | -| **Metadata** | Update What's New, description, and keywords per locale | -| **App Info** | Set per-locale name, subtitle, privacy policy; manage categories and age rating | -| **Screenshots** | Create screenshot sets and upload images | -| **App Previews** | Upload video previews (`.mp4`, `.mov`, `.m4v`) per locale and device size | -| **App Shots** | AI-powered screenshot generation — single templates, gallery sets, plugin-provided themes (colors, decorations, animations), Gemini enhancement; two-step ThemeDesign workflow for batch styling without extra AI calls | -| **TestFlight** | Manage beta groups; add/remove/import/export testers; submit builds for beta review; per-locale **Beta App Description** + tester feedback metadata | -| **Monetization** | IAPs (consumable, non-consumable, non-renewing); subscriptions, intro offers, **promotional offers**, **win-back offers**, offer codes (3-level), **promoted purchases**; full lifecycle (update/delete/unsubmit), per-territory pricing (with `proceedsYear2`), review screenshots, and 1024×1024 promotional images | -| **Code Signing** | Bundle IDs, certificates, devices, provisioning profiles | -| **Authentication** | Multi-account credential management; named accounts, active-account switching | -| **Project Init** | `asc init` pins app context to `.asc/project.json`; auto-detects from `.xcodeproj` | -| **Customer Reviews** | Read customer reviews, respond to feedback, and manage review responses | -| **Product Page Optimization** | Create A/B tests of your product page (`experiments`), add up to three treatments with alternate icons, choose locales, start/stop the test | -| **App Clips** | Manage App Clips, default experiences, and locale-specific card content | -| **Game Center** | Manage achievements and leaderboards for your game | -| **Plugins** | Install executable plugins in `~/.asc/plugins/` for custom event handlers | -| **Reports** | Sales, subscription, installs, and financial reports; multi-step analytics workflow | -| **Iris (Private API)** | Cookie-based auth; create apps, list apps, read App Review's Resolution Center rejection messages via the iris private API that powers the ASC web UI | -| **Simulators** | List, boot, shutdown local iOS simulators; streaming via plugin | -| **AI Agents** | JSON output with CAEOAS affordances — agents navigate without knowing the command tree | - -## Requirements - -- macOS 13+ -- App Store Connect API key ([create one here](https://appstoreconnect.apple.com/access/integrations/api)) -- Swift 6.2+ _(only needed when building from source)_ - -## Installation - -### Homebrew (recommended) +You need an App Store Connect API key ([create one here](https://appstoreconnect.apple.com/access/integrations/api)). ```bash brew install asccli -``` - -### Build from source - -```bash -git clone https://github.com/tddworks/asc-cli.git -cd asc-cli -swift build -c release -cp .build/release/asc /usr/local/bin/ -``` - -## Authentication - -### Persistent login (recommended) -```bash -# Single account (saves as "default") asc auth login \ --key-id YOUR_KEY_ID \ --issuer-id YOUR_ISSUER_ID \ --private-key-path ~/.asc/AuthKey_XXXXXX.p8 -# Multiple accounts -asc auth login --key-id K1 --issuer-id I1 --private-key-path work.p8 --name work -asc auth login --key-id K2 --issuer-id I2 --private-key-path personal.p8 --name personal - -asc auth update --vendor-number 88012345 # save vendor number for reports -asc auth list # list all saved accounts -asc auth use work # switch active account -asc auth check # → shows active account name + source: "file" -asc auth logout # remove active account -asc auth logout --name personal # remove a specific account -``` - -Credentials are saved to `~/.asc/credentials.json`. All `asc` commands use the active account automatically — no environment variables needed per session. Account names must not contain spaces. - -### Environment variables (alternative) - -```bash -export ASC_KEY_ID="YOUR_KEY_ID" -export ASC_ISSUER_ID="YOUR_ISSUER_ID" -export ASC_PRIVATE_KEY_PATH="~/.asc/AuthKey_XXXXXX.p8" -# or: export ASC_PRIVATE_KEY="" -``` - -**Resolution order:** `~/.asc/credentials.json` → environment variables. - -## Command Reference - -### Auth & Project - -```bash -asc auth login --key-id --issuer-id --private-key-path [--name alias] [--vendor-number ] -asc auth update [--name alias] --vendor-number -asc auth list -asc auth use -asc auth check -asc auth logout [--name alias] - -asc init # auto-detect app from *.xcodeproj bundle ID -asc init --name "My App" # search by name -asc init --app-id # pin directly — no API call needed -``` - -### Apps & Versions - -```bash -asc apps list -asc versions list --app-id -asc versions create --app-id --version --platform ios -asc versions set-build --version-id --build-id -asc versions check-readiness --version-id -asc apps price-points list --app-id [--territory USA] -asc apps prices set --app-id --base-territory USA --price-point-id # 0.0 point = free -asc versions submit --version-id [--with-products] [--dry-run] -asc version-review-detail get --version-id -asc version-review-detail update --version-id --contact-first-name Jane --contact-email dev@example.com -``` - -### Builds & TestFlight - -```bash -asc builds list [--app-id ] [--platform ] [--version ] -asc builds next-number --app-id --version --platform -asc builds archive --scheme MyApp [--platform ios] [--export-method app-store] [--upload --app-id --version 1.0.0 --build-number 42] -asc builds upload --app-id --file MyApp.ipa --version 1.0.0 --build-number 42 -asc builds uploads list --app-id -asc builds uploads get --upload-id -asc builds uploads delete --upload-id -asc builds add-beta-group --build-id --beta-group-id -asc builds remove-beta-group --build-id --beta-group-id -asc builds update-beta-notes --build-id --locale en-US --notes "What's new" -asc builds set-encryption-compliance --build-id --uses-non-exempt-encryption true|false - -asc testflight groups list [--app-id ] -asc testflight testers list --beta-group-id -asc testflight testers add --beta-group-id --email user@example.com -asc testflight testers remove --beta-group-id --tester-id -asc testflight testers import --beta-group-id --file testers.csv -asc testflight testers export --beta-group-id - -asc beta-review submissions list --build-id -asc beta-review submissions create --build-id -asc beta-review submissions get --submission-id -asc beta-review detail get --app-id -asc beta-review detail update --detail-id [--contact-first-name ] [--notes ] - -asc beta-app-localizations list --app-id -asc beta-app-localizations get --localization-id -asc beta-app-localizations create --app-id --locale en-US [--description ] [--feedback-email ] [--marketing-url ] [--privacy-policy-url ] -asc beta-app-localizations update --localization-id [--description ] [--feedback-email ] -asc beta-app-localizations delete --localization-id - -asc review-submissions list --app-id [--state WAITING_FOR_REVIEW,IN_REVIEW,READY_FOR_REVIEW] [--limit 200] -asc review-submissions get --submission-id -asc review-submissions items list --submission-id [--state REJECTED] -asc review-submissions create --app-id [--platform ios] -asc review-submissions items add --submission-id --iap-version-id # or --version-id / --subscription-version-id / --subscription-group-version-id -asc review-submissions submit --submission-id -asc iap versions list --iap-id # also: subscriptions / subscription-groups versions list - -# Rejected? Read App Review's actual message (iris cookie auth — see docs/features/resolution-center.md) -asc iris resolution-center get --submission-id --plain-text -``` - -### Xcode Cloud - -```bash -asc xcode-cloud products list [--app-id ] -asc xcode-cloud workflows list --product-id -asc xcode-cloud builds list --workflow-id -asc xcode-cloud builds get --build-run-id -asc xcode-cloud builds start --workflow-id [--clean] -``` - -### Customer Reviews - -```bash -# List all reviews for an app -asc reviews list --app-id - -# Get a specific review -asc reviews get --review-id - -# Respond to a review -asc review-responses create --review-id --response-body "Thank you for your feedback!" - -# Get the response to a review -asc review-responses get --review-id - -# Delete a response -asc review-responses delete --response-id -``` - -### Game Center - -```bash -# Get Game Center configuration (detail-id needed for subsequent commands) -asc game-center detail get --app-id - -# Achievements -asc game-center achievements list --detail-id -asc game-center achievements create --detail-id --reference-name "First Steps" --vendor-identifier first_steps --points 10 -asc game-center achievements create --detail-id --reference-name --vendor-identifier --points [--show-before-earned] [--repeatable] -asc game-center achievements delete --achievement-id - -# Leaderboards -asc game-center leaderboards list --detail-id -asc game-center leaderboards create --detail-id --reference-name "All Time High" --vendor-identifier all_time_high --score-sort-type DESC -asc game-center leaderboards create --detail-id --reference-name --vendor-identifier --score-sort-type ASC|DESC [--submission-type BEST_SCORE|MOST_RECENT_SCORE] -asc game-center leaderboards delete --leaderboard-id -``` - -### Power & Performance - -```bash -# App-level performance metrics (launch time, hang rate, memory, etc.) -asc perf-metrics list --app-id -asc perf-metrics list --app-id --metric-type LAUNCH -asc perf-metrics list --build-id --metric-type HANG - -# Diagnostic signatures (hang/disk-write/launch hotspots) -asc diagnostics list --build-id -asc diagnostics list --build-id --diagnostic-type HANGS - -# Diagnostic logs (call stacks for a signature) -asc diagnostic-logs list --signature-id -``` - -### Metadata - -```bash -# Version localizations (What's New, description, keywords) -asc version-localizations list --version-id -asc version-localizations create --version-id --locale zh-Hans -asc version-localizations update --localization-id --whats-new "Bug fixes" - -# App info localizations (name, subtitle, privacy policy) -asc app-infos list --app-id -asc app-infos update --app-info-id --primary-category GAMES --primary-subcategory-one GAMES_ACTION -asc app-categories list [--platform IOS] -asc app-info-localizations list --app-info-id -asc app-info-localizations create --app-info-id --locale zh-Hans --name "我的应用" -asc app-info-localizations update --localization-id --name "My App" --subtitle "Do things faster" -asc app-info-localizations delete --localization-id - -# Age rating -asc age-rating get --app-info-id -asc age-rating update --declaration-id --violence-realistic NONE --gambling false --kids-age-band NINE_TO_ELEVEN -``` - -### Screenshots & Previews - -```bash -# Screenshots -asc screenshot-sets list --localization-id -asc screenshot-sets create --localization-id --display-type APP_IPHONE_67 -asc screenshots list --set-id -asc screenshots upload --set-id --file ./screen.png - -# Video previews -asc app-preview-sets list --localization-id -asc app-preview-sets create --localization-id --preview-type IPHONE_67 -asc app-previews list --set-id -asc app-previews upload --set-id --file ./preview.mp4 [--preview-frame-time-code 00:00:05] -``` - -### App Shots (AI screenshot generation) - -Two modes: **Gallery** (all screenshots styled as a coordinated set) and **Single Template** (one screenshot at a time). Optional AI themes via plugins add colors, backgrounds, and floating decorations. - -```bash -# --- Templates (single shot) --- -asc app-shots templates list # browse available templates -asc app-shots templates list --size portrait --output table -asc app-shots templates apply --id top-hero \ - --screenshot screen.png --headline "Ship Faster" \ - --preview html > preview.html && open preview.html # preview in browser -asc app-shots templates apply --id top-hero \ - --screenshot screen.png --headline "Ship Faster" \ - --preview image --image-output marketing.png # export to PNG - -# --- Gallery templates (multi-screen sets) --- -asc app-shots gallery-templates list --output table -asc app-shots gallery-templates get --id neon-pop --preview > gallery.html && open gallery.html - -# --- Gallery mode (all screenshots at once) --- -asc app-shots gallery create \ - --app-name "MyApp" \ - --screenshots screen-0.png screen-1.png screen-2.png - -# --- Themes (plugin-provided, AI-powered styling) --- -asc app-shots themes list # browse themes -asc app-shots themes get --id space --pretty # theme detail -asc app-shots themes apply --theme space --template top-hero \ - --screenshot screen.png --headline "Ship Faster" # AI-styled HTML - -# Two-step workflow: generate ThemeDesign once, apply to many (no extra AI calls) -asc app-shots themes design --id luxury > design.json -asc app-shots themes apply-design --design design.json \ - --template top-hero --screenshot screen-0.png --headline "Feature 1" \ - --preview html > s0.html -asc app-shots themes apply-design --design design.json \ - --template top-hero --screenshot screen-1.png --headline "Feature 2" \ - --preview image --image-output s1.png - -# --- Gemini AI enhancement --- -asc app-shots config --gemini-api-key KEY # save key once -asc app-shots generate --file screen.png # enhance with Gemini -asc app-shots generate --file screen.png --device-type APP_IPHONE_67 -asc app-shots generate --file screen.png --style-reference ~/ref.png -``` - -### Monetization - -Full IAP and subscription management — lifecycle, pricing, offer codes, promotional/win-back offers, review screenshots, promotional images, and App Store promoted slots. Every command also serves as a REST endpoint when running `asc web-server`. Affordances embedded in JSON output are state-aware — they only suggest the next legal action. - -```bash -# Quick taste — see the linked docs for the full command surface. -asc iap list --app-id -asc iap create --app-id --reference-name "Gold Coins" --product-id "com.app.gold" --type consumable -asc iap submit --iap-id - -asc subscription-groups create --app-id --reference-name "Premium" -asc subscriptions create --group-id --name "Monthly" --product-id "com.app.monthly" --period ONE_MONTH -asc subscriptions prices set --subscription-id --territory USA --price-point-id - -asc promoted-purchases create --app-id --iap-id --visible --enabled - -asc experiments create --app-id --name "Icon test" --traffic-proportion 30 -asc experiment-treatments create --experiment-id --name "Blue icon" --app-icon-name AppIcon-Blue -``` - -| Area | Documentation | -|------|---------------| -| In-app purchases & subscriptions overview | [docs/features/iap-subscriptions.md](docs/features/iap-subscriptions.md) | -| Lifecycle (update / delete / unsubmit across IAP, subscription, group, intro offers) | [iap-subscriptions/lifecycle.md](docs/features/iap-subscriptions/lifecycle.md) | -| IAP base-territory pricing & subscription per-territory pricing (incl. `proceedsYear2`) | [iap-subscriptions/pricing.md](docs/features/iap-subscriptions/pricing.md) | -| Offer codes (3-level: code → custom → one-time + redemption CSV) | [iap-subscriptions/offer-codes.md](docs/features/iap-subscriptions/offer-codes.md) | -| Subscription group localizations (Custom App Name per locale) | [iap-subscriptions/group-localizations.md](docs/features/iap-subscriptions/group-localizations.md) | -| Promotional offers (in-app, with per-territory inline pricing) | [iap-subscriptions/promotional-offers.md](docs/features/iap-subscriptions/promotional-offers.md) | -| Win-back offers (lapsed subscribers, eligibility rules + priority) | [iap-subscriptions/win-back-offers.md](docs/features/iap-subscriptions/win-back-offers.md) | -| Review screenshots & 1024×1024 promotional images | [iap-subscriptions/review-assets.md](docs/features/iap-subscriptions/review-assets.md) | -| Promoted purchases (App Store product page slots) | [docs/features/promoted-purchases.md](docs/features/promoted-purchases.md) | -| Product Page Optimization tests (experiments, treatments, treatment localizations) | [docs/features/product-page-optimization.md](docs/features/product-page-optimization.md) | -| Territory availability (apps, IAPs, subscriptions) | [docs/features/iap-subscription-availability.md](docs/features/iap-subscription-availability.md) | - -### Code Signing - -```bash -asc bundle-ids list [--platform ios|macos|universal] [--identifier com.example.app] -asc bundle-ids create --name "My App" --identifier com.example.app --platform ios -asc bundle-ids delete --bundle-id-id - -asc certificates list [--type IOS_DISTRIBUTION] [--limit 200] [--expired-only] [--before 2026-11-01T00:00:00Z] -asc certificates create --type IOS_DISTRIBUTION --csr-content "$(cat MyApp.certSigningRequest)" -asc certificates revoke --certificate-id - -asc devices list [--platform ios|macos] -asc devices register --name "My iPhone" --udid --platform ios - -asc profiles list [--bundle-id-id ] [--type IOS_APP_STORE] -asc profiles create --name "My Profile" --type IOS_APP_STORE --bundle-id-id --certificate-ids -asc profiles delete --profile-id -``` - -### Team Management - -```bash -# List all team members -asc users list - -# Filter by role -asc users list --role DEVELOPER --output table - -# Update a member's roles -asc users update --user-id --role APP_MANAGER --role DEVELOPER - -# Revoke access (e.g. on offboarding) -asc users remove --user-id - -# List pending invitations -asc user-invitations list - -# Invite a new member -asc user-invitations invite --email new@example.com --first-name Alex --last-name Smith --role DEVELOPER - -# Cancel a pending invitation -asc user-invitations cancel --invitation-id -``` - -### Reports - -```bash -# Daily sales (latest — --report-date optional for DAILY only) -# --vendor-number auto-resolved from active account if saved via auth login/update -asc sales-reports download --report-type SALES --sub-type SUMMARY --frequency DAILY - -# Weekly/monthly/yearly require --report-date -asc sales-reports download --report-type SUBSCRIPTION --sub-type SUMMARY --frequency MONTHLY --report-date 2024-01 - -# Rollup across a date range -asc sales-reports summary --from 2026-05-13 --to 2026-05-18 - -# Financial report (--report-date always required) -asc finance-reports download --report-type FINANCIAL --region-code US --report-date 2024-01 - -# Explicit vendor number override -asc sales-reports download --vendor-number --report-type SALES --sub-type SUMMARY --frequency DAILY - -# Analytics (multi-step workflow) -asc analytics-reports request --app-id --access-type ONE_TIME_SNAPSHOT -asc analytics-reports list --app-id -asc analytics-reports reports --request-id --category COMMERCE -asc analytics-reports instances --report-id --granularity DAILY -asc analytics-reports segments --instance-id -``` - -### Plugins - -```bash -asc plugins list -asc plugins install ./my-plugin -asc plugins uninstall --name slack-notify -asc plugins enable --name slack-notify -asc plugins disable --name slack-notify -asc plugins run --name slack-notify --event build.uploaded -``` - -### Iris (Private API) - -```bash -# Check cookie session status -asc iris status --pretty - -# List apps via iris -asc iris apps list --pretty - -# Create a new app -asc iris apps create --name "My App" --bundle-id com.example.app --sku com.example.app --pretty - -# Multi-platform with custom version -asc iris apps create --name "My App" --bundle-id com.example.app --sku MYSKU \ - --platforms IOS MAC_OS --version 2.0 -``` - -Authentication: log in to [appstoreconnect.apple.com](https://appstoreconnect.apple.com) in your browser — cookies are extracted automatically. For CI/CD, set `ASC_IRIS_COOKIES`. - -### Simulators - -```bash -# List available iOS simulators -asc simulators list --output table -asc simulators list --booted --pretty - -# Boot / shutdown -asc simulators boot --udid -asc simulators shutdown --udid - -# Start web server (opens cc.asccli.app) -asc web-server -``` - -Streaming and interaction available via the [ASC Pro plugin](docs/features/plugin-ui-architecture.md). - -### Output & TUI - -```bash -asc apps list # JSON (default) -asc apps list --output table # aligned table -asc apps list --output markdown # markdown table -asc apps list --output json --pretty # pretty-printed JSON - -asc tui # interactive browser — arrow keys, Enter to drill in, Escape to go back -``` - -## Release Workflow - -A full App Store release from build upload to review submission: - -```bash -# 1. Upload build and wait for processing -# Option A: Archive from Xcode project and upload in one step -asc builds archive --scheme MyApp --upload --app-id APP_ID --version 1.2.0 --build-number 55 -# Option B: Upload a pre-built IPA/PKG -asc builds upload --app-id APP_ID --file ./MyApp.ipa --version 1.2.0 --build-number 55 --wait - -# 2. Distribute to TestFlight -GROUP_ID=$(asc testflight groups list --app-id APP_ID | jq -r '.data[0].id') -BUILD_ID=$(asc builds list --app-id APP_ID | jq -r '.data[0].id') -asc builds add-beta-group --build-id "$BUILD_ID" --beta-group-id "$GROUP_ID" -asc builds update-beta-notes --build-id "$BUILD_ID" --locale en-US --notes "What's new in 1.2.0" - -# 3. Prepare the App Store version -VERSION_ID=$(asc versions list --app-id APP_ID | jq -r '.data[0].id') -asc versions set-build --version-id "$VERSION_ID" --build-id "$BUILD_ID" - -# 4. Update What's New -LOC_ID=$(asc version-localizations list --version-id "$VERSION_ID" | jq -r '.data[0].id') -asc version-localizations update --localization-id "$LOC_ID" --whats-new "Bug fixes and performance improvements" - -# 5. Pre-flight check, then submit -asc versions check-readiness --version-id "$VERSION_ID" --pretty -asc versions submit --version-id "$VERSION_ID" +asc apps list # find your app ID +asc init --app-id # pin it — skip --app-id on every future command ``` -## Feature Guides +Multiple accounts, environment variables for CI: [auth](docs/features/asc-auth/README.md). Building from source: [CONTRIBUTING.md](CONTRIBUTING.md). -Detailed documentation for each feature: +## Built for Agents: CAEOAS -- [Auth](docs/features/asc-auth.md) — multi-account credential management; login, list, use, logout, check -- [Version Localizations](docs/features/version-localizations.md) — What's New, description, keywords -- [Screenshots](docs/features/screenshots.md) — screenshot sets and image uploads -- [App Previews](docs/features/app-previews.md) — preview sets and video uploads -- [App Info](docs/features/app-infos.md) — name, subtitle, privacy policy, categories, age rating -- [TestFlight](docs/features/testflight.md) — beta groups, tester management, CSV import/export -- [Beta Review](docs/features/beta-review.md) — submit builds for beta app review, manage review contact details -- [Review Submissions](docs/features/review-submissions.md) — inspect App Store review submissions, drill into per-item state to find what Apple rejected -- [Xcode Cloud](docs/features/xcode-cloud.md) — products, workflows, build runs, start builds -- [Builds Archive](docs/features/builds-archive.md) — archive Xcode projects, export IPA/PKG, optional upload chaining -- [Builds Upload](docs/features/builds-upload.md) — upload IPA/PKG, TestFlight distribution, beta notes -- [Code Signing](docs/features/code-signing.md) — bundle IDs, certificates, devices, profiles -- [Version Check-Readiness](docs/features/version-check-readiness.md) — pre-flight submission checks -- [In-App Purchases & Subscriptions](docs/features/iap-subscriptions.md) — IAPs, subscriptions, offers, pricing -- [App Shots](docs/features/app-shots.md) — templates, galleries, Gemini enhancement, and export -- [App Shots Themes](docs/features/app-shots-themes.md) — plugin-provided visual themes with two-step ThemeDesign workflow -- [Plugins](docs/features/plugins.md) — custom event handlers (Slack, Telegram, webhooks) -- [App Wall](docs/features/app-wall.md) — community showcase; `apps.json` format and architecture -- [Users & Roles](docs/features/users.md) — team member management, role assignment, invitation lifecycle; directory integration for automated access control -- [Customer Reviews](docs/features/customer-reviews.md) — list reviews, respond to feedback, manage review responses -- [Game Center](docs/features/game-center.md) — achievements (list, create, delete) and leaderboards (list, create, delete) -- [Power & Performance](docs/features/performance.md) — performance metrics (app/build), diagnostic signatures, diagnostic logs -- [Reports](docs/features/reports.md) — sales, finance, and analytics reports; TSV parsing, multi-step analytics workflow -- [Iris (Private API)](docs/features/iris.md) — cookie-based auth; create apps, list apps via the iris private API -- [Simulators](docs/features/simulators.md) — list, boot, shutdown; streaming via plugin -- [Web Server](docs/features/web-server-architecture.md) — Hummingbird API server with plugin system -- [Plugins](docs/features/plugin-ui-architecture.md) — extend CLI with routes, commands, affordances, and UI - -## Design: CAEOAS - -REST has **HATEOAS** — responses embed URLs so clients navigate without knowing the API. This CLI has **CAEOAS** (Commands As the Engine Of Application State): responses embed ready-to-run CLI commands so agents navigate without memorising the command tree. - -```bash -$ asc versions list --app-id app-abc -``` +REST has **HATEOAS** — responses embed URLs so clients navigate without knowing the API. asc has **CAEOAS** (Commands As the Engine Of Application State): every response embeds ready-to-run commands, so an agent navigates without memorising the command tree. ```jsonc +// asc versions list --app-id app-abc { "id": "v1", "versionString": "2.1.0", @@ -611,117 +46,37 @@ $ asc versions list --app-id app-abc } ``` -Affordances are state-aware — `submitForReview` only appears when `isEditable == true`. See [docs/design.md](docs/desgin.md) for the full pattern. - -## Use as a Swift Package (SPM) +JSON is the default; add `--output table` or `--output markdown` for people, or run `asc tui` to browse interactively. `asc web-server` serves the same commands as a REST API. More in [docs/design.md](docs/design.md). -`asc-cli` exposes an `ASCKit` library product so you can embed App Store Connect automation directly into your own Swift tool, script, or app without using the CLI binary. +## What It Covers -### Add the dependency - -In your `Package.swift`: - -```swift -dependencies: [ - .package(url: "https://github.com/tddworks/asc-cli.git", from: "0.1.0"), -], -targets: [ - .target( - name: "MyTool", - dependencies: [ - .product(name: "ASCKit", package: "asc-cli"), - ] - ), -] -``` - -### Example usage - -```swift -import Domain -import Infrastructure - -// 1. Provide credentials (inline, env vars, or your own AuthProvider) -let credentials = AuthCredentials( - keyID: "YOUR_KEY_ID", - issuerID: "YOUR_ISSUER_ID", - privateKeyPEM: """ - -----BEGIN PRIVATE KEY----- - ... - -----END PRIVATE KEY----- - """ -) - -// 2. A simple inline AuthProvider that wraps fixed credentials -struct StaticAuthProvider: AuthProvider { - let credentials: AuthCredentials - func resolve() throws -> AuthCredentials { credentials } -} - -// 3. Create any repository via ClientFactory -let factory = ClientFactory() -let authProvider = StaticAuthProvider(credentials: credentials) - -let appRepo = try factory.makeAppRepository(authProvider: authProvider) -let apps = try await appRepo.listApps(limit: 50) -apps.data.forEach { print($0.name, $0.bundleID) } - -// Build repository example -let buildRepo = try factory.makeBuildRepository(authProvider: authProvider) -let builds = try await buildRepo.listBuilds(appId: "YOUR_APP_ID", limit: 10) -``` - -### Available factories (ClientFactory) - -`ClientFactory` provides a `make*` method for every domain area: - -| Method | Repository protocol | -|--------|---------------------| -| `makeAppRepository` | `AppRepository` | -| `makeBuildRepository` | `BuildRepository` | -| `makeVersionRepository` | `VersionRepository` | -| `makeTestFlightRepository` | `TestFlightRepository` | -| `makeScreenshotRepository` | `ScreenshotRepository` | -| `makeAppInfoRepository` | `AppInfoRepository` | -| `makeSubmissionRepository` | `SubmissionRepository` | -| `makeInAppPurchaseRepository` | `InAppPurchaseRepository` | -| `makeSubscriptionRepository` | `SubscriptionRepository` | -| `makeGameCenterRepository` | `GameCenterRepository` | -| `makeUserRepository` | `UserRepository` | -| `makeReportRepository` / `makeAnalyticsReportRepository` | `ReportRepository` / `AnalyticsReportRepository` | -| `makeCustomerReviewRepository` | `CustomerReviewRepository` | -| `makeCertificateRepository` / `makeProfileRepository` / `makeDeviceRepository` | Code-signing repositories | -| `makeXcodeCloudProductRepository` | `XcodeCloudProductRepository` | -| … and more | see `Sources/Infrastructure/Client/ClientFactory.swift` | - - -## Development - -```bash -swift build # build -swift test # run tests (Chicago School TDD) -swift format --in-place --recursive Sources Tests -``` - -**Architecture:** - -``` -Sources/ -├── Domain/ # Pure value types, @Mockable protocols — zero I/O -├── Infrastructure/ # SDK adapters (appstoreconnect-swift-sdk), parent ID injection -└── ASCCommand/ # CLI commands, output formatting, TUI -``` - -Unidirectional dependency: `ASCCommand → Infrastructure → Domain` - -**Dependencies:** -- [appstoreconnect-swift-sdk](https://github.com/AvdLee/appstoreconnect-swift-sdk) -- [swift-argument-parser](https://github.com/apple/swift-argument-parser) -- [TauTUI](https://github.com/steipete/TauTUI) -- [Mockable](https://github.com/Kolos65/Mockable) -- [SweetCookieKit](https://github.com/steipete/SweetCookieKit) - -See [CHANGELOG.md](CHANGELOG.md) for version history. +| Area | What you can do | +| --- | --- | +| **Apps & Versions** | Set the app's price or make it free, create versions, link builds, pre-flight checks, submit for review — with first-time IAPs and subscriptions in the same submission → [pricing](docs/features/app-pricing/README.md) · [readiness](docs/features/version-check-readiness/README.md) · [submit with products](docs/features/submit-with-products/README.md) · [review submissions](docs/features/review-submissions/README.md) | +| **Builds** | Archive Xcode projects, export IPA/PKG, upload, set encryption compliance → [archive](docs/features/builds-archive/README.md) · [upload](docs/features/builds-upload/README.md) | +| **TestFlight** | Beta groups, testers (CSV import/export), beta review, per-locale beta app description → [testflight](docs/features/testflight/README.md) · [beta review](docs/features/beta-review/README.md) · [beta localizations](docs/features/beta-app-localizations/README.md) | +| **Metadata** | What's New, description, keywords; name, subtitle, privacy policy; categories; age rating; review contact → [version localizations](docs/features/version-localizations/README.md) · [app info](docs/features/app-infos/README.md) · [age rating](docs/features/age-rating/README.md) · [review detail](docs/features/version-review-detail/README.md) | +| **Screenshots & Previews** | Screenshot sets and uploads, video previews → [screenshots](docs/features/screenshots/README.md) · [previews](docs/features/app-previews/README.md) | +| **App Shots** | AI screenshot generation: templates, gallery sets, plugin themes, Gemini enhancement → [app shots](docs/features/app-shots/README.md) · [themes](docs/features/app-shots-themes/README.md) | +| **Monetization** | IAPs, subscriptions, intro/promotional/win-back offers, offer codes, per-territory pricing, promoted purchases, availability → [IAP & subscriptions](docs/features/iap-subscriptions/README.md) · [promoted purchases](docs/features/promoted-purchases/README.md) · [availability](docs/features/iap-subscription-availability/README.md) | +| **Product Page Optimization** | A/B test your product page with alternate icons → [experiments](docs/features/product-page-optimization/README.md) | +| **Code Signing** | Bundle IDs, certificates, devices, provisioning profiles → [code signing](docs/features/code-signing/README.md) | +| **Customer Reviews** | Read reviews and respond → [reviews](docs/features/customer-reviews/README.md) | +| **Team** | Members, roles, invitations → [users](docs/features/asc-users/README.md) | +| **Xcode Cloud** | Products, workflows, start and inspect builds → [xcode cloud](docs/features/xcode-cloud/README.md) | +| **Insights** | Sales, finance and analytics reports; performance metrics and diagnostics → [reports](docs/features/reports/README.md) · [performance](docs/features/performance/README.md) | +| **App Clips & Game Center** | App Clip experiences; achievements and leaderboards → [app clips](docs/features/app-clips/README.md) · [game center](docs/features/game-center/README.md) | +| **Iris (private API)** | Web-UI features with no public API: create apps, read App Review's rejection messages → [iris](docs/features/iris/README.md) · [resolution center](docs/features/resolution-center/README.md) | +| **Extend** | Plugins, skills, local simulators, REST server → [plugins](docs/features/plugins/README.md) · [skills](docs/features/skills/README.md) · [simulators](docs/features/simulators/README.md) · [REST API](docs/features/rest-api/README.md) | + +Every feature, one line each: [docs index](docs/README.md). Every command and flag: [command reference](docs/commands.md) or `asc --help`. + +## More + +- [Release workflow](docs/release.md) — upload → TestFlight → submit, end to end +- [Use as a Swift package](docs/library.md) — embed `ASCKit` in your own tool +- [Contributing](CONTRIBUTING.md) — build, test, and where things go +- [Changelog](CHANGELOG.md) ## Sponsors @@ -735,9 +90,7 @@ Apps that use and support asc-cli development: ## App Wall -Apps built and published using asc-cli. To add yours, edit [`homepage/apps.json`](homepage/apps.json) and open a pull request — see [docs/features/app-wall.md](docs/features/app-wall.md) for the format. - -View the live wall at [asccli.app/#app-wall](https://asccli.app/#app-wall). +Apps built and published using asc-cli. To add yours, edit [`homepage/apps.json`](homepage/apps.json) and open a pull request — see [docs/features/app-wall/README.md](docs/features/app-wall/README.md) for the format. View the live wall at [asccli.app/#app-wall](https://asccli.app/#app-wall). ## License diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..2242bba6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,55 @@ + +# asc-cli docs + +New here? Start with the [README](../README.md). For every flag, see the [command reference](commands.md). + +| Feature | What it's for | +|---|---| +| [age-rating](features/age-rating/README.md) | Read and update an app's age rating declaration (content descriptors, kids age band, regional overrides). Use when answering the age rating questionnaire or changing the store age rating. | +| [app-clips](features/app-clips/README.md) | Manage App Clips, their default experiences, and the localized subtitles shown on App Clip cards. Use when setting up or editing an App Clip card. | +| [app-icons](features/app-icons/README.md) | REST-only option that enriches the apps list with each app's icon as a sizable image template. Use when a client needs to render app icons from asc web-server. | +| [app-info-localizations](features/app-info-localizations/README.md) | Manage per-locale app metadata (name, subtitle, privacy policy URL and text) on the App Store listing. Use when renaming the app, setting a subtitle, or adding a new listing locale. | +| [app-infos](features/app-infos/README.md) | Manage app-level metadata (name, subtitle, privacy policy, categories) that persists across versions. Use when renaming an app, adding a locale's name/subtitle, or setting App Store categories. | +| [app-previews](features/app-previews/README.md) | Create App Store video preview sets per device type and upload preview videos. Use when adding or checking app preview videos on a version's product page. | +| [app-pricing](features/app-pricing/README.md) | List an app's price points and set its price, including making it free. Use before a first submission, which Apple refuses until the app has a price schedule. | +| [app-shots](features/app-shots/README.md) | Compose App Store marketing screenshots from templates and gallery sets, preview as HTML or export PNG, and optionally enhance with Gemini. Use when turning raw app screenshots into styled store images. | +| [app-shots-themes](features/app-shots-themes/README.md) | Browse plugin-provided visual themes and apply them to App Store screenshot templates, either per slide via AI or once-and-reuse via a cached ThemeDesign. Use when restyling app-shots with colors, backgrounds, and decorations. | +| [app-wall](features/app-wall/README.md) | Add your App Store apps to the asc community app wall on asccli.app by opening a GitHub pull request. Use when showcasing apps published with asc. | +| [asc-auth](features/asc-auth/README.md) | Save App Store Connect API keys as named accounts, switch between them, or use environment variables. Use when setting up credentials, managing several teams, or configuring CI. | +| [asc-users](features/asc-users/README.md) | Manage App Store Connect team members, their roles, and pending user invitations. Use when onboarding or offboarding people or changing someone's access. | +| [asc-web-management](features/asc-web-management/README.md) | Design notes for the original bundled browser console (asc web) that ran asc commands through a local server. Use when working on the web console's pages, inline editing or command-execution security. | +| [beta-app-localizations](features/beta-app-localizations/README.md) | Manage the TestFlight Beta App Description and per-locale beta metadata (feedback email, marketing and privacy URLs). Use when preparing a build for external TestFlight testers. | +| [beta-review](features/beta-review/README.md) | Submit a build for TestFlight beta app review and manage the beta review contact and demo account details. Use when sending a build to external testers for the first time. | +| [builds-archive](features/builds-archive/README.md) | Archive an Xcode project with xcodebuild, export an IPA or PKG, and optionally upload it to App Store Connect in one command. Use when going from an Xcode project to TestFlight without running xcodebuild by hand. | +| [builds-upload](features/builds-upload/README.md) | Upload IPA/PKG builds, add them to TestFlight groups, set beta notes and export compliance, and link a build to a version. Use when shipping a new binary to TestFlight or the App Store. | +| [code-signing](features/code-signing/README.md) | Manage bundle IDs, signing certificates, test devices and provisioning profiles. Use when setting up code signing for CI/CD or cleaning up expired certificates. | +| [command-center-react](features/command-center-react/README.md) | Design notes for the React + Vite + TypeScript rewrite of the Command Center web UI. Use when working on apps/asc-web/command-center or writing a plugin page for it. | +| [customer-reviews](features/customer-reviews/README.md) | List and read App Store customer reviews, and create or delete developer responses. Use when triaging ratings or replying to reviewers. | +| [game-center](features/game-center/README.md) | Get an app's Game Center configuration and list, create or delete its achievements and leaderboards. Use when setting up Game Center for a game. | +| [iap-subscription-availability](features/iap-subscription-availability/README.md) | Set and inspect which territories an app, in-app purchase or subscription is sold in. Use when setting up availability for a new app or product, or finding out why a territory is blocked. | +| [iap-subscriptions](features/iap-subscriptions/README.md) | Manage in-app purchases and auto-renewable subscriptions end to end, from creation and pricing to offer codes, offers, review assets and submission. Use when setting up or changing anything a user can buy inside an app. | +| [init](features/init/README.md) | Save the current project's app ID, name, bundle ID and review contact to .asc/project.json. Use when setting up a repo so agents and scripts know which app to work on without listing apps every session. | +| [iris](features/iris/README.md) | Call App Store Connect's private iris API with an Apple ID session instead of an API key. Use when you need web-UI-only features such as creating an app, queuing an IAP with the next version, or reading rejection messages. | +| [market](features/market/README.md) | Browse, search, install and uninstall asc plugins from the tddworks/asc-registry marketplace, and publish your own plugin to it. Use when finding a plugin to install or listing a new plugin in the registry. | +| [performance](features/performance/README.md) | Read power and performance metrics (launch time, hangs, memory, disk, battery) and drill into diagnostic signatures and call stacks. Use when checking an app or build for performance regressions. | +| [plugins](features/plugins/README.md) | List, install, uninstall, and update compiled asc plugins that add commands, server routes, UI, and affordances. Use when extending asc with marketplace plugins such as ASC Pro. | +| [product-page-optimization](features/product-page-optimization/README.md) | Run App Store Product Page Optimization tests with up to three alternate product pages (treatments). Use when A/B testing an app icon or per-locale product page against the original. | +| [promoted-purchases](features/promoted-purchases/README.md) | Manage the "Featured In-App Purchases" slots on an app's App Store product page. Use when promoting an IAP or subscription on the product page, or hiding/disabling a promoted slot. | +| [reports](features/reports/README.md) | Download sales, finance and analytics reports from App Store Connect, and roll daily sales into a summary. Use when you need downloads, proceeds or usage data for an app. | +| [resolution-center](features/resolution-center/README.md) | Read App Review's rejection messages, guideline citations and attachments from the Resolution Center via the iris web session. Use when a review submission is UNRESOLVED_ISSUES and you need to know why. | +| [rest-api](features/rest-api/README.md) | Drive asc over HTTP with asc web-server, a HATEOAS REST API whose _links mirror the CLI affordances. Use when an agent, script or web app needs App Store Connect data over REST instead of the CLI. | +| [review-submissions](features/review-submissions/README.md) | Inspect App Store review submissions, their state and which attached item Apple rejected. Use when checking review status or finding what was rejected. | +| [screenshot-editor](features/screenshot-editor/README.md) | Design App Store screenshots (background, device bezel, text) in a browser editor, export a ZIP, and upload it in one command. Use when composing screenshots visually and importing them into a version. | +| [screenshots](features/screenshots/README.md) | List and create screenshot sets and upload screenshots for an App Store version localization. Use when adding or checking App Store screenshots for a version. | +| [simulators](features/simulators/README.md) | List, boot, and shut down local iOS simulators from the CLI. Use when you need a running simulator for testing, screenshots, or streaming via the ASC Pro plugin. | +| [skills](features/skills/README.md) | Browse, install, update and remove the asc Claude Code agent skills. Use when setting up an AI agent to drive asc or keeping its skills current. | +| [submit-with-products](features/submit-with-products/README.md) | Send in-app purchases, subscriptions and subscription groups to App Review in the same submission as an app version. Use when submitting first-time products, which Apple requires to ride with an app version. | +| [testflight](features/testflight/README.md) | Manage TestFlight beta groups and testers, and bulk import/export testers via CSV. Use when distributing a build to testers. | +| [version-check-readiness](features/version-check-readiness/README.md) | Pre-flight check that reports whether an App Store version meets Apple's submission requirements. Use when gating a submit in CI or finding out what blocks a version from review. | +| [version-localizations](features/version-localizations/README.md) | Manage per-locale App Store version text such as What's New, description, keywords, promotional text and URLs. Use when writing release notes or localizing a version's store listing. | +| [version-review-detail](features/version-review-detail/README.md) | Set the App Review contact details, demo account credentials and reviewer notes for an App Store version. Use before submitting a version for review. | +| [web-apps](features/web-apps/README.md) | Browser interfaces for asc, a visual Command Center and a terminal-style Console, backed by a local asc web-server. Use when you want to browse or run asc commands from a browser. | +| [web-server-architecture](features/web-server-architecture/README.md) | How asc web-server bridges the browser UI and the CLI, loads plugins and serves HTTPS. Use when running the local server for asccli.app or building a server plugin. | +| [xcode-cloud](features/xcode-cloud/README.md) | List Xcode Cloud products, workflows and build runs, and start CI builds. Use when triggering or checking an Xcode Cloud build from the terminal or an agent. | + +**Guides:** [Release workflow](release.md) · [Use as a Swift package](library.md) · [Design: CAEOAS and architecture](design.md) · [How these docs are organised](documentation-design/README.md) diff --git a/docs/changelog/0.1.md b/docs/changelog/0.1.md new file mode 100644 index 00000000..7f5378d1 --- /dev/null +++ b/docs/changelog/0.1.md @@ -0,0 +1,728 @@ +# Changelog — 0.1.x + +Archived unchanged from [CHANGELOG.md](../../CHANGELOG.md), which holds the current releases. + +## [0.1.68] - 2026-04-14 + +### Added +- **`asc testflight groups create`** — create external or internal TestFlight beta groups (`--internal` flag toggles internal; `--public-link-enabled` / `--feedback-enabled` for external groups) +- **`asc init` review contact flags** — `--contact-first-name`, `--contact-last-name`, `--contact-phone`, `--contact-email` save review contact info to `.asc/project.json` for reuse across versions +- **`ProjectConfig` review contact fields** — optional `contactFirstName`, `contactLastName`, `contactPhone`, `contactEmail` with `hasReviewContact` computed property +- **`setReviewContact` / `updateReviewContact` affordances** — `ProjectConfig` now suggests setting review contact when missing + +--- + +## [0.1.67] - 2026-04-13 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.66] - 2026-04-09 + +### Added +- **`ThemeDesign` domain model** — composes from Gallery-native types (`GalleryPalette` + `[Decoration]`) for structured theme output. Generated by AI once, applied deterministically to all screenshots +- **`ThemeDesignApplier`** — re-renders through `GalleryHTMLRenderer.renderScreen()` pipeline with overridden palette and merged decorations (no HTML patching) +- **`GalleryPalette.textColor`** — optional explicit text color, overrides the auto-detect heuristic +- **`Decoration.label()` shape** — text/emoji decorative elements (e.g. `Decoration(shape: .label("✨"), ...)`) +- **`DecorationAnimation`** — float, drift, pulse, spin, twinkle animations for decorations +- **`ScreenLayout.withDecorations()`** — creates a copy with additional decorations +- **`GalleryHTMLRenderer.renderDecorations()`** — renders `ScreenLayout.decorations` (previously dead code) using `cqi` units +- **`buildDesignContext()` on `ScreenTheme`** — prompt method that instructs AI to return `ThemeDesign` JSON +- **`design()` on `ThemeProvider`/`ThemeRepository`** — generate a ThemeDesign from AI in one call +- **`--design-only` / `--apply-design` CLI flags** — batch theme workflow +- **REST endpoints** — `POST /app-shots/themes/design` and `POST /app-shots/themes/apply-design` + +### Changed +- **`GalleryHTMLRenderer` refactored to Mustache templates** — all HTML extracted from Swift into 7 `.mustache` template files using [swift-mustache](https://github.com/hummingbird-project/swift-mustache). The renderer only builds context dictionaries; all HTML, CSS colors (via CSS custom properties in `theme-vars.mustache`), and keyframe animations live in templates. Templates are pre-compiled at startup via `MustacheLibrary` for performance. Preview rendering is cached per template ID. +- **`DecorationShape.displayCharacter`** — computed property on the model instead of renderer logic +- **`GalleryPalette.isLight` + `headlineColor`** — theme detection and text color derivation moved from renderer to palette +- **`Decoration` extended** — new optional fields: `color`, `background`, `borderRadius`, `animation` +- **Theme selection no longer requires auto-compose** — clicking a theme applies immediately to slides with existing preview HTML via `ThemeDesign` (1 AI call for design, then deterministic apply to all slides) +- **Blitz plugin: `design()` implemented** — generates `ThemeDesign` via compose bridge `mode: "design"`, enabling the fast design→apply-design flow + +--- + +## [0.1.65] - 2026-04-06 + +### Changed +- **`Presentable` protocol** — domain models own their table headers and row values; eliminates `headers:`/`rowMapper:` boilerplate from 37 list commands +- **REST controllers with DI** — routes rewritten as controller structs (`AppsController`, `CodeSigningController`, etc.) with injected repo dependencies; repos created once at server startup, not per request +- **No more `Command.parse([])` in REST layer** — controllers call domain repos and operations directly; CLI and REST are equal thin adapters +- **`AffordanceRegistry` uses structured `Affordance`** — plugin affordances now render to both CLI commands and REST `_links` (previously CLI-only) +- **`ScreenshotTemplate` domain operations** — `apply(content:)`, `renderFragment(content:)` as rich domain methods; `ThemedPage` value type for page wrapping +- **`ScreenshotTemplate` Codable includes `previewHTML` and `deviceCount`** — REST consumers get preview data without special handling + +--- + +## [0.1.64] - 2026-04-05 + +### Added +- **REST API with HATEOAS** — `GET /api/v1` entry point plus 12 resource endpoints (apps, versions, builds, testflight, certificates, bundle-ids, devices, profiles, simulators, plugins, territories) calling domain repositories directly (in-process, no subprocess), returning JSON with `_links` for agent navigation +- **`APIRoot` model** — HATEOAS entry point at `GET /api/v1` listing all available top-level resources with navigable `_links` +- **Structured `Affordance` type** — single source of truth for both CLI commands and REST links; models define affordances once, rendered to either format by `OutputFormatter` +- **`APILink` and `AffordanceMode`** — domain types supporting dual-mode affordance rendering (`.cli` → `"affordances"`, `.rest` → `"_links"`) +- **`RESTPathResolver`** — resolves CLI command + params into REST API paths using a route table covering 35+ resource types across the full domain hierarchy +- **HTML-to-PNG export** — `--preview image` option on `asc app-shots templates apply` and `asc app-shots themes apply` renders composed HTML to PNG via WebKit, with `--image-output` for custom output path +- **`HTMLRenderer` protocol** — `@Mockable` domain protocol for HTML-to-image rendering, implemented by `WebKitHTMLRenderer` using WKWebView snapshot + +--- + +## [0.1.63] - 2026-04-02 + +### Added +- **Plugin Marketplace** — `asc plugins market list` and `asc plugins market search --query X` to browse and search plugins from [tddworks/asc-registry](https://github.com/tddworks/asc-registry) +- **Multi-source plugin registry** — `PluginSource` protocol with `GitHubPluginSource` implementation; composable sources in `PluginMarketRepository` +- **`asc plugins install --name X`** — download and install `.plugin` bundles from the marketplace +- **`asc plugins uninstall --name X`** — remove installed plugin bundles (matches by slug or registry ID) +- **Plugins page in Command Center** — web UI with Installed and Marketplace tabs, stats bar, install/uninstall with loading spinners +- **Enriched plugin manifest** — `manifest.json` now supports `description`, `author`, `repositoryURL`, `categories`; installed plugins display the same rich info as marketplace listings +- **Centralized plugin affordance merging** — `AffordanceRegistry` affordances merged automatically in `OutputFormatter` via `WithPluginAffordances`; domain models no longer need manual registry calls +- **Example plugin** — `examples/hello-plugin/` demonstrates `AffordanceRegistry`, server routes, and UI affordance handlers +- **Plugin registry repo** — [tddworks/asc-registry](https://github.com/tddworks/asc-registry) with `registry.json` and release assets for ASC Pro + Hello Plugin + +### Changed +- **Unified `Plugin` model** — merged `Plugin` and `MarketPlugin` into a single model with `isInstalled`, `downloadURL?`, `slug?` fields +- **Refactored plugin system** — replaced event-based script plugins with dylib `.plugin` bundle management +- **Removed event plugin system** — deleted `PluginEvent`, `PluginEventPayload`, `PluginResult`, `PluginRunner`, `PluginEventBus` and all infrastructure +- **Removed event bus from commands** — `BuildsUpload` and `VersionsSubmit` no longer emit plugin events +- **Apps page affordance buttons** — plugin affordances (e.g. `greet` from Hello Plugin) render as clickable buttons via `appAffordanceHandlers` registry + +--- + +## [0.1.62] - 2026-04-02 + +### Added +- **`asc web-server`** — Swift/Hummingbird HTTP+WebSocket server replacing Node.js. Single binary, zero external dependencies, HTTPS with self-signed cert +- **Plugin architecture** — `.plugin` bundles in `~/.asc/plugins/` extend the CLI with routes, commands, affordances, and UI. Dylibs built with `dynamic_lookup` (~300KB) +- **`AffordanceRegistry`** — plugins extend domain model affordances at runtime (e.g. pro plugin adds `stream` to booted simulators) +- **Affordance-driven UI** — web app renders buttons from CAEOAS affordances, not hardcoded features. Plugins register handlers via `window.simAffordanceHandlers` +- **Command Center Simulators page** — device list with search/filter, stats cards, affordance-driven actions + +--- + +## [0.1.61] - 2026-03-31 + +### Added +- **Screenshot image URLs** — `asc screenshots list` now returns `imageUrl` for each screenshot, so you can view and render real App Store images directly from CLI output +- **Screenshot platform filtering** — filter and browse screenshots by platform (iPhone, iPad, etc.) in the web UI +- **Simulator screenshot capture** — capture and browse simulator screenshots in the web gallery + +--- + +## [0.1.60] - 2026-03-31 + +### Added +- **`asc simulators list [--booted]`** — list available iOS simulators with state-aware affordances +- **`asc simulators boot --udid `** — boot a simulator +- **`asc simulators shutdown --udid `** — shut down a simulator +- **`asc simulators list/boot/shutdown`** — manage local iOS simulators with state-aware affordances + +--- + +## [0.1.59] - 2026-03-30 + +### Added +- **`asc builds list --platform`** — filter builds by platform (ios, macos, tvos, visionos) +- **`asc builds list --version`** — filter builds by marketing version (e.g. 1.0.0) +- **`asc builds next-number`** — get the next build number for a version/platform, ideal for CI/CD automation (resolves #13) +- **Build platform field** — builds now include platform from preReleaseVersion +- **Build number populated** — `buildNumber` field now maps from `Build.attributes.version` (was always nil) + +### Fixed +- **Global options (`--pretty`, `--output`, `--timeout`) now accepted by all commands** — previously 10 leaf commands (`builds add-beta-group`, `builds remove-beta-group`, `builds uploads delete`, `versions set-build`, `reviews responses delete`, `app-clip-experiences delete`, `app-clip-experience-localizations delete`, `analytics-reports delete`, `auth logout`, `auth use`) rejected these flags with "Unknown option" + +### Changed +- **`asc builds list`** — table output now includes Build Number and Platform columns +- **Build version semantics** — `version` field now holds the marketing version (from PreReleaseVersion) when available; `buildNumber` holds the build string +- **Builds sorted by newest first** — list results default to `-uploadedDate` sort order + +--- + +## [0.1.58] - 2026-03-23 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.57] - 2026-03-23 + +### Changed +- **`asc web-server`** — `/command-center/`, `/console/`, and `/` now redirect (302) to the hosted apps at `asccli.app/command-center` and `asccli.app/console` instead of serving local files that no longer exist; startup banner updated to show hosted URLs + +--- + +## [0.1.56] - 2026-03-21 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.55] - 2026-03-21 + +### Added +- **Iris private API support** — new `asc iris` command namespace for cookie-based App Store Connect private API access +- **`asc iris status`** — check iris cookie session availability (browser or environment source) +- **`asc iris apps list`** — list apps via iris `/v1/appBundles` endpoint +- **`asc iris apps create`** — create a new app with initial version, supports multi-platform (`--platforms IOS MAC_OS`) +- **Cookie-based authentication** — auto-extracts `myacinfo` and session cookies from Chrome/Safari/Firefox via SweetCookieKit, with `ASC_IRIS_COOKIES` env var fallback for CI/CD +- **SweetCookieKit dependency** — browser cookie extraction for iris authentication + +--- + +## [0.1.53] - 2026-03-18 + +### Changed +- **`asc web` → `asc web-server`** — renamed command to reflect its role as a local API proxy, no longer serves static files +- **Removed Hummingbird dependency** — web server replaced with embedded Node.js proxy (`apps/server.js`), reducing binary size and build dependencies +- **Unified web apps under `apps/`** — moved dashboard from `Sources/ASCCommand/Resources/web/` to `apps/asc-web-command-center/`, console from `homepage/asc-web-console/` to `apps/asc-web-console/` +- **Dual-mode API detection** — both web apps auto-detect local proxy (tries relative `/api/run`, then `localhost:8420`, then falls back to mock mode) + +### Added +- **SPM build plugin (`Plugins/EmbedServerJS/`)** — auto-generates Swift source from `apps/server.js` at build time, embedding the API proxy in the binary with `apps/server.js` as single source of truth +- **Command Center nav link on homepage** — added "Command Center" navigation to homepage template with translations in all 12 languages +- **GitHub Pages deployment for web apps** — deploy workflow now copies `apps/asc-web-*` to homepage for static hosting + +### Removed +- `WebServer.swift` (Hummingbird-based static file server) +- `homepage/asc-web-console/server.py` (Python dev server) +- Hummingbird package dependency + +--- + +## [0.1.52] - 2026-03-18 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.51] - 2026-03-17 + +### Added +- `asc web` management dashboard — complete redesign with professional light/dark theme, Plus Jakarta Sans typography, and self-contained CSS (no Tailwind CDN dependency) +- App context selector in sidebar — select an app from the Apps page and all other sections (Versions, Builds, TestFlight, Reviews, IAP, Subscriptions) use that app context automatically +- Rich domain models in web UI — every entity carries parent IDs, semantic booleans (`isLive`, `isEditable`, `isPending`, `isUsable`), and state-aware CAEOAS affordances matching the Swift Domain layer exactly +- Mock/CLI dual data mode — web UI auto-detects the `asc web` backend; falls back to built-in mock data for offline development and demos; toggle via header button +- Dark mode with system preference detection and localStorage persistence +- 15 management pages: Command Center, Apps, Versions, Builds, TestFlight, Submissions, App Info, Screenshots, Reviews, In-App Purchases, Subscriptions, Reports, Code Signing, Xcode Cloud, Users & Roles +- Command Center with release pipeline timeline, quick actions, and stats overview +- Command log modal showing every CLI command executed by the UI +- Standalone development server (`apps/asc-web-command-center/server.js`) for working on the web UI outside of `asc web` + +--- + +## [0.1.50] - 2026-03-15 + +### Added +- `asc territories list` — list all available App Store territories with currency codes +- `asc iap-availability get --iap-id ` — get territory availability for an in-app purchase (includes currency per territory) +- `asc iap-availability create --iap-id --available-in-new-territories --territory USA --territory CHN` — create territory availability for an IAP +- `asc subscription-availability get --subscription-id ` — get territory availability for a subscription (includes currency per territory) +- `asc subscription-availability create --subscription-id --available-in-new-territories --territory USA` — create territory availability for a subscription +- `Territory` domain model with id and currency, shared across availability responses +- `getAvailability` affordance on `InAppPurchase` and `Subscription` models for CAEOAS navigation +- `listTerritories` affordance on availability models for territory discovery +- `asc app-availability get --app-id ` — get per-territory availability with `isAvailable`, `contentStatuses` (blocking reasons), `releaseDate`, and `isPreOrderEnabled` for every territory + +--- + +## [0.1.49] - 2026-03-14 + +### Added +- `asc skills list` — list available skills from the asc-cli repository via `npx skills add tddworks/asc-cli --list` +- `asc skills install --name ` — install a specific skill; `--all` to install all available skills +- `asc skills installed` — show skills installed in `~/.claude/skills/` with CAEOAS affordances +- `asc skills uninstall --name ` — remove an installed skill from `~/.claude/skills/` +- `asc skills check` — check for available skill updates via `npx skills check` +- `asc skills update` — update installed skills via `npx skills update` +- Auto-update checker — non-blocking skill update check on every `asc` command (24h cooldown, CI-aware, disable with `ASC_SKIP_SKILL_CHECK=true`) +- `--app-id` flag on `asc app-wall submit` — accepts App Store Connect app IDs directly, auto-constructs App Store URLs (repeatable, combinable with `--app` and `--developer-id`) + +--- + +## [0.1.48] - 2026-03-13 + +### Changed +- `--developer` on `asc app-wall submit` is now optional — when omitted, the homepage card uses the iTunes artist name instead +- `asc beta-review submissions list --build-id ` — list beta app review submissions for a build +- `asc beta-review submissions create --build-id ` — submit a build for beta (TestFlight external) review +- `asc beta-review submissions get --submission-id ` — get a specific beta review submission +- `asc beta-review detail get --app-id ` — get beta review contact info and demo account details +- `asc beta-review detail update --detail-id ` — update beta review contact info, demo account, and notes + +--- + +## [0.1.47] - 2026-03-13 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.46] - 2026-03-13 + +### Added +- `asc builds archive --scheme ` — archive and export Xcode projects locally via `xcodebuild`, auto-detecting workspace/project from the current directory; supports `--platform`, `--configuration`, `--export-method` (app-store/ad-hoc/development/enterprise), and `--output-dir` +- `--upload` flag on `asc builds archive` chains the exported IPA/PKG directly into App Store Connect upload, combining archive + export + upload in a single command + +--- + +## [0.1.45] - 2026-03-12 + +### Changed +- Subscription offer codes: `asc subscription-offer-codes list/create/update`, `asc subscription-offer-code-custom-codes list/create/update`, `asc subscription-offer-code-one-time-codes list/create/update` — manage offer codes, custom redeemable codes, and one-time use code batches for auto-renewable subscriptions +- IAP offer codes: `asc iap-offer-codes list/create/update`, `asc iap-offer-code-custom-codes list/create/update`, `asc iap-offer-code-one-time-codes list/create/update` — manage offer codes, custom redeemable codes, and one-time use code batches for in-app purchases +- `InAppPurchase` and `Subscription` affordances now include `listOfferCodes` for direct navigation to offer codes + +--- + +## [0.1.44] - 2026-03-12 + +### Added +- Power & performance metrics: `asc perf-metrics list --app-id ` and `asc perf-metrics list --build-id ` — download launch time, hang rate, memory, disk, battery, termination, and animation metrics with `--metric-type` filter +- Diagnostic signatures: `asc diagnostics list --build-id ` — list hang, disk write, and launch diagnostic hotspots with `--diagnostic-type` filter +- Diagnostic logs: `asc diagnostic-logs list --signature-id ` — view call stack metadata for a specific diagnostic signature + +--- + +## [0.1.43] - 2026-03-11 + +### Added +- HTML screenshot generation: `asc app-shots html --plan composition-plan.json` — deterministic App Store screenshot generation with real device mockup frames, no AI or API keys needed +- CompositionPlan format: normalized 0-1 coordinates, multiple devices per slide, text overlays with alignment, gradient backgrounds, per-screen color themes +- Device mockup system: bundled iPhone 17 Pro Max frame with `mockups.json` config; users can add custom mockups to `~/.asc/mockups/` +- TextOverlay `textAlign` property: supports `left`, `center`, `right` alignment for text positioning in composition plans +- Client-side PNG export via html-to-image CDN in generated HTML pages + +--- + +## [0.1.42] - 2026-03-11 + +### Added +- Customer reviews: `asc reviews list --app-id ` and `asc reviews get --review-id ` — list and view customer reviews with rating, title, body, reviewer nickname, and territory +- Review responses: `asc review-responses create --review-id --response-body "text"`, `asc review-responses get --review-id `, `asc review-responses delete --response-id ` — manage developer responses to customer reviews + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.41] - 2026-03-10 + +### Added +- Sales reports: `asc sales-reports download` with support for 10 report types, 5 sub-types, and 4 frequencies +- Finance reports: `asc finance-reports download` with financial and finance detail report types +- Gzip decompression and TSV parsing infrastructure for App Store Connect report downloads +- Analytics reports: `asc analytics-reports request/list/delete/reports/instances/segments` — multi-step analytics workflow with 5 report categories and 3 granularity levels +- `asc auth login --vendor-number ` — save vendor number with account credentials +- `asc auth update --vendor-number ` — add or update vendor number on an existing account +- Vendor number auto-resolution for `sales-reports download` and `finance-reports download` — `--vendor-number` is now optional when saved on the active account + +--- + +## [0.1.40] - 2026-03-09 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.39] - 2026-03-08 + +### Added +- `asc app-clips list --app-id ` — list App Clips for an app; `AppClip` carries `appId` (injected), `bundleId?`; affordances: `listAppClips`, `listExperiences` +- `asc app-clip-experiences list --app-clip-id ` — list default experiences for an App Clip; `AppClipDefaultExperience` carries `appClipId` (injected), `action?` (OPEN/VIEW/PLAY); affordances: `delete`, `listExperiences`, `listLocalizations` +- `asc app-clip-experiences create --app-clip-id [--action OPEN|VIEW|PLAY]` — create a default experience +- `asc app-clip-experiences delete --experience-id ` — delete a default experience +- `asc app-clip-experience-localizations list --experience-id ` — list localizations; `AppClipDefaultExperienceLocalization` carries `experienceId` (injected), `locale`, `subtitle?`; affordances: `delete`, `listLocalizations` +- `asc app-clip-experience-localizations create --experience-id --locale [--subtitle "..."]` — create a localization +- `asc app-clip-experience-localizations delete --localization-id ` — delete a localization +- `AppClipRepository` `@Mockable` protocol with 7 methods covering all CRUD operations +- `asc game-center detail get --app-id ` — get Game Center configuration for an app; `GameCenterDetail` carries `appId` (injected), `isArcadeEnabled`; affordances: `getDetail`, `listAchievements`, `listLeaderboards` +- `asc game-center achievements list --detail-id ` — list Game Center achievements; `GameCenterAchievement` carries `gameCenterDetailId` (injected), `referenceName`, `vendorIdentifier`, `points`, `isShowBeforeEarned`, `isRepeatable`, `isArchived`; affordances: `listAchievements`, `delete` +- `asc game-center achievements create --detail-id --reference-name --vendor-identifier --points [--show-before-earned] [--repeatable]` — create a new achievement +- `asc game-center achievements delete --achievement-id ` — delete an achievement +- `asc game-center leaderboards list --detail-id ` — list Game Center leaderboards; `GameCenterLeaderboard` carries `gameCenterDetailId` (injected), `referenceName`, `vendorIdentifier`, `scoreSortType` (ASC/DESC), `submissionType` (BEST_SCORE/MOST_RECENT_SCORE), `isArchived`; affordances: `listLeaderboards`, `delete` +- `asc game-center leaderboards create --detail-id --reference-name --vendor-identifier --score-sort-type ASC|DESC [--submission-type BEST_SCORE|MOST_RECENT_SCORE]` — create a new leaderboard +- `asc game-center leaderboards delete --leaderboard-id ` — delete a leaderboard +- `ScoreSortType` and `LeaderboardSubmissionType` domain enums +- `GameCenterRepository` `@Mockable` protocol with 7 methods covering all CRUD operations + +--- + +## [0.1.38] - 2026-03-07 + +### Added +- `asc xcode-cloud products list [--app-id ]` — list Xcode Cloud products; `XcodeCloudProduct` carries `appId` (injected from relationship), `name`, `productType`; affordances: `listWorkflows`, `listProducts` +- `asc xcode-cloud workflows list --product-id ` — list CI workflows for a product; `XcodeCloudWorkflow` carries `productId` (injected), `name`, `description`, `isEnabled`, `isLockedForEditing`; affordances: `listBuildRuns`, `listWorkflows` always, `startBuild` only when `isEnabled` +- `asc xcode-cloud builds list --workflow-id ` — list build runs for a workflow; `XcodeCloudBuildRun` carries `workflowId` (injected), `number`, `executionProgress` (PENDING/RUNNING/COMPLETE), `completionStatus` (SUCCEEDED/FAILED/ERRORED/CANCELED/SKIPPED), `startReason`; semantic booleans: `isPending`, `isRunning`, `isComplete`, `isSucceeded`, `hasFailed` +- `asc xcode-cloud builds get --build-run-id ` — get a specific build run by ID +- `asc xcode-cloud builds start --workflow-id [--clean]` — start a new build run; `--clean` flag performs a clean build removing derived data + +--- + +## [0.1.37] - 2026-03-06 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.36] - 2026-03-05 + +### Added +- `asc users list/update/remove` — manage App Store Connect team members; list users filtered by role, update a member's roles (replaces all current roles), or revoke access immediately; `TeamMember` carries `username`, `firstName`, `lastName`, `roles`, `isAllAppsVisible`, `isProvisioningAllowed`; affordances: `remove`, `updateRoles` (pre-filled with current roles) +- `asc user-invitations list/invite/cancel` — manage pending team invitations; invite a new member with one or more roles (`--role DEVELOPER --role APP_MANAGER`); `UserInvitationRecord` carries `email`, `roles`, `expirationDate`; affordance: `cancel`; supports 13 roles; `--all-apps-visible` flag on invite grants access to all apps +- `asc auth list` — list all saved App Store Connect accounts with active status and per-account affordances (`use`, `logout`) +- `asc auth use ` — switch the active App Store Connect account; all subsequent commands use the newly active account +- `asc auth login --name ` — optional `--name` flag to save credentials under a human-readable alias (defaults to `"default"`); first saved account becomes active automatically; subsequent logins with new names are saved alongside existing accounts; account names must not contain spaces (use hyphens or underscores) + +### Changed +- `asc auth logout` — now accepts optional `--name ` flag; removes the named account or the active account if `--name` is omitted +- `asc auth check` — now shows the account `name` field when credentials come from a saved account (omitted for environment-variable credentials) +- `~/.asc/credentials.json` — upgraded to multi-account format `{ "active": "name", "accounts": { ... } }`; old single-credential files are auto-migrated to a `"default"` named account on first use + +--- + +## [0.1.35] - 2026-03-04 + +### Added +- `asc app-wall submit` — submit your app to the community app wall at asccli.app by opening a GitHub pull request against `tddworks/asc-cli`; forks the repo, adds your entry to `homepage/apps.json`, and creates a PR automatically; supports `--developer-id` (auto-fetches all your App Store apps), `--app` (specific App Store URLs, repeatable), `--github`, `--x`; GitHub token resolved from `--github-token` flag, `$GITHUB_TOKEN`, or `gh auth token` + +--- + +## [0.1.34] - 2026-03-02 + +### Fixed +- `asc app-shots translate` — no longer regenerates the visual design (background, colors, device mockup, layout) from the original plan specs; now sends a simple "edit this image, translate only the text overlays" prompt so the existing generated screenshot is preserved exactly + +--- + +## [0.1.33] - 2026-03-02 + +### Added +- `asc init` — initialise project context by saving the app ID, name, and bundle ID to `.asc/project.json` in the current directory; supports `--app-id` (direct), `--name` (search by name), or auto-detect from `.xcodeproj` bundle identifier; output includes CAEOAS affordances for common next steps + +--- + +## [0.1.32] - 2026-03-01 + +### Changed +- Bug fixes and improvements. + +--- + +## [0.1.31] - 2026-03-01 + +### Added +- **Plugin system** — users can install executable plugins in `~/.asc/plugins//` to extend the CLI with custom event handlers (e.g., Slack/Telegram notifications) +- `asc plugins list` — list all installed plugins with name, version, enabled status, and subscribed events +- `asc plugins install ` — install a plugin from a local directory containing `manifest.json` and a `run` executable +- `asc plugins uninstall --name ` — remove an installed plugin +- `asc plugins enable --name ` — re-enable a disabled plugin +- `asc plugins disable --name ` — disable a plugin without removing it +- `asc plugins run --name --event ` — manually invoke a plugin for testing; supports `--app-id`, `--version-id`, `--build-id` payload flags +- Auto-event emission: `asc builds upload` fires `build.uploaded` after a successful upload; `asc versions submit` fires `version.submitted` after a successful submission +- Plugin protocol: plugins receive a JSON event payload on stdin and write a `{"success": bool, "message": "..."}` result to stdout +- `version-review-detail get --version-id ` — fetch the App Store review contact info and demo account settings for a version +- `asc version-review-detail update --version-id [flags]` — upsert review info (creates if none exists, patches if already set); supports `--contact-first-name`, `--contact-last-name`, `--contact-phone`, `--contact-email`, `--demo-account-required`, `--demo-account-name`, `--demo-account-password`, `--notes` +- `notes` field added to `AppStoreReviewDetail` domain model +- `getReviewDetail` affordance added to `AppStoreVersion` for agent navigation + +--- + +## [0.1.30] - 2026-03-01 + +### Added +- `asc age-rating get --app-info-id ` — fetch the full age rating declaration for an app info, including all content intensity ratings, boolean flags, kids age band, and region overrides +- `asc age-rating update --declaration-id [flags]` — update individual age rating fields via PATCH; supports all 9 boolean flags (`--advertising`, `--gambling`, `--loot-box`, etc.) and 13 intensity ratings (`--violence-realistic`, `--profanity`, `--sexual-content`, etc.) plus `--kids-age-band`, `--age-rating-override`, and `--korea-age-rating-override` +- `getAgeRating` affordance added to `AppInfo` for agent navigation +- `--privacy-choices-url` and `--privacy-policy-text` flags on `asc app-info-localizations update` — expose the two remaining updatable privacy fields from the App Store Connect API +- `asc app-info-localizations delete --localization-id ` — remove a per-locale metadata entry; `delete` affordance added to `AppInfoLocalization` +- `asc app-infos update --app-info-id [--primary-category] [--primary-subcategory-one] [--primary-subcategory-two] [--secondary-category] [--secondary-subcategory-one] [--secondary-subcategory-two]` — set or update all 6 category relationship fields on an AppInfo; `updateCategories` affordance added to `AppInfo` +- `asc app-categories list [--platform IOS|MAC_OS|TV_OS]` — list all available App Store categories and subcategories; returns a flat list combining top-level (`data[]`) and subcategories (`included[]`) from the API; `AppCategory` domain model with `parentId` for subcategory identification + +--- + +## [0.1.29] - 2026-03-01 + +### Added +- `--style-reference ` flag on `asc app-shots generate` and `asc app-shots translate` — pass any PNG/JPEG as a visual style guide; Gemini replicates the reference's colors, typography, gradients, and layout patterns without copying its content; the reference image is sent as the first part in the Gemini request followed by an explicit style-guide instruction + +--- + +## [0.1.28] - 2026-02-28 + +### Added +- `--device-type` flag on `asc app-shots generate` and `asc app-shots translate` — accepts named App Store display type constants (`APP_IPHONE_69`, `APP_IPHONE_67`, `APP_IPAD_PRO_129`, etc.) and automatically sets the correct `--output-width`/`--output-height`; overrides explicit dimension flags when both are provided; all 16 device types supported across iPhone, iPad, Apple TV, Mac, and Apple Vision Pro + +### Fixed +- `asc app-shots generate` and `asc app-shots translate` now upscale Gemini output to the correct App Store dimensions using CoreGraphics. Gemini returns ~704×1520; the required iPhone 6.9" size is 1320×2868. New `--output-width` (default `1320`) and `--output-height` (default `2868`) flags control the target size for all other device types. + +--- + +## [0.1.27] - 2026-02-28 + +### Added +- `asc app-shots translate` — one-shot localization of generated screenshots; reads the English plan + existing `screen-{n}.png` files, sends them to Gemini with per-locale translation instructions, writes `{output-dir}/{locale}/screen-{n}.png`; supports multiple locales in one invocation (`--to zh --to ja --to ko`); locales processed in parallel via `TaskGroup` +- `asc app-shots generate` — AI-powered App Store screenshot generation using Gemini; reads a `ScreenPlan` JSON + screenshot images, calls Gemini image generation API, writes `screen-{index}.png` files; `--plan` defaults to `.asc/app-shots/app-shots-plan.json`, `--output-dir` defaults to `.asc/app-shots/output`, screenshots auto-discovered from plan directory when not provided — zero-argument happy path: `asc app-shots generate` +- `asc app-shots config` — persistent Gemini API key management; `--gemini-api-key` saves to `~/.asc/app-shots-config.json`, bare invocation shows current key + source (file/env), `--remove` deletes it; `generate` resolves key from flag → env var → stored config +- `ScreenPlan`, `ScreenConfig`, `ScreenTone`, `LayoutMode`, `ScreenColors`, `AppShotsConfig` domain models +- `ScreenshotGenerationRepository` + `AppShotsConfigStorage` `@Mockable` protocols +- `GeminiScreenshotGenerationRepository` — native Gemini `generateContent` API with `responseModalities: ["TEXT","IMAGE"]`, parallel TaskGroup generation +- `FileAppShotsConfigStorage` — saves Gemini API key to `~/.asc/app-shots-config.json` + +--- + +## [0.1.25] - 2026-02-27 + +### Added +- `asc subscriptions submit` — submit a subscription for App Store review; `submit` affordance on `Subscription` appears only when `state == READY_TO_SUBMIT` +- `asc subscription-offers list` — list introductory offers for a subscription +- `asc subscription-offers create` — create a `FREE_TRIAL`, `PAY_AS_YOU_GO`, or `PAY_UP_FRONT` introductory offer; validates that `--price-point-id` is provided for paid modes +- `SubscriptionIntroductoryOffer` domain model with `SubscriptionOfferDuration` and `SubscriptionOfferMode` enums; `requiresPricePoint` semantic boolean on `SubscriptionOfferMode` +- `createIntroductoryOffer` and `listIntroductoryOffers` CAEOAS affordances on `Subscription` +- `asc iap list` — list in-app purchases (consumable, non-consumable, non-renewing subscription) for an app +- `asc iap create` — create a new in-app purchase with reference name, product ID, and type +- `asc iap-localizations list` — list localizations for an in-app purchase +- `asc iap-localizations create` — create a per-locale name and description for an in-app purchase +- `asc subscription-groups list` — list subscription groups for an app +- `asc subscription-groups create` — create a new subscription group +- `asc subscriptions list` — list subscriptions within a group +- `asc subscriptions create` — create a subscription with period (ONE_WEEK–ONE_YEAR), family sharing, and group level +- `asc subscription-localizations list` — list per-locale metadata for a subscription +- `asc subscription-localizations create` — create a per-locale name and description for a subscription +- `asc iap submit` — submit an in-app purchase for App Store review +- `asc iap price-points list` — list available price tiers for an IAP, optionally filtered by territory +- `asc iap prices set` — set the price schedule for an IAP (base territory + auto-pricing for all others) +- `InAppPurchaseSubmission`, `InAppPurchasePricePoint`, `InAppPurchasePriceSchedule` domain models with CAEOAS affordances +- `InAppPurchaseState` and `SubscriptionState` enums with semantic booleans (`isApproved`, `isLive`, `isEditable`, `isPendingReview`) +- CAEOAS affordances on all new models linking to sibling and child commands +- `asc app-preview-sets list` — list App Store video preview sets for a version localization +- `asc app-preview-sets create` — create a new preview set for a specific device type +- `asc app-previews list` — list video previews in a preview set +- `asc app-previews upload` — upload a video file (`.mp4`, `.mov`, `.m4v`) to a preview set using a 3-step upload flow (reserve → PUT chunks → PATCH with MD5) +- `PreviewType` enum with 16 device cases (iPhone, iPad, Mac, Apple TV, Apple Vision Pro) +- `AppPreview.VideoDeliveryState` with 5 states including `PROCESSING` (unique to video) +- Renamed `--group-id` to `--beta-group-id` in all `asc testflight testers` commands for consistency with `asc builds add-beta-group` + +--- + +## [0.1.24] - 2026-02-27 + +--- + +## [0.1.23] - 2026-02-26 + +--- + +## [0.1.22] - 2026-02-26 + +--- + +## [0.1.21] - 2026-02-26 + +--- + +## [0.1.20] - 2026-02-26 + +--- + +## [0.1.9] - 2026-02-25 + +--- + +## [0.1.8] - 2026-02-25 + +--- + +## [0.1.7] - 2026-02-25 + +--- + +## [0.1.6] - 2026-02-24 + +--- + +## [0.1.5] - 2026-02-24 + +--- + +## [0.1.4] - 2026-02-24 + +### Added +- **Persistent Auth Login**: Save API key credentials to `~/.asc/credentials.json` so environment variables are not required on every session. + - `asc auth login --key-id --issuer-id --private-key-path ` — save credentials to disk + - `asc auth logout` — remove saved credentials + - `asc auth check` — verify credentials and show their source (`file` or `environment`) + - Credential resolution order: `~/.asc/credentials.json` → environment variables (fully backwards-compatible) + - `asc auth check` now outputs JSON with an `affordances` field (same agent-first format as all other commands) + +### Technical +- Added `AuthStorage` `@Mockable` protocol (`save`, `load`, `delete`) in Domain +- Added `AuthStatus` domain model (`keyID`, `issuerID`, `source: CredentialSource`) with `AffordanceProviding` +- Added `CredentialSource` enum (`.file` / `.environment`) as `Codable` String raw value +- `AuthCredentials` gains `Codable` conformance for JSON file serialization +- `FileAuthStorage` (Infrastructure) reads/writes `~/.asc/credentials.json` using `JSONEncoder`/`JSONDecoder` +- `FileAuthProvider` (Infrastructure) implements `AuthProvider` backed by `FileAuthStorage` +- `CompositeAuthProvider` (Infrastructure) tries file credentials first, falls back to `EnvironmentAuthProvider` +- `ClientProvider` updated to use `CompositeAuthProvider` — all existing commands gain file-based auth transparently + +--- + +## [0.1.3] - 2026-02-24 + +### Added +- **Version Localization Update**: Set What's New text, description, keywords, marketing URL, support URL, and promotional text for any locale directly from the CLI. + - `asc version-localizations update --localization-id --whats-new "text"` — update What's New + - `asc version-localizations update --localization-id --description "text" --keywords "a,b,c"` — update other fields + - All content fields are optional — only provided fields are sent to the API +- **App Info Localizations**: Manage per-locale app metadata (name, subtitle, privacy policy) directly from the CLI. Each locale's App Store listing information is now fully writable. + - `asc app-infos list --app-id ` — list AppInfo records for an app + - `asc app-info-localizations list --app-info-id ` — list all locale entries + - `asc app-info-localizations create --app-info-id --locale --name ` — add a new locale + - `asc app-info-localizations update --localization-id [--name] [--subtitle] [--privacy-policy-url]` — patch one or more fields + +### Technical +- Introduced `VersionLocalizationRepository` (`@Mockable` protocol) with `listLocalizations`, `createLocalization`, `updateLocalization` — separate from `ScreenshotRepository` which now handles only screenshot sets and images +- Added `SDKLocalizationRepository` implementing `VersionLocalizationRepository`; converts `URL?` ↔ `String?` for `marketingURL`/`supportURL` SDK fields +- `AppStoreVersionLocalization` gains 6 optional text fields with nil-safe Codable (`encodeIfPresent`) — nil fields are omitted from JSON output +- `AppStoreVersionLocalization.affordances` now includes `updateLocalization` command +- `ScreenshotsImport` updated to accept `localizationRepo` + `screenshotRepo` as separate parameters +- Added `AppInfo` and `AppInfoLocalization` domain models carrying parent IDs for agent navigation +- Added `AppInfoRepository` `@Mockable` protocol with `listAppInfos`, `listLocalizations`, `createLocalization`, `updateLocalization` +- Added `SDKAppInfoRepository` mapping `privacyPolicyURL`/`privacyChoicesURL` SDK fields; extracts `appInfoId` from PATCH response relationships +- Updated `App.affordances` to include `listAppInfos` command +- Added `docs/features/version-localizations.md`, `docs/features/app-info-localizations.md` + +--- + +## [0.1.1] - 2026-02-22 + +### Added +- **App Store Version Management**: Browse and create versions for any app across all platforms (iOS, macOS, tvOS, watchOS, visionOS). + - `asc versions list --app-id ` + - `asc versions create --app-id --version --platform ` +- **Localization Management**: List and create App Store version localizations. + - `asc version-localizations list --version-id ` + - `asc version-localizations create --version-id --locale ` +- **Screenshot Sets**: List and create screenshot sets for a localization (one set per display type: iPhone 6.7", iPad Pro 12.9", Mac, etc.). + - `asc screenshot-sets list --localization-id ` + - `asc screenshot-sets create --localization-id --display-type ` +- **Screenshots**: List and upload screenshots within a set. + - `asc screenshots list --set-id ` — with file size, dimensions, and delivery state + - `asc screenshots upload --set-id --file ` — three-step ASC flow (reserve → S3 upload → commit) handled automatically +- **Submit for Review**: Submit an App Store version for review in one command. Reuses an existing open review submission if one is already pending. + - `asc versions submit --version-id ` +- **TestFlight**: List beta groups and testers. + - `asc testflight groups [--app-id ]` + - `asc testflight testers --group-id ` +- **TUI Screenshot Browser**: Interactive terminal UI now supports the full screenshot hierarchy — app → version → platform → locale → screenshot set. +- **Agent-First Output (CAEOAS)**: JSON responses include an `affordances` field with ready-to-run follow-up commands so AI agents can navigate the full resource tree without knowing the command structure. + ```json + { "data": [{ "id": "...", "affordances": { "listVersions": "asc versions list --app-id ..." } }] } + ``` +- **Homebrew Tap**: Install via Homebrew with native architecture binaries (arm64 and x86_64). + ```bash + brew install asc-tools/tap/asc + ``` +- **Homepage**: Static landing page at [asccli.app](https://asccli.app) with English, Chinese, and Japanese localizations. + +### Fixed +- Submission flow now correctly includes the app relationship in the version request, preventing API 422 errors. +- Submission flow reuses an unresolved (open) review submission instead of always creating a new one. + +### Technical +- `AppStoreVersion`, `AppStoreVersionState`, `AppStorePlatform` models with semantic booleans (`isLive`, `isEditable`, `isPending`) +- `AppStoreVersionLocalization`, `AppScreenshotSet`, `AppScreenshot` models; all carry parent IDs +- `ScreenshotDisplayType` enum with 39 display types, device categories, and human-readable names +- `AffordanceProviding` protocol; `OutputFormatter.formatAgentItems()` merges affordances into JSON +- `SubmissionRepository` orchestrating 4-step review submission API flow +- `ReviewSubmission` domain model with `ReviewSubmissionState` and `isWaitingForReview` +- `SequencedStubAPIClient` for multi-step API flow tests +- `APIClient` protocol abstraction enabling `StubAPIClient` for infrastructure tests +- Automated GitHub Actions release workflow with binary artifacts and Homebrew formula update +- CI/CD Codecov coverage reporting + +--- + +## [0.1.0] - 2026-02-10 + +### Added +- **Apps**: List and get apps on your App Store Connect account. + - `asc apps list` +- **Builds**: List builds for an app with processing state, version, and expiry. + - `asc builds list [--app-id ]` +- **TUI**: Interactive terminal UI (`asc tui`) for browsing apps and builds without remembering IDs. +- **Auth Check**: Verify environment credentials are valid. + - `asc auth check` +- **Output Formats**: All commands support `--output json` (default), `--output table`, and `--output markdown`. Use `--pretty` for formatted JSON. +- **Environment Auth**: Configure via `ASC_KEY_ID`, `ASC_ISSUER_ID`, and `ASC_PRIVATE_KEY_PATH` (or `ASC_PRIVATE_KEY` for inline PEM). + +### Technical +- Three-layer architecture: `ASCCommand → Infrastructure → Domain` +- `appstoreconnect-swift-sdk` adapter with clean domain model separation +- `App`, `Build`, `BetaGroup`, `BetaTester` domain models (`Sendable`, `Equatable`, `Identifiable`, `Codable`) +- `AppRepository`, `BuildRepository`, `TestFlightRepository` `@Mockable` protocols +- `ClientFactory` + `ClientProvider` dependency injection wiring +- Apple `@Testing` macro test suite following Chicago School TDD + +[0.1.68]: https://github.com/tddworks/asc-cli/compare/v0.1.67...v0.1.68 +[0.1.67]: https://github.com/tddworks/asc-cli/compare/v0.1.66...v0.1.67 +[0.1.66]: https://github.com/tddworks/asc-cli/compare/v0.1.65...v0.1.66 +[0.1.65]: https://github.com/tddworks/asc-cli/compare/v0.1.64...v0.1.65 +[0.1.64]: https://github.com/tddworks/asc-cli/compare/v0.1.63...v0.1.64 +[0.1.63]: https://github.com/tddworks/asc-cli/compare/v0.1.62...v0.1.63 +[0.1.62]: https://github.com/tddworks/asc-cli/compare/v0.1.61...v0.1.62 +[0.1.61]: https://github.com/tddworks/asc-cli/compare/v0.1.60...v0.1.61 +[0.1.60]: https://github.com/tddworks/asc-cli/compare/v0.1.59...v0.1.60 +[0.1.59]: https://github.com/tddworks/asc-cli/compare/v0.1.58...v0.1.59 +[0.1.58]: https://github.com/tddworks/asc-cli/compare/v0.1.57...v0.1.58 +[0.1.57]: https://github.com/tddworks/asc-cli/compare/v0.1.56...v0.1.57 +[0.1.56]: https://github.com/tddworks/asc-cli/compare/v0.1.55...v0.1.56 +[0.1.55]: https://github.com/tddworks/asc-cli/compare/v0.1.53...v0.1.55 +[0.1.53]: https://github.com/tddworks/asc-cli/compare/v0.1.52...v0.1.53 +[0.1.52]: https://github.com/tddworks/asc-cli/compare/v0.1.51...v0.1.52 +[0.1.51]: https://github.com/tddworks/asc-cli/compare/v0.1.50...v0.1.51 +[0.1.50]: https://github.com/tddworks/asc-cli/compare/v0.1.49...v0.1.50 +[0.1.49]: https://github.com/tddworks/asc-cli/compare/v0.1.48...v0.1.49 +[0.1.48]: https://github.com/tddworks/asc-cli/compare/v0.1.47...v0.1.48 +[0.1.47]: https://github.com/tddworks/asc-cli/compare/v0.1.46...v0.1.47 +[0.1.46]: https://github.com/tddworks/asc-cli/compare/v0.1.45...v0.1.46 +[0.1.45]: https://github.com/tddworks/asc-cli/compare/v0.1.43...v0.1.45 +[0.1.43]: https://github.com/tddworks/asc-cli/compare/v0.1.42...v0.1.43 +[0.1.42]: https://github.com/tddworks/asc-cli/compare/v0.1.41...v0.1.42 +[0.1.41]: https://github.com/tddworks/asc-cli/compare/v0.1.40...v0.1.41 +[0.1.40]: https://github.com/tddworks/asc-cli/compare/v0.1.39...v0.1.40 +[0.1.39]: https://github.com/tddworks/asc-cli/compare/v0.1.38...v0.1.39 +[0.1.38]: https://github.com/tddworks/asc-cli/compare/v0.1.37...v0.1.38 +[0.1.37]: https://github.com/tddworks/asc-cli/compare/v0.1.36...v0.1.37 +[0.1.36]: https://github.com/tddworks/asc-cli/compare/v0.1.35...v0.1.36 +[0.1.35]: https://github.com/tddworks/asc-cli/compare/v0.1.34...v0.1.35 +[0.1.34]: https://github.com/tddworks/asc-cli/compare/v0.1.33...v0.1.34 +[0.1.33]: https://github.com/tddworks/asc-cli/compare/v0.1.32...v0.1.33 +[0.1.32]: https://github.com/tddworks/asc-cli/compare/v0.1.31...v0.1.32 +[0.1.31]: https://github.com/tddworks/asc-cli/compare/v0.1.30...v0.1.31 +[0.1.30]: https://github.com/tddworks/asc-cli/compare/v0.1.29...v0.1.30 +[0.1.29]: https://github.com/tddworks/asc-cli/compare/v0.1.28...v0.1.29 +[0.1.28]: https://github.com/tddworks/asc-cli/compare/v0.1.27...v0.1.28 +[0.1.27]: https://github.com/tddworks/asc-cli/compare/v0.1.25...v0.1.27 +[0.1.25]: https://github.com/tddworks/asc-cli/compare/v0.1.24...v0.1.25 +[0.1.24]: https://github.com/tddworks/asc-cli/compare/v0.1.23...v0.1.24 +[0.1.23]: https://github.com/tddworks/asc-cli/compare/v0.1.22...v0.1.23 +[0.1.22]: https://github.com/tddworks/asc-cli/compare/v0.1.21...v0.1.22 +[0.1.21]: https://github.com/tddworks/asc-cli/compare/v0.1.20...v0.1.21 +[0.1.20]: https://github.com/tddworks/asc-cli/compare/v0.1.9...v0.1.20 +[0.1.9]: https://github.com/tddworks/asc-cli/compare/v0.1.8...v0.1.9 +[0.1.8]: https://github.com/tddworks/asc-cli/compare/v0.1.7...v0.1.8 +[0.1.7]: https://github.com/tddworks/asc-cli/compare/v0.1.6...v0.1.7 +[0.1.6]: https://github.com/tddworks/asc-cli/compare/v0.1.5...v0.1.6 +[0.1.5]: https://github.com/tddworks/asc-cli/compare/v0.1.4...v0.1.5 +[0.1.4]: https://github.com/tddworks/asc-cli/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/tddworks/asc-cli/compare/v0.1.1...v0.1.3 +[0.1.1]: https://github.com/tddworks/asc-cli/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/tddworks/asc-cli/releases/tag/v0.1.0 diff --git a/docs/changelog/0.16.md b/docs/changelog/0.16.md new file mode 100644 index 00000000..5bac2c59 --- /dev/null +++ b/docs/changelog/0.16.md @@ -0,0 +1,18 @@ +# Changelog — 0.16.x + +Archived unchanged from [CHANGELOG.md](../../CHANGELOG.md), which holds the current releases. + +## [0.16.9] - 2026-04-20 + +### Added +- **`asc review-submissions list`** — list App Store review submissions for an app. Required `--app-id`; optional `--state ` (e.g. `WAITING_FOR_REVIEW,IN_REVIEW,READY_FOR_REVIEW` or `UNRESOLVED_ISSUES`) and `--limit`. Backed by `SubmissionRepository.listSubmissions(appId:states:limit:)` +- **`asc certificates list` filtering flags** — `--limit` (server-side), `--expired-only` (client-side, drops unexpired certs), `--before ` (client-side, keeps certs with expirationDate strictly before the cutoff) +- **REST endpoint `GET /api/v1/apps/{appId}/review-submissions`** — new `ReviewSubmissionsController` in the `asc web-server`. Supports `?state=…&limit=…` query params; mirrors the CLI. **No fleet route** — Apple's OpenAPI spec marks `filter[app]` as required, so review submissions can only be listed per-app +- **REST endpoint `GET /api/v1/certificates` query params** — now honours `?type=&limit=&expired-only=&before=` (previously ignored). `before` accepts both full ISO8601 (`2026-11-01T00:00:00Z`) and date-only (`2026-11-01`, interpreted as midnight UTC). Same filter semantics as the CLI +- **REST endpoint `GET /api/v1/builds`** — fleet listing with optional `?app-id=&platform=&version=&limit=` query params. `/api/v1/apps/{appId}/builds` still works for the nested form + +### Changed +- **`CertificateRepository.listCertificates`** signature now takes `(certificateType:limit:)` — forwards `limit` to the SDK +- **`ReviewSubmission` REST links** — affordance migrated from raw `affordances` dictionary to `structuredAffordances`, so REST responses now render `_links` correctly (e.g. `/api/v1/apps/{appId}/versions`) + +[0.16.9]: https://github.com/tddworks/asc-cli/compare/v0.1.68...v0.16.9 diff --git a/docs/changelog/0.17.md b/docs/changelog/0.17.md new file mode 100644 index 00000000..d0468a62 --- /dev/null +++ b/docs/changelog/0.17.md @@ -0,0 +1,247 @@ +# Changelog — 0.17.x + +Archived unchanged from [CHANGELOG.md](../../CHANGELOG.md), which holds the current releases. + +## [0.17.9] - 2026-05-09 + +### Added +- **`asc builds set-encryption-compliance --build-id --uses-non-exempt-encryption ` — set Apple's export-compliance answer on a build** — closes the second TestFlight-blocking gap: when an IPA is uploaded without `ITSAppUsesNonExemptEncryption` in Info.plist, ASC marks the build "Missing Compliance" and external testing is blocked. The new command PATCHes `/v1/builds/{id}` with `usesNonExemptEncryption` so the answer can be supplied post-upload from CI scripts that don't control the Info.plist. `Build` domain model now carries `usesNonExemptEncryption: Bool?` and a `isMissingEncryptionCompliance` semantic boolean (`true` when nil); usable builds in the missing state advertise a `setEncryptionCompliance` affordance with the ready-to-run command. Build's `tableRow` gains an "Encryption" column showing `uses` / `exempt` / `missing`. REST equivalent: `PATCH /api/v1/builds/:buildId/encryption-compliance` body `{"usesNonExemptEncryption": true|false}`. New `BuildRepository.updateBuildEncryptionCompliance(buildId:usesNonExemptEncryption:)`. See `docs/features/builds-upload.md`. +- **`asc beta-app-localizations {list,get,create,update,delete}` — TestFlight Beta App Description per locale** — closes the parity gap that blocked TestFlight submissions: Apple's `BetaAppLocalizations` resource holds the per-locale beta description, tester feedback email, marketing URL, and privacy policy URL shown in TestFlight before external testing can be enabled. Distinct from the existing `asc builds update-beta-notes` (which writes per-build "What to Test" notes) and `asc beta-review` (which writes review contact info). New `BetaAppLocalization` domain model + `@Mockable BetaAppLocalizationRepository` (Domain/Apps/TestFlight/), `SDKBetaAppLocalizationRepository` (Infrastructure) backed by `/v1/apps/{id}/betaAppLocalizations` (list), `/v1/betaAppLocalizations/{id}` (get/update/delete), and `/v1/betaAppLocalizations` (create). Affordances: `delete`, `get`, `listSiblings`, `update`. REST equivalents: `GET /api/v1/apps/:appId/beta-app-localizations` and `GET /api/v1/beta-app-localizations/:id` via `BetaAppLocalizationsController`. New REST path resolver entry for `beta-app-localizations` parented under `apps`. See `docs/features/beta-app-localizations.md`. + +--- + +## [0.17.8] - 2026-04-29 + +### Added +- **`asc iris iap-submissions delete --submission-id `** — iris-side dequeue of an IAP submission (`DELETE /iris/v1/inAppPurchaseSubmissions/{id}`). Iris-queued submissions don't round-trip through the public-SDK delete; they can only be removed via this iris-cookie-authed path. New `IrisClient.delete`, new `IrisInAppPurchaseSubmissionRepository.deleteSubmission(session:submissionId:)`. REST equivalent: `DELETE /api/v1/iris/iap-submissions/:submissionId` on `IrisIAPSubmissionsController`. **`removeFromNextVersion` affordance now resolves end-to-end through iris** (CLI: `asc iris iap-submissions delete --submission-id `; REST: `DELETE /api/v1/iris/iap-submissions/`). `RESTPathResolver.resourcePath` now converts spaces in segment names to slashes so multi-word commands like `iris iap-submissions` produce clean URL segments. Removed the short-lived public-SDK alias `POST /api/v1/iap/:iapId/unsubmit` (incorrect — public DELETE doesn't accept iris-queued submissions). +- **`removeFromNextVersion` affordance on queued IAPs** — when an IAP is in `READY_TO_SUBMIT` and Apple's iris listing reports `submitWithNextAppStoreVersion: true` (queued to ride along with the next app version), `InAppPurchase.structuredAffordances` surfaces `removeFromNextVersion` → `asc iap unsubmit --submission-id ` and **suppresses** the `submit` affordance (mutually exclusive — Apple rejects re-submits on already-queued IAPs). The dequeue uses the existing public-SDK `DELETE /v1/inAppPurchaseSubmissions/{id}` (Apple's iris flow keys the submission resource by parent IAP id, so `--submission-id ` works). Underpinned by a new best-effort iris enrichment: `IrisSDKInAppPurchaseStateRepository` calls `GET /iris/v1/apps/:appId/inAppPurchasesV2?fields[inAppPurchases]=submitWithNextAppStoreVersion&filter[state]=READY_TO_SUBMIT`, mapping each IAP to its queued bit. `SDKInAppPurchaseRepository.listInAppPurchases` accepts an optional `irisFlagsProvider` closure; the factory wires it from `IrisCookieProvider` + `IrisSDKInAppPurchaseStateRepository`. CI scripts using API-key auth keep their existing JSON output (flag stays `false`, no `removeFromNextVersion` affordance). New `InAppPurchase.submitWithNextAppStoreVersion: Bool` field, encoded only when `true` to avoid snapshot churn. New `IrisInAppPurchaseStateRepository` `@Mockable` Domain protocol. + +### Changed +- **`InAppPurchase` submission affordances reorganized into iris-queue family vs. public-SDK direct submit.** Two inverse pairs, all gated on `state == .readyToSubmit`: + - `addToNextVersion` (NEW) → `asc iris iap-submissions create --iap-id ` — queues the IAP to ride along with the next App Store version submission. Always available for ready, not-yet-queued IAPs. + - `removeFromNextVersion` → `asc iap unsubmit --submission-id ` — dequeues. Mutually exclusive with the others; emitted only when iris listing reports `submitWithNextAppStoreVersion: true`. + - `submit` → `asc iap submit --iap-id ` — public-SDK direct submit (standalone review). Now emitted **only** when `isFirstTimeSubmission == false` (Apple rejects the direct path for the first IAP for an app). + - Established-app ready IAPs see **both** `submit` and `addToNextVersion`; agent picks based on release strategy. + - Replaces the previous behavior where `submit` quietly auto-dispatched between iris and public SDK paths — keys are now explicit about what each call does. New `InAppPurchaseState.hasBeenApproved` semantic boolean. New `iris iap-submissions` REST route registered with the path resolver so `addToNextVersion` `_links` resolve to `/api/v1/iris/iap//submissions`. + +### Added +- **`asc iris iap-submissions create --iap-id ` — IAP submission via the iris private API** — closes the parity gap with the public SDK's `POST /v1/inAppPurchaseSubmissions`, which has no `attributes` field and therefore can't carry `submitWithNextAppStoreVersion`. The new command posts to `POST /iris/v1/inAppPurchaseSubmissions` with that attribute set, which is required for first-time IAP submissions (Apple binds the first IAP to the next App Store version). Defaults to `--with-next-version`; pass `--no-with-next-version` to opt out. Lives under `asc iris …` because it needs iris cookies (either browser cookies or a session from `asc iris auth login`). Surfaced via `IrisStatus.affordances.submitIAP` so `asc iris status` advertises the path when authenticated. Existing `asc iap submit` stays untouched — CI scripts using key-auth keep working unchanged. See `docs/features/iap-subscriptions/submission-iris-parity.md`. +- **REST: `POST /api/v1/iris/iap/:iapId/submissions`** — body `{"submitWithNextAppStoreVersion": true}` (default true); returns the submission resource with `_links.viewIAP` pointing back at the public IAP endpoint. + +### Fixed +- **iris 2FA `verify-code` no longer 401s with a valid code** — `IdmsaAPIClient.applyHeaders` was sending `X-Apple-OAuth-Client-Id`, `X-Apple-OAuth-Redirect-URI`, `X-Apple-OAuth-Response-Mode`, `X-Apple-OAuth-Response-Type`, `Origin`, and `Referer` on `signin/init` and `signin/complete`. None of `XcodesOrg/XcodesApp`, `XcodesOrg/XcodesLoginKit`, or the `rorkai/App-Store-Connect-CLI` Go reference send those — they taint the SRP session so the post-409 `GET /appleauth/auth` returns 401, rotates `scnt`, and the subsequent `POST /verify/trusteddevice/securitycode` is rejected as out-of-sequence. Header set is now narrowed to `Accept`, `Content-Type`, `X-Requested-With`, `X-Apple-Widget-Key`, `scnt`, `X-Apple-ID-Session-Id` (+ `X-Apple-HC` on `signin/complete`). `signinComplete` also flips to `?isRememberMeEnabled=false` with body `rememberMe: false` to match the same references — `true` puts Apple in the "trust this browser" path, which mismatches a CLI session. + +### Added +- **`asc iris auth login` / `verify-code` / `logout` — Apple ID SRP login for the iris private API** — until now iris features (`asc iris apps list`, `asc iris status`) only worked when the user had logged into App Store Connect in their browser; we'd borrow cookies from there. Headless CI had no path. The new command performs a full Apple SRP-6a handshake against `idmsa.apple.com`, handles 2FA (trusted-device push or phone code), then persists the resulting session to `~/.asc/iris/session.json` (0600 perms). New `CompositeIrisCookieProvider` chains SRP-stored cookies before browser cookies, so any existing iris command immediately picks up the SRP session without code changes elsewhere. See `docs/features/iris/iris-srp-login.md`. + - `asc iris auth login --apple-id user@example.com` — prompts for password on stdin, runs SRP, persists session on success or pending-2FA state on 409. + - `asc iris auth verify-code 123456` — submits the 2FA code, calls `2sv/trust`, fetches `olympus/v1/session` for team metadata, persists the full session. + - `asc iris auth logout` — deletes the persisted session. + - `ASC_IRIS_DEBUG=1` — dumps every idmsa request/response to stderr with `a`/`m1`/`m2` redacted, so failures self-capture safely-shareable test data. + - Built on `adam-fowler/swift-srp` (Apache 2.0) for RFC 5054 group-2048 constants, BigNum, and key generation. Apple's PBKDF2-derived `x` is layered on top in `AppleSRPClient`. PBKDF2-HMAC-SHA256 implementation cross-checked against RFC 7914 §11 vectors. + - **Caveat**: while every layer is unit-tested with mocked HTTP and synthesized vectors, real-world correctness against Apple's specific `M1` shape gets validated only on the first real login attempt. If Apple's protocol shifts, expect churn here. + +--- + +## [0.17.7] - 2026-04-29 + +### Added +- **REST `POST` / `PATCH` / `DELETE` for subscription group localizations** — `SubscriptionGroupLocalizationsController` previously exposed only `GET`, so `POST /api/v1/subscription-groups/:groupId/subscription-group-localizations` (the call the AppNexus web UI emits when creating a new locale) returned 404. Now wired: + - `POST /api/v1/subscription-groups/:groupId/subscription-group-localizations` — body `{locale, name, customAppName?}`. + - `PATCH /api/v1/subscription-group-localizations/:localizationId` — body `{name?, customAppName?}`. + - `DELETE /api/v1/subscription-group-localizations/:localizationId` — returns `{"deleted": true}`. +- **`uploadReviewScreenshot` and `uploadImage` affordances on `InAppPurchase` and `Subscription`** — until now an agent looking at an IAP or subscription with no review screenshot had no `_links` path to discover the upload command. The four new affordances close that gap (CLI surface, REST `_links`, REST endpoints). +- **REST upload endpoints (raw body)** — `POST /api/v1/iap/:iapId/review-screenshot`, `POST /api/v1/iap/:iapId/images`, `POST /api/v1/subscriptions/:subscriptionId/review-screenshot`, `POST /api/v1/subscriptions/:subscriptionId/images`. Send the image bytes as the request body (Content-Type `image/png`, `image/jpeg`, etc.); body is spooled to a temp file and uploaded through the existing `uploadReviewScreenshot` / `uploadImage` repository methods. 20MB body ceiling. Same path as the `GET` collection — verb selects read vs. upload. + +### Fixed +- **`uploadImage` / `uploadReviewScreenshot` now wait for ASC processing before returning** — ASC's PATCH-commit response carries an empty `imageAsset` because processing is async; clients were getting `{templateURL: "", width: 0, height: 0}` and had no signal of when the asset URL would appear. Both `SDKInAppPurchaseReviewRepository` and `SDKSubscriptionReviewRepository` now mirror the iOS SDK pattern (`AppStoreSdk` `verifyImageUpload` / `verifyReviewScreenshotUpload`): after the commit PATCH, poll `GET /v1/inAppPurchaseImages/{id}` (or the screenshot equivalent) until `state ∈ {PREPARE_FOR_SUBMISSION, WAITING_FOR_REVIEW, APPROVED}` for images / `assetDeliveryState == COMPLETE` for screenshots. Defaults: 15 attempts × 2s = 30s ceiling; failure states (`FAILED`, `REJECTED`) throw immediately. The mapper additionally treats empty `templateURL` / zero dimensions as nil so any leftover not-ready shape is omitted from JSON via `encodeIfPresent` rather than leaking. Result: `POST /api/v1/iap/:iapId/images` and the three sibling upload endpoints return a record whose `imageAsset` is fully populated and ready to render — no client-side polling needed. + +### Changed +- **`upload` action resolves to bare collection path in REST** — `RESTPathResolver` now treats `upload` like `get/update/delete` (no extra suffix) so an upload `_links` href is `/api/v1/iap/:iapId/review-screenshot` (POST), not `/...review-screenshot/upload`. Standard REST: the verb carries the intent, the path names the resource. +- **`reviewNote` now read-side on `InAppPurchase` and `Subscription`** — the field was previously write-only via `asc iap update --review-note` / `asc subscriptions update --review-note`. List/get responses now expose `reviewNote: String?` so an agent (or web UI like AppNexus's IAP/Subscription review tabs) can read the existing value without a separate fetch. SDK mappers thread `attributes.reviewNote` from `InAppPurchaseV2` and `Subscription` SDK responses; nil values are omitted from JSON output via `encodeIfPresent`. Existing JSON snapshots stay byte-identical when no review note is set. + +--- + +## [0.17.6] - 2026-04-28 + +### Fixed +- **`PATCH /api/v1/iap/{iapId}/availability` returned 404** — the route was never registered, so the web frontend's "save territories" call failed. `IAPAvailabilityController` now serves `PATCH` with body `{territoryIds, availableInNewTerritories}` and routes through `repo.createAvailability` (ASC's `POST /v1/inAppPurchaseAvailabilities` upserts — there is no separate update endpoint). + +--- + +## [0.17.5] - 2026-04-28 + +### Added +- **Production / sandbox split for offer codes** — `InAppPurchaseOfferCode` and `SubscriptionOfferCode` now expose `productionCodeCount` and `sandboxCodeCount` (Apple returns these alongside the existing total). Visible in `asc iap-offer-codes list`, `asc subscription-offer-codes list`, and on the existing `GET /api/v1/iap/:iapId/offer-codes` and `GET /api/v1/subscriptions/:subscriptionId/offer-codes` endpoints (flow through Codable). +- **`environment` on one-time-use code batches** — `InAppPurchaseOfferCodeOneTimeUseCode` and `SubscriptionOfferCodeOneTimeUseCode` now carry `environment: OfferCodeEnvironment?` (PRODUCTION or SANDBOX). Apple separates redemption by environment; sandbox batches redeem against sandbox tester accounts and have a smaller per-quarter ceiling. +- **`--environment` flag on one-time-codes create** — `asc iap-offer-code-one-time-codes create --environment sandbox` and `asc subscription-offer-code-one-time-codes create --environment sandbox`. Default is `production`, matching prior behaviour. Maps to the SDK's `Attributes(environment:)` field on the create-request body so sandbox batches can be generated from CLI. +- **REST endpoints for one-time-codes** — `GET/POST /api/v1/iap-offer-codes/:offerCodeId/one-time-codes`, `PATCH /api/v1/iap-offer-code-one-time-codes/:oneTimeCodeId`, and the subscription mirrors. POST body accepts `{numberOfCodes, expirationDate, environment?}` so REST clients can generate sandbox redemption batches without dropping to CLI. Each batch row's `_links` resolves `listOneTimeCodes` to the nested parent path. +- **`createOfferCode` affordance + POST endpoints** — `InAppPurchase` and `Subscription` now advertise `createOfferCode` in `_links`, mirroring the iOS app's "+ New offer code" affordance. `POST /api/v1/iap/:iapId/offer-codes` accepts `{name, customerEligibilities[]}`; `POST /api/v1/subscriptions/:subscriptionId/offer-codes` accepts `{name, duration, mode, periods, customerEligibilities[], offerEligibility}`. Previously these were CLI-only. +- **Per-territory `prices` on offer-code create (IAP + Subscription)** — both CLI and REST now accept the full territory price list at creation time, matching ASC's actual contract. CLI: `--price =` (repeatable, paid) and `--free-territory ` (repeatable). REST: `prices: [{territory, pricePointId?}]` in POST body — omit `pricePointId` for free. New shared `OfferCodePriceInput` domain type. SDK adapters compose the `included[InAppPurchaseOfferPriceInlineCreate]` / `included[SubscriptionOfferCodePriceInlineCreate]` payload with `${local-price-N}` placeholder ids referenced from `relationships.prices.data` — same pattern as the existing IAP price-schedule create. **Fixes a long-standing bug**: previous `createOfferCode` calls sent an empty `prices` relationship, so every offer code created via asc-cli was missing per-territory pricing (and ASC's `prices` is read-only post-create — there was no recovery path). +- **`--auto-renew` on subscription offer-code create** — CLI flag (default `true`) and REST body field `isAutoRenewEnabled` (also accepts `autoRenew`). Setting it `false` creates a non-renewing/one-time offer; ASC only accepts `--mode FREE_TRIAL` in that case. Previously not exposed. + +--- + +## [0.1.74] - 2026-04-27 + +### Added +- **HATEOAS `_links` for IAPs and Subscriptions** — `GET /api/v1/apps/{id}/iap` and `GET /api/v1/subscription-groups/{id}/subscriptions` now embed `_links` per item so an agent can navigate to localizations, availability, offer codes, price points, review screenshots, promotional offers, win-back offers (subscriptions only), and intro offers without knowing the URL conventions. +- **REST controllers for IAP details** — `GET /api/v1/iap/:id/localizations`, `/availability`, `/offer-codes`, `/price-points`. Each returns the agent-first `data: [...]` envelope with `_links` already populated. +- **REST controllers for Subscription details** — `GET /api/v1/subscriptions/:id/localizations`, `/availability`, `/offer-codes`, `/introductory-offers`. +- **`InAppPurchasePriceSchedule` enriched with `baseTerritory` + `territoryPrices`** — `GET /api/v1/iap/:id/price-schedule` now returns the developer-set base territory plus all auto-equalized per-territory prices (currency + customerPrice + proceeds) so the iOS Pricing tab has data to render. Composes three ASC API calls under the hood: schedule (with base territory), manual prices, and equalizations. +- **IAP equalizations primitive** — `asc iap-equalizations list --price-point-id [--limit N]` and `GET /api/v1/iap-price-points/:id/equalizations`. Exposes `GET /v1/inAppPurchasePricePoints/{id}/equalizations` (~175 territory prices auto-derived from a manual base price). +- **Subscription price schedule** — new `SubscriptionPriceSchedule` domain type with `territoryPrices` and `price(for:)` lookup (no base territory — subscriptions are per-territory). `asc subscription-price-schedule get --subscription-id ` and `GET /api/v1/subscriptions/:id/price-schedule`. Composes `GET /v1/subscriptions/{id}/prices` + equalizations. +- **Subscription equalizations primitive** — `asc subscription-equalizations list --price-point-id ` and `GET /api/v1/subscription-price-points/:id/equalizations`. +- **Multi-territory subscription `setPrices` (batch)** — `asc subscriptions prices set-batch --subscription-id --price USA=spp-1 --price JPN=spp-2 ...`. Mirrors iOS `setPrices(prices:)`. Returns the post-write schedule. +- **Subscription promotional images** — `asc subscription-images list|upload|delete` and `GET /api/v1/subscriptions/:id/images`. New `SubscriptionPromotionalImage` domain type with `delete` suppression while `state.isPendingReview` (mirrors IAP images). +- **`asc subscriptions update --period `** — change billing period (ONE_WEEK, ONE_MONTH, …, ONE_YEAR) on an existing subscription. Mirrors the iOS app's `subscription.update(subscriptionPeriod:)`. +- **`setPrice` affordance + `POST /api/v1/iap/{id}/prices/set`** — IAPs now advertise how to set or change their base price/territory. Calling with a new `baseTerritory` is how the iOS app implements "Change Base Territory" — ASC replaces the schedule and re-equalizes other territories. +- **`setPrices` affordance + `POST /api/v1/subscriptions/{id}/prices/set-batch`** — subscriptions now advertise the batch multi-territory price-set operation. Body accepts `{ prices: [{territory, pricePointId, startDate?, preserveCurrentPrice?}] }` (preferred) or a flat `{ "USA": "spp-1", "JPN": "spp-2" }` shorthand. +- **Cursor pagination for IAP/Subscription price points** — `listPricePoints` now accepts `limit` + `cursor` and returns `PaginatedResponse { data, nextCursor, totalCount }`. CLI: `asc iap price-points list --iap-id X --limit 200 --cursor `. REST: `GET /api/v1/iap/{id}/price-points?territory=CHN&limit=200&cursor=` returns `{ data, nextCursor, totalCount }`. Frontends loop until `nextCursor` is absent. Mirrors iOS's `loadPricePoints(territory:limit:cursor:) → PaginatedResult`. + +### Fixed +- **`POST/PATCH /api/v1/iap/{id}/localizations` returned bare 500 on ASC validation failures + dropped over-limit content** — when the description exceeded ASC's 45-character cap (or name exceeded 30), Hummingbird's default error path returned 500 with no body, browsers showed "Failed to fetch", and the user lost the typed text. The controller now (1) trims `name` to 30 grapheme clusters and `description` to 45 before sending, matching the iOS app's `LocaleCard` `limit:` constants, and (2) catches any remaining ASC error (e.g., invalid locale) and surfaces it as `400 Bad Request` with the underlying message so the UI can render a meaningful inline error. +- **IAP/Subscription availability returned only ~10 territories instead of all ~175** — the parent endpoint's `include=availableTerritories` truncates the relationship to a single page. `getAvailability` now issues two parallel calls (attributes + dedicated `/availableTerritories?limit=200`) so the full territory list reaches the frontend's Availability tab. Matches the iOS SDK's `fetchAvailability` composition. +- **`SubscriptionGroup._links` was empty** — `GET /api/v1/apps/{id}/subscription-groups` items now embed populated `_links` for `listSubscriptions`, `listLocalizations`, `createSubscription`, `createLocalization`, `update`, `delete`. Migrated `SubscriptionGroup` from raw `affordances` to `structuredAffordances` so `apiLinks` auto-derives, and registered `_subscriptionGroupLocalizationRoutes` in `RESTPathResolver.ensureInitialized` so the nested localizations path resolves. +- **`/api/v1/subscription-groups/{id}/subscriptions` returned 404** — the `_links.listSubscriptions` URL had no controller. Added `SubscriptionsController` and wired it in `RESTRoutes.swift`. Each subscription's `_links` already advertise the full child surface (localizations, availability, price-schedule, offers, etc.). +- **Review screenshots and promotional images returned no image URL** — the responses included `id`/`fileName`/`fileSize`/`assetState` but nothing renderable. Added `imageAsset: ImageAsset?` to `SubscriptionReviewScreenshot`, `InAppPurchaseReviewScreenshot`, `SubscriptionPromotionalImage`, `InAppPurchasePromotionalImage` — populated from the SDK's `attributes.imageAsset` (templateUrl + width + height). Frontend calls `imageAsset.url(maxSize:format:)` to substitute the `{w}/{h}/{f}` placeholders and produce a real CDN URL. +- **`SubscriptionPrice._links` was empty** — migrated `SubscriptionPrice` from raw `affordances` to `structuredAffordances` so `apiLinks` auto-derives. `listPricePoints` now resolves to `/api/v1/subscriptions/{id}/price-points` over REST. +- **`POST /iap/{id}/prices/set` returned 500** — the inline manual-price payload omitted the `inAppPurchaseV2` back-reference and used a plain `"p1"` placeholder, both of which ASC rejects. The request now mirrors the iOS app shape: `${local-manual-price-1}` placeholder + `inAppPurchaseV2` relationship pointing back at the IAP. Also wrapped `IAPPricesController` in try/catch so future failures log the underlying ASC error to stderr and return a JSON error body instead of an empty 500. +- **`POST /api/v1/apps/{id}/iap` returned 404** — the route wasn't registered. `IAPController` now serves `POST` (create — accepts `referenceName`, `productId`, and `inAppPurchaseType` in either CLI-style `non-consumable` or raw enum `NON_CONSUMABLE`), `PATCH /iap/{id}` (update), and `DELETE /iap/{id}`. Mirrors `VersionsController`'s POST/PATCH conventions. +- **`POST /api/v1/apps/{id}/subscription-groups` returned 404** — same fix for subscription groups. `SubscriptionGroupsController` now serves `POST` (create — `referenceName`), `PATCH /subscription-groups/{id}` (rename), and `DELETE /subscription-groups/{id}`. +- **`GET /api/v1/iap/{id}/availability` returned 500 for newly-created IAPs** — ASC returns 404 until the developer creates the availability resource. `getAvailability(iapId:)` and `getAvailability(subscriptionId:)` now return `Optional` and tolerate the 404 by returning nil; the SDK adapter mirrors iOS's `refreshTerritoryStatuses` 404 tolerance. +- **Availability tab showed empty list on fresh IAP/subscription** — iOS shows all 175 territories preselected as the default UX; the web frontend got `data: []` and rendered nothing. `IAPAvailabilityController` and `SubscriptionAvailabilityController` now synthesize a default-all-territories availability (with `isAvailableInNewTerritories: true`) by fetching `/v1/territories` whenever no resource exists. Frontend renders the same 175-territory grid iOS does. + +### Changed +- **`RESTPathResolver` resolves singleton-under-parent `get` to nested path** — when an action is not `list`/`create`, the singularized own-id is missing, and a registered route's parent param matches one in `params`, the resolver now returns the nested `/parent/{id}/segment` path. This corrects `_links.getReviewScreenshot` for IAP and Subscription (was `/api/v1/iap-review-screenshot/{id}` → now `/api/v1/iap/{id}/review-screenshot`) and similar singletons (availability, age-rating). +- **`AgeRatingController`** now serves the canonical nested path `/api/v1/app-infos/{appInfoId}/age-rating` matching the resolver's `_links`. The flat `/api/v1/age-rating/{id}` remains for back-compat. + +--- + +## [0.17.3] - 2026-04-26 + +### Added +- **State-aware affordances on the new aggregates** — affordances no longer suggest illegal next actions: + - `PromotedPurchase` exposes `state.isLocked` (true while `WAITING_FOR_REVIEW` / `IN_REVIEW`); `update` and `delete` are suppressed while locked, so an agent following affordances won't issue a 409 against App Review. + - `InAppPurchasePromotionalImage` suppresses `delete` while `state.isPendingReview` is true. + - `InAppPurchaseReviewScreenshot.AssetState` and `SubscriptionReviewScreenshot.AssetState` gain `isComplete` / `hasFailed` semantic booleans. `delete` is offered once the asset is reachable (`uploadComplete` / `complete` / `failed`); while `awaitingUpload`, only `upload` is offered as the recovery path. +- **IAP & Subscription review screenshots + IAP promotional images** — closes the upload-heavy feature parity gap. New CLI commands implement the standard ASC reserve → upload chunks → commit-with-MD5 protocol on top of `URLSession`: + - `asc iap-review-screenshot get|upload|delete` — single review screenshot per IAP. `get` returns an empty `data: []` array when no screenshot is present, mirroring CAEOAS conventions. + - `asc iap-images list|upload|delete` — 1024×1024 promotional images for an IAP, with state semantic booleans (`isApproved`, `isPendingReview`). + - `asc subscription-review-screenshot get|upload|delete` — single review screenshot per subscription. + - New domain types: `InAppPurchaseReviewScreenshot`, `InAppPurchasePromotionalImage` (with `ImageState` enum), `SubscriptionReviewScreenshot`. New repository protocols `InAppPurchaseReviewRepository` + `SubscriptionReviewRepository`, both `@Mockable`. + - `InAppPurchase` now advertises `getReviewScreenshot` + `listImages`; `Subscription` advertises `getReviewScreenshot`. +- **Promoted purchases** — App Store product page promoted slot CRUD. New CLI commands under `asc promoted-purchases`: + - `list --app-id `, `create --app-id (--iap-id | --subscription-id ) [--visible|--hidden] [--enabled|--disabled]`, `update --promoted-id ...`, `delete --promoted-id `. + - New `PromotedPurchase` domain type carrying `appId` + either `inAppPurchaseId` or `subscriptionId` (mutually exclusive at create time, validated in the command). State enum `PromotedPurchaseState` with raw values matching ASC plus `isLocked` / `isApproved` semantic booleans. + - Backed by `GET/POST /v1/apps/{id}/promotedPurchases` and `PATCH/DELETE /v1/promotedPurchases/{id}`. +- **Win-back offers** — full CRUD with eligibility rules, priority, promotion intent, and per-territory pricing. New CLI command tree under `asc win-back-offers`: + - `list --subscription-id `, `delete --offer-id `, `update --offer-id [--priority HIGH|NORMAL] [--start-date ...] [--end-date ...] [--paid-months n] [--since-min n] [--since-max n] [--wait-months n] [--promotion-intent ...]`, `prices list --offer-id `. + - `create --subscription-id --reference-name --offer-id --duration --mode --periods --paid-months --since-min --since-max --start-date --priority HIGH|NORMAL [--end-date ...] [--wait-months n] [--promotion-intent ...] [--price USA=spp-1 --price GBR=spp-2 ...]` — bypasses the generated SDK's incomplete `WinBackOfferPriceInlineCreate` (missing relationships) by encoding the body manually with type-erased `AnyCodable`. + - Domain types: `WinBackOffer` (with `customerEligibilityTimeSinceLastSubscribed` min/max, `WinBackOfferPriority`, `WinBackOfferPromotionIntent`), `WinBackOfferPrice`, `WinBackOfferPriceInput`. `Subscription` advertises `listWinBackOffers`. +- **Subscription promotional offers** — CRUD plus per-territory inline price creation. New CLI commands under `asc subscription-promotional-offers`: + - `list --subscription-id `, `delete --offer-id `, `prices list --offer-id `. + - `create --subscription-id --name --offer-code --duration --mode --periods [--price USA=spp-1 ...]` — uses `${newPromoOfferPrice-N}` 1-based local IDs in the `included` array, matching the ASC web UI request shape. + - Domain types: `SubscriptionPromotionalOffer`, `SubscriptionPromotionalOfferPrice`, shared `PromotionalOfferPriceInput`. `Subscription` advertises `createPromotionalOffer` + `listPromotionalOffers`. +- **Subscription group localizations** — per-locale display name and Custom App Name for a subscription group. New CLI command tree under `asc subscription-group-localizations`: + - `list --group-id `, `create --group-id --locale --name [--custom-app-name ]`, `update --localization-id [--name ] [--custom-app-name ]`, and `delete --localization-id `. + - New `SubscriptionGroupLocalization` domain type ({id, groupId, locale, name?, customAppName?, state?}) with `listSiblings`/`update`/`delete` affordances. New `SubscriptionGroupLocalizationRepository` `@Mockable` protocol, SDK adapter on `/v1/subscriptionGroupLocalizations`. + - `SubscriptionGroup` now advertises `createLocalization`/`listLocalizations` so an agent navigating from a group can discover the localization tree. +- **Offer code prices + one-time code values** — completes the offer-code feature so an agent can read back per-territory pricing and download distributable redemption codes: + - `asc iap-offer-codes prices list --offer-code-id ` and `asc subscription-offer-codes prices list --offer-code-id ` — returns each price with `territory` and `pricePointId` (IAP) or `subscriptionPricePointId` (Subscription). Backed by `GET /v1/inAppPurchaseOfferCodes/{id}/prices` and `GET /v1/subscriptionOfferCodes/{id}/prices`. + - `asc iap-offer-code-one-time-codes values --one-time-code-id ` and `asc subscription-offer-code-one-time-codes values --one-time-code-id ` — fetches the raw CSV body of one-time-use redemption codes for distribution. Backed by `GET /v1/.../oneTimeUseCodes/{id}/values` (`Request`). + - Two new domain types: `InAppPurchaseOfferCodePrice { id, offerCodeId, territory?, pricePointId? }` and `SubscriptionOfferCodePrice { id, offerCodeId, territory?, subscriptionPricePointId? }`. Each advertises a `listPrices` affordance that points back at its parent offer code. + - Repository protocols extended with `listPrices(offerCodeId:)` and `fetchOneTimeUseCodeValues(oneTimeCodeId:) -> String`; SDK adapters implement them on top of the appstoreconnect-swift-sdk. +- **Subscription pricing parity** — subscriptions now match IAP for browsing tiers and committing per-territory prices. Subscriptions don't have a base territory (Apple auto-equalizes), so the model has `proceedsYear2` and `prices set` is per-territory rather than per-base. New CLI commands (mirrored under `asc subscriptions`): + - `asc subscriptions price-points list --subscription-id [--territory ]` — list `SubscriptionPricePoint`s with optional territory filter. Each result advertises `setPrice` only when a territory is attached. + - `asc subscriptions prices set --subscription-id --territory --price-point-id [--start-date YYYY-MM-DD] [--preserve-current-price]` — POST `/v1/subscriptionPrices` to commit a price. + - New domain models: `SubscriptionPricePoint` (id, subscriptionId, territory, customerPrice, proceeds, **proceedsYear2**) and `SubscriptionPrice` (id, subscriptionId). + - New `SubscriptionPriceRepository` `@Mockable` protocol with `listPricePoints` + `setPrice`; SDK adapter wires `GET /v1/subscriptions/{id}/pricePoints` and `POST /v1/subscriptionPrices`. +- **IAP & Subscription lifecycle parity** — symmetric update/delete/unsubmit across the in-app-purchase and subscription aggregates so an agent can drive the full lifecycle, not just create-then-submit. New CLI commands: + - `asc iap-localizations update --localization-id [--name ] [--description ]` and `asc iap-localizations delete --localization-id `. + - `asc subscription-localizations update --localization-id [--name ] [--description ]` and `asc subscription-localizations delete --localization-id `. + - `asc iap update --iap-id [--reference-name ] [--review-note ] [--family-sharable | --not-family-sharable]`, `asc iap delete --iap-id `, and `asc iap unsubmit --submission-id ` (the SDK lacks a generated DELETE, so it goes through a manual `Request`). + - `asc subscriptions update --subscription-id [--name ] [--family-sharable | --not-family-sharable] [--group-level ] [--review-note ]`, `asc subscriptions delete --subscription-id `, and `asc subscriptions unsubmit --submission-id ` (manual DELETE same as IAP). + - `asc subscription-groups update --group-id --reference-name ` and `asc subscription-groups delete --group-id `. + - `asc subscription-offers delete --offer-id ` to drop an introductory offer. +- Affordance updates so the new commands surface in the JSON output of every list/create/submit response: `InAppPurchase` now advertises `update`/`delete`, `Subscription` advertises `update`/`delete`, `SubscriptionGroup` advertises `update`/`delete`, both localization models advertise `update`/`delete`, both submission models advertise `unsubmit`, and `SubscriptionIntroductoryOffer` advertises `delete`. + +--- + +## [0.17.2] - 2026-04-25 + +### Added +- **Plugin update workflow (Sparkle-style)** — check for and apply plugin updates via CLI or REST: + - `asc plugins updates` (CLI) and `GET /api/v1/plugins/updates` (REST) — list every installed plugin where the marketplace has a newer version. Each entry is a `PluginUpdate { name, installedVersion, latestVersion, repositoryURL?, downloadURL? }` with affordances pointing at `asc plugins update --name X` (CLI) and `POST /api/v1/plugins/:name/update` (REST). + - `asc plugins update --name X` (CLI) and `POST /api/v1/plugins/:name/update` (REST) — uninstall the named plugin and reinstall the latest marketplace version. Returns the freshly installed `Plugin`. + - `Plugin.affordances` adds `checkUpdate → asc plugins updates` for installed plugins so frontends can wire a "Check for updates" entry without hard-coding the path. + - New `PluginRepository.listOutdated()` and `update(name:)` methods on the repository protocol; implemented in `PluginMarketRepository` by zipping `listInstalled()` with `listAvailable()` on `name`. +- **Plugins REST install/uninstall/search** — `PluginsController` now mirrors the full `asc plugins` CLI: + - `POST /api/v1/plugins` — install a plugin from the marketplace. Body: `{ "name": "Hello.plugin" }`. Returns the installed `Plugin` with `isInstalled: true`. + - `DELETE /api/v1/plugins/:name` — uninstall by name (or slug). Returns `204 No Content`. + - `GET /api/v1/plugins/market?q=` — search the marketplace. Without `q`, behaves like the existing list. With `q`, calls `PluginRepository.searchAvailable`. Same response shape as `GET /api/v1/plugins/market`. + - These endpoints back the install/uninstall/search affordances already advertised on `Plugin.affordances` — frontends following affordances no longer hit a 404. +- **Auth REST endpoints** — `asc auth` is now drivable from a web client. New `AuthController` exposes: + - `POST /api/v1/auth/accounts` (login) — body `{ keyId, issuerId, privateKeyPEM, name?, vendorNumber? }`. Saves to `~/.asc/credentials.json` and marks active. Returns the new `AuthStatus`. + - `GET /api/v1/auth/accounts` (list) — returns all saved `ConnectAccount` records. + - `GET /api/v1/auth/accounts/active` (check) — returns the active account's `AuthStatus`. + - `PATCH /api/v1/auth/accounts/active` (use) — body `{ name }` switches the active account, returns updated `AuthStatus`. + - `PATCH /api/v1/auth/accounts/:name` (update) — body `{ vendorNumber }` updates a stored account. + - `DELETE /api/v1/auth/accounts/active` and `DELETE /api/v1/auth/accounts/:name` (logout) — return `204 No Content`. + - **Security:** the controller writes API key PEMs to disk via `AuthStorage`. Run `asc web-server` bound to loopback only when this controller is enabled; the request body is sensitive. + - `AuthStatus` and `ConnectAccount` now conform to `Presentable` so REST responses share the `{"data":[…]}` shape used elsewhere. + +--- + +## [0.17.1] - 2026-04-24 + +### Added +- **`createVersion` affordance on `App`** — every app response now advertises `asc versions create --app-id ` (REST: `POST /api/v1/apps/{appId}/versions`). Frontends driven by affordances (e.g. the command center UI) can use the presence of this key to enable a "Create Version" action without hard-coding capabilities. +- **`updateVersion` affordance on editable `AppStoreVersion`** — versions in `prepareForSubmission` state expose `asc versions update --version-id ` (REST: `PATCH /api/v1/versions/{id}`). Live and pending versions omit it, giving the UI a state-aware signal for the edit dialog. +- **`asc versions update --version-id --version `** — new CLI command to update an existing App Store version's version string. Backed by new `VersionRepository.updateVersion(id:versionString:)` which maps to the ASC SDK `AppStoreVersionUpdateRequest`. +- **`POST /api/v1/apps/{appId}/versions`** and **`PATCH /api/v1/versions/{versionId}`** — REST endpoints for version create/update on `VersionsController`. Request body: `{ "versionString": "...", "platform": "IOS" }` for create, `{ "versionString": "..." }` for update. +- **`createLocalization` affordance on `AppInfo`** — every app-info response now advertises the locale-add endpoint. Unblocks the frontend's "+ Add Locale" button, which previously had no link to POST against. +- **`POST /api/v1/app-infos/{appInfoId}/localizations`** — create an `AppInfoLocalization` via REST. Body: `{ "locale": "fr-FR", "name": "..." }`. Returns the new row with its `_links` (`updateLocalization`, `delete`, `listLocalizations`). +- **`PATCH /api/v1/app-info-localizations/{localizationId}`** — update `name`, `subtitle`, `privacyPolicyUrl`, `privacyChoicesUrl`, `privacyPolicyText`. Missing keys mean "don't change"; sending an empty string clears the field. Fixes the `404` the frontend was seeing when saving per-locale name / subtitle / privacy URLs. +- **`DELETE /api/v1/app-info-localizations/{localizationId}`** — delete a locale, returns `204 No Content`. Backs the "trash" button on the localization row. +- **`App.contentRightsDeclaration` + `ContentRightsDeclaration` enum** — apps now carry the third-party-content declaration (`USES_THIRD_PARTY_CONTENT` / `DOES_NOT_USE_THIRD_PARTY_CONTENT`). Field is optional and omitted from JSON when unset. This is the ASC-accurate mapping — the declaration lives on `App`, not on `AppInfo`. +- **`asc apps update --app-id --content-rights-declaration `** + **`PATCH /api/v1/apps/{appId}`** — update content rights declaration via CLI or REST. Backed by new `AppRepository.updateContentRights(appId:declaration:)` which maps to the ASC SDK `AppUpdateRequest`. +- **`updateContentRights` affordance on `App`** — every app response advertises the new PATCH endpoint so frontends can wire the declaration switch without hard-coding the URL. +- **`PATCH /api/v1/age-rating/{declarationId}`** — update an age rating declaration via REST. Body accepts any subset of the `AgeRatingDeclarationUpdate` fields (boolean flags, `ContentIntensity` values, `kidsAgeBand`, `ageRatingOverride`, `koreaAgeRatingOverride`). Fixes the `404` the frontend was seeing when following the `update` `_link` on an `AgeRatingDeclaration` response. Matches the affordance key already advertised by `AgeRatingDeclaration.structuredAffordances`. + +--- + +## [0.17.0] - 2026-04-21 + +### Added +- **`GET /api/v1/apps?include=icon`** — enriches each app in the response with its primary build's `iconAsset` (`templateUrl`, `width`, `height`). Icon fetch is opt-in to keep the default list-apps path fast. Without `?include=icon`, behaviour is unchanged. Template URL placeholders (`{w}`, `{h}`, `{f}`) can be substituted client-side to render at any size (e.g. `120x120bb.png`). +- **Domain types `ImageAsset`, `App.iconAsset`** — new optional `iconAsset` on `App` (omitted from JSON when nil), new `ImageAsset` value type under `Domain/Shared/` with `url(maxSize:format:)` helper. Populated from SDK `Build.iconAssetToken` via `/v1/apps/{id}/appStoreVersions?include=build` +- **`AppRepository.fetchAppIcon(appId:)`** — returns `ImageAsset?` by joining the latest app version to its build. Returns `nil` when no version has an attached build +- **`GET /api/v1/apps/{appId}/app-infos`** — new REST route backed by `AppInfoRepository`. Previously the affordance on `App` advertised this path but the controller returned 404. `AppInfo` is now `Presentable` and uses `structuredAffordances`, so responses include `_links` to app-info localizations, age rating, and the enclosing app-infos list +- **`AppInfo.appStoreState` / `AppInfo.state`** — app-infos responses now include lifecycle fields (`appStoreState` uses legacy ASC version states like `READY_FOR_SALE`; `state` uses the newer `AppInfo.State` enum like `READY_FOR_DISTRIBUTION`, `PREPARE_FOR_SUBMISSION`). Agents can use these to pick the live-version app-info vs the version-being-prepared. Computed booleans `isLive` and `isEditable` expose the common decisions +- **`AppInfo.appStoreAgeRating`** — computed App Store age rating (e.g. `FOUR_PLUS`, `NINE_PLUS`, `TWELVE_PLUS`, `SEVENTEEN_PLUS`) now surfaced on app-infos responses. This is what the App Store listing page displays; cheaper than fetching the full `ageRatingDeclaration` when only the label is needed +- **`asc app-categories get --category-id `** + **`GET /api/v1/app-categories/{id}`** — fetch a single App Store category by ID. `AppInfo` responses now include `getPrimaryCategory`, `getSecondaryCategory`, and four subcategory affordances (conditional on the respective ID being set), each pointing at the new endpoint. Backed by new `AppCategoryRepository.getCategory(id:)` +- **`GET /api/v1/app-categories`** — list categories exposed at the REST layer (previously CLI-only). Optional `?platform=` query param mirrors the CLI flag +- **`GET /api/v1/app-infos/{appInfoId}/localizations`** — new REST route backed by `AppInfoRepository.listLocalizations`. `AppInfoLocalization` is now `Presentable` with `structuredAffordances` including update/delete links +- **Unified REST path resolver** — removed the global `{param → segment}` alias table from `RESTPathResolver`. New single rule: for resource actions (get/update/delete/submit/…), the command name IS the REST segment. CLI flag aliases (e.g. `--localization-id`, `--product-id`) are presentation concerns and no longer drive REST routing. Fixes ambiguity where the same CLI flag belonged to multiple resources +- **`GET /api/v1/age-rating/{appInfoId}`** — fetch the age rating declaration for an app-info. Previously only `asc age-rating get` worked; the REST route was advertised via `_links.getAgeRating` but returned 404. `AgeRatingDeclaration` is now `Presentable` with `structuredAffordances` +- **`PATCH /api/v1/app-infos/{appInfoId}`** — update app-info categories (primary, secondary, up to two subcategories each) via REST. JSON body accepts `primaryCategoryId`, `primarySubcategoryOneId`, `primarySubcategoryTwoId`, `secondaryCategoryId`, `secondarySubcategoryOneId`, `secondarySubcategoryTwoId` — any subset. Returns the updated `AppInfo` with `_links` that now include `getPrimaryCategory` / `getSecondaryCategory` pointing at the new values +- **`GET /api/v1/version-localizations/{localizationId}/screenshot-sets`** and **`GET /api/v1/screenshot-sets/{setId}/screenshots`** — screenshot-related `_links` on `AppStoreVersionLocalization` and `AppScreenshotSet` responses now resolve to working REST endpoints. `AppStoreVersionLocalization`, `AppScreenshotSet`, and `AppScreenshot` now use `structuredAffordances` (replacing raw `affordances`); the latter two also gained `Presentable` conformance + +### Changed +- **REST controllers split by resource (SRP/OCP refactor)** — the former `AppsController` was a god controller accumulating 15+ routes across 8 unrelated resources (apps, versions, localizations, screenshot-sets, screenshots, builds, testflight, reviews, iap, subscription-groups, app-infos, app-info-localizations, app-categories, age-rating). Now split into 13 focused controllers, one per REST resource type — `AppsController`, `VersionsController`, `VersionLocalizationsController`, `ScreenshotSetsController`, `ScreenshotsController`, `BuildsController`, `TestFlightController`, `CustomerReviewsController`, `IAPController`, `SubscriptionGroupsController`, `AppInfosController`, `AppCategoriesController`, `AgeRatingController`. Each takes only the 1–2 repositories it actually needs. `RESTRoutes.configure` composes them with one line per resource. Adding a new resource no longer requires editing an existing controller + +### Fixed +- **`RESTPathResolver.ensureInitialized` race** — the `initialized` flag was being set *before* the domain route registrations ran, so concurrent callers could observe `initialized == true` while the `routes` dictionary was still empty. Now uses a separate `initLock` and sets the flag only after all `_*Routes` registrations complete. Fixes intermittent test failures where screenshot/localization `_links` resolved to top-level paths under parallel test execution +- **`include=primaryCategory,…` on app-infos request** — added to `SDKAppInfoRepository.listAppInfos` alongside `fields[appInfos]`. The ASC API returns only relationship `links` with `fields[]` alone; `include=` is required to populate `relationships.primaryCategory.data.id` so the mapper can surface `primaryCategoryId` etc. + +### Fixed +- **`AppInfo.primaryCategoryId` / secondary categories missing from `/v1/apps/{id}/appInfos`** — the ASC API returns relationship `data` sparsely unless the client requests the explicit sparse fieldset. `SDKAppInfoRepository.listAppInfos` now passes `fields[appInfos]=primaryCategory,primarySubcategoryOne,primarySubcategoryTwo,secondaryCategory,secondarySubcategoryOne,secondarySubcategoryTwo`, so the mapped `AppInfo` carries the category IDs + +[0.17.9]: https://github.com/tddworks/asc-cli/compare/v0.17.8...v0.17.9 +[0.17.8]: https://github.com/tddworks/asc-cli/compare/v0.17.7...v0.17.8 +[0.17.7]: https://github.com/tddworks/asc-cli/compare/v0.1.74...v0.17.7 +[0.1.74]: https://github.com/tddworks/asc-cli/compare/v0.17.3...v0.1.74 +[0.17.3]: https://github.com/tddworks/asc-cli/compare/v0.17.2...v0.17.3 +[0.17.2]: https://github.com/tddworks/asc-cli/compare/v0.17.1...v0.17.2 +[0.17.1]: https://github.com/tddworks/asc-cli/compare/v0.17.0...v0.17.1 +[0.17.0]: https://github.com/tddworks/asc-cli/compare/v0.16.9...v0.17.0 diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..8852e444 --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,4036 @@ + +# Command reference + +Every `asc` command and flag, generated from the binary. For how to use a feature, start at the [docs index](README.md). + +**Global flags** (accepted by every command, not repeated below): +`--output json|table|markdown` (default `json`) · `--pretty` · `--timeout <30s|2m>` + +**Commands:** [apps](#asc-apps) · [init](#asc-init) · [versions](#asc-versions) · [version-localizations](#asc-version-localizations) · [screenshot-sets](#asc-screenshot-sets) · [screenshots](#asc-screenshots) · [app-infos](#asc-app-infos) · [app-info-localizations](#asc-app-info-localizations) · [builds](#asc-builds) · [testflight](#asc-testflight) · [auth](#asc-auth) · [version](#asc-version) · [tui](#asc-tui) · [bundle-ids](#asc-bundle-ids) · [certificates](#asc-certificates) · [devices](#asc-devices) · [profiles](#asc-profiles) · [app-preview-sets](#asc-app-preview-sets) · [app-previews](#asc-app-previews) · [iap](#asc-iap) · [iap-equalizations](#asc-iap-equalizations) · [iap-localizations](#asc-iap-localizations) · [iap-price-schedule](#asc-iap-price-schedule) · [subscription-groups](#asc-subscription-groups) · [subscriptions](#asc-subscriptions) · [subscription-localizations](#asc-subscription-localizations) · [subscription-group-localizations](#asc-subscription-group-localizations) · [subscription-price-schedule](#asc-subscription-price-schedule) · [subscription-equalizations](#asc-subscription-equalizations) · [subscription-offers](#asc-subscription-offers) · [subscription-promotional-offers](#asc-subscription-promotional-offers) · [win-back-offers](#asc-win-back-offers) · [promoted-purchases](#asc-promoted-purchases) · [experiments](#asc-experiments) · [experiment-treatments](#asc-experiment-treatments) · [experiment-treatment-localizations](#asc-experiment-treatment-localizations) · [iap-review-screenshot](#asc-iap-review-screenshot) · [iap-images](#asc-iap-images) · [subscription-review-screenshot](#asc-subscription-review-screenshot) · [subscription-images](#asc-subscription-images) · [subscription-offer-codes](#asc-subscription-offer-codes) · [subscription-offer-code-custom-codes](#asc-subscription-offer-code-custom-codes) · [subscription-offer-code-one-time-codes](#asc-subscription-offer-code-one-time-codes) · [iap-offer-codes](#asc-iap-offer-codes) · [iap-offer-code-custom-codes](#asc-iap-offer-code-custom-codes) · [iap-offer-code-one-time-codes](#asc-iap-offer-code-one-time-codes) · [app-shots](#asc-app-shots) · [age-rating](#asc-age-rating) · [app-categories](#asc-app-categories) · [version-review-detail](#asc-version-review-detail) · [plugins](#asc-plugins) · [skills](#asc-skills) · [app-wall](#asc-app-wall) · [users](#asc-users) · [user-invitations](#asc-user-invitations) · [xcode-cloud](#asc-xcode-cloud) · [game-center](#asc-game-center) · [app-clips](#asc-app-clips) · [app-clip-experiences](#asc-app-clip-experiences) · [app-clip-experience-localizations](#asc-app-clip-experience-localizations) · [sales-reports](#asc-sales-reports) · [finance-reports](#asc-finance-reports) · [analytics-reports](#asc-analytics-reports) · [reviews](#asc-reviews) · [review-responses](#asc-review-responses) · [perf-metrics](#asc-perf-metrics) · [diagnostics](#asc-diagnostics) · [diagnostic-logs](#asc-diagnostic-logs) · [beta-review](#asc-beta-review) · [beta-app-localizations](#asc-beta-app-localizations) · [review-submissions](#asc-review-submissions) · [app-availability](#asc-app-availability) · [iap-availability](#asc-iap-availability) · [subscription-availability](#asc-subscription-availability) · [territories](#asc-territories) · [web-server](#asc-web-server) · [iris](#asc-iris) · [simulators](#asc-simulators) + +## asc apps + +Manage apps + +### asc apps list + +List all apps + +``` +asc apps list [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--limit` | | | Maximum number of apps to return | + +### asc apps update + +Update app-level metadata (currently: content rights declaration) + +``` +asc apps update --app-id [--content-rights-declaration ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--content-rights-declaration` | | | Content rights declaration: USES_THIRD_PARTY_CONTENT or DOES_NOT_USE_THIRD_PARTY_CONTENT | + +### asc apps price-points list + +List the prices an app can be sold at in a territory (the 0 price makes it free) + +``` +asc apps price-points list --app-id [--territory ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--territory` | | `USA` | Territory code (default: USA) | + +### asc apps prices set + +Set an app's price from a base-territory price point; Apple equalizes the other territories + +``` +asc apps prices set --app-id --base-territory --price-point-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--base-territory` | yes | | Base territory code, e.g. USA | +| `--price-point-id` | yes | | Price point ID in the base territory (from `asc apps price-points list`) | + +## asc init + +Initialise project context — saves app ID to .asc/project.json + +``` +asc init [--app-id ] [--name ] [--contact-first-name ] [--contact-last-name ] [--contact-phone ] [--contact-email ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | | | App Store Connect app ID | +| `--name` | | | App name to search for | +| `--contact-first-name` | | | Review contact first name | +| `--contact-last-name` | | | Review contact last name | +| `--contact-phone` | | | Review contact phone number | +| `--contact-email` | | | Review contact email address | + +## asc versions + +Manage App Store versions + +### asc versions list + +List App Store versions for an app (one per platform: iOS, macOS, tvOS, …) + +``` +asc versions list --app-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | + +### asc versions create + +Create a new App Store version + +``` +asc versions create --app-id --version --platform +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--version` | yes | | Version string (e.g. 1.2.3) | +| `--platform` | yes | | Platform: ios, macos, tvos, watchos, visionos | + +### asc versions update + +Update an App Store version's version string, copyright, or release schedule + +``` +asc versions update --version-id [--version ] [--copyright ] [--release-type ] [--earliest-release-date ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store version ID | +| `--version` | | | New version string (e.g. 1.2.3) | +| `--copyright` | | | Copyright line shown on the App Store page (e.g. "© 2026 Acme") | +| `--release-type` | | | Release type: MANUAL, AFTER_APPROVAL, or SCHEDULED | +| `--earliest-release-date` | | | ISO-8601 timestamp for SCHEDULED releases (e.g. 2026-06-01T00:00:00Z) | + +### asc versions submit + +Submit an App Store version for review + +``` +asc versions submit --version-id [--with-products] [--dry-run] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store Version ID | +| `--with-products` | | | Also submit every ready in-app purchase and subscription version with this app version | +| `--dry-run` | | | List what would be submitted without submitting (implies --with-products) | + +### asc versions set-build + +Link a build to an App Store version before submission + +``` +asc versions set-build --version-id --build-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store version ID | +| `--build-id` | yes | | Build ID | + +### asc versions check-readiness + +Pre-flight check for App Store submission readiness + +``` +asc versions check-readiness --version-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store Version ID | + +## asc version-localizations + +Manage App Store version localizations + +### asc version-localizations list + +List localizations for an App Store version + +``` +asc version-localizations list --version-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store version ID | + +### asc version-localizations create + +Create a new App Store version localization + +``` +asc version-localizations create --version-id --locale +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store version ID | +| `--locale` | yes | | Locale identifier (e.g. en-US, zh-Hans) | + +### asc version-localizations update + +Update text content for an App Store version localization + +``` +asc version-localizations update --localization-id [--whats-new ] [--description ] [--keywords ] [--marketing-url ] [--support-url ] [--promotional-text ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | App Store version localization ID | +| `--whats-new` | | | What's New text (release notes) | +| `--description` | | | App description | +| `--keywords` | | | Keywords (comma-separated) | +| `--marketing-url` | | | Marketing URL | +| `--support-url` | | | Support URL | +| `--promotional-text` | | | Promotional text | + +## asc screenshot-sets + +Manage App Store screenshot sets + +### asc screenshot-sets list + +List screenshot sets for an App Store version localization + +``` +asc screenshot-sets list --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | App Store version localization ID | + +### asc screenshot-sets create + +Create a new screenshot set for a localization + +``` +asc screenshot-sets create --localization-id --display-type +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | +| `--display-type` | yes | | Screenshot display type (e.g. APP_IPHONE_67) | + +## asc screenshots + +Manage App Store screenshots + +### asc screenshots list + +List screenshots in a screenshot set + +``` +asc screenshots list --set-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--set-id` | yes | | Screenshot set ID | + +### asc screenshots upload + +Upload a screenshot to a screenshot set + +``` +asc screenshots upload --set-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--set-id` | yes | | Screenshot set ID | +| `--file` | yes | | Path to the image file | + +### asc screenshots import + +Import screenshots from an exported ZIP file + +``` +asc screenshots import --version-id --from +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--version-id` | yes | | App Store version ID | +| `--from` | yes | | Path to export.zip from the screenshot editor | + +## asc app-infos + +Manage App Store app info + +### asc app-infos list + +List app infos for an app + +``` +asc app-infos list --app-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | + +### asc app-infos update + +Update app info categories + +``` +asc app-infos update --app-info-id [--primary-category ] [--primary-subcategory-one ] [--primary-subcategory-two ] [--secondary-category ] [--secondary-subcategory-one ] [--secondary-subcategory-two ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-info-id` | yes | | App info ID | +| `--primary-category` | | | Primary category ID (e.g. 6014 for Games) | +| `--primary-subcategory-one` | | | Primary subcategory 1 ID | +| `--primary-subcategory-two` | | | Primary subcategory 2 ID | +| `--secondary-category` | | | Secondary category ID | +| `--secondary-subcategory-one` | | | Secondary subcategory 1 ID | +| `--secondary-subcategory-two` | | | Secondary subcategory 2 ID | + +## asc app-info-localizations + +Manage App Store app info localizations + +### asc app-info-localizations list + +List localizations for an app info + +``` +asc app-info-localizations list --app-info-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-info-id` | yes | | App info ID | + +### asc app-info-localizations create + +Create a new app info localization + +``` +asc app-info-localizations create --app-info-id --locale --name +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-info-id` | yes | | App info ID | +| `--locale` | yes | | Locale identifier (e.g. en-US, zh-Hans) | +| `--name` | yes | | App name (up to 30 characters) | + +### asc app-info-localizations update + +Update an app info localization + +``` +asc app-info-localizations update --localization-id [--name ] [--subtitle ] [--privacy-policy-url ] [--privacy-choices-url ] [--privacy-policy-text ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | +| `--name` | | | App name (up to 30 characters) | +| `--subtitle` | | | Subtitle (up to 30 characters) | +| `--privacy-policy-url` | | | Privacy policy URL | +| `--privacy-choices-url` | | | Privacy choices URL | +| `--privacy-policy-text` | | | Privacy policy text | + +### asc app-info-localizations delete + +Delete an app info localization + +``` +asc app-info-localizations delete --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | + +## asc builds + +Manage builds + +### asc builds list + +List builds + +``` +asc builds list [--app-id ] [--platform ] [--version ] [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | | | Filter by app ID | +| `--platform` | | | Filter by platform (ios, macos, tvos, visionos) | +| `--version` | | | Filter by version (e.g. 1.0.0) | +| `--limit` | | | Maximum number of builds to return | + +### asc builds next-number + +Get the next build number for a version + +``` +asc builds next-number --app-id --version --platform +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID (required) | +| `--version` | yes | | Version string (e.g. 1.0.0) | +| `--platform` | yes | | Platform (ios, macos, tvos, visionos) | + +### asc builds upload + +Upload an IPA or PKG to App Store Connect + +``` +asc builds upload --app-id --file --version --build-number [--platform ] [--wait] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--file` | yes | | Path to .ipa (iOS/tvOS/visionOS) or .pkg (macOS) file | +| `--version` | yes | | CFBundleShortVersionString (e.g. 1.0.0) | +| `--build-number` | yes | | CFBundleVersion build number (e.g. 42) | +| `--platform` | | | Platform: ios, macos, tvos, visionos (auto-detected from file extension) | +| `--wait` | | | Poll until build processing completes | + +### asc builds archive + +Archive and export an Xcode project for App Store / TestFlight + +``` +asc builds archive --scheme [--workspace ] [--project ] [--platform ] [--configuration ] [--export-method ] [--signing-style ] [--team-id ] [--output-dir ] [--upload] [--app-id ] [--version ] [--build-number ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--scheme` | yes | | Xcode scheme to archive | +| `--workspace` | | | Path to .xcworkspace (auto-detected if omitted) | +| `--project` | | | Path to .xcodeproj (auto-detected if omitted) | +| `--platform` | | | Platform: ios, macos, tvos, visionos (default: ios) | +| `--configuration` | | `Release` | Build configuration (default: Release) | +| `--export-method` | | | Export method: app-store-connect, ad-hoc, development, enterprise (default: app-store-connect) | +| `--signing-style` | | | Signing style: automatic, manual (default: automatic) | +| `--team-id` | | | Team ID for signing | +| `--output-dir` | | `.build` | Output directory for archive and export (default: .build) | +| `--upload` | | | Upload the exported IPA/PKG to App Store Connect after archiving | +| `--app-id` | | | App ID (required if --upload) | +| `--version` | | | Version string (required if --upload, e.g. 1.0.0) | +| `--build-number` | | | Build number (required if --upload, e.g. 42) | + +### asc builds uploads list + +List build upload records for an app + +``` +asc builds uploads list --app-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | + +### asc builds uploads get + +Get a build upload record by ID + +``` +asc builds uploads get --upload-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--upload-id` | yes | | Build upload ID | + +### asc builds uploads delete + +Delete a build upload record + +``` +asc builds uploads delete --upload-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--upload-id` | yes | | Build upload ID | + +### asc builds add-beta-group + +Add a beta group to a build for TestFlight distribution + +``` +asc builds add-beta-group --build-id --beta-group-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--build-id` | yes | | Build ID | +| `--beta-group-id` | yes | | Beta group ID | + +### asc builds remove-beta-group + +Remove a beta group from a build + +``` +asc builds remove-beta-group --build-id --beta-group-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--build-id` | yes | | Build ID | +| `--beta-group-id` | yes | | Beta group ID | + +### asc builds update-beta-notes + +Set TestFlight 'What's New' notes for a build locale + +``` +asc builds update-beta-notes --build-id --locale --notes +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--build-id` | yes | | Build ID | +| `--locale` | yes | | Locale (e.g. en-US) | +| `--notes` | yes | | What's new notes text | + +### asc builds set-encryption-compliance + +Set Apple's export-compliance answer (Info.plist ITSAppUsesNonExemptEncryption) on a build + +``` +asc builds set-encryption-compliance --build-id --uses-non-exempt-encryption +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--build-id` | yes | | Build ID | +| `--uses-non-exempt-encryption` | yes | | Whether the build uses non-exempt encryption (true\|false) | + +## asc testflight + +Manage TestFlight beta testing + +### asc testflight groups list + +List beta groups + +``` +asc testflight groups list [--app-id ] [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | | | Filter by app ID | +| `--limit` | | | Maximum number of groups to return | + +### asc testflight groups create + +Create a beta group (external by default; use --internal for an internal group) + +``` +asc testflight groups create --app-id --name [--internal] [--public-link-enabled] [--no-public-link-enabled] [--feedback-enabled] [--no-feedback-enabled] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID that owns the group | +| `--name` | yes | | Group name | +| `--internal` | | | Create an internal beta group (testers must be members of your App Store Connect team). Default: external. | +| `--public-link-enabled` | | `--no-public-link-enabled` | Enable the public TestFlight link (external groups only) | +| `--no-public-link-enabled` | | | Enable the public TestFlight link (external groups only) | +| `--feedback-enabled` | | `--no-feedback-enabled` | Enable tester feedback (external groups only) | +| `--no-feedback-enabled` | | | Enable tester feedback (external groups only) | + +### asc testflight testers list + +List beta testers in a group + +``` +asc testflight testers list --beta-group-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--beta-group-id` | yes | | Beta group ID | +| `--limit` | | | Maximum number of testers to return | + +### asc testflight testers add + +Add a beta tester to a group + +``` +asc testflight testers add --beta-group-id --email [--first-name ] [--last-name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--beta-group-id` | yes | | Beta group ID | +| `--email` | yes | | Tester email address | +| `--first-name` | | | Tester first name | +| `--last-name` | | | Tester last name | + +### asc testflight testers remove + +Remove a beta tester from a group + +``` +asc testflight testers remove --beta-group-id --tester-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--beta-group-id` | yes | | Beta group ID | +| `--tester-id` | yes | | Tester ID to remove | + +### asc testflight testers import + +Import beta testers from a CSV file (columns: email,firstName,lastName) + +``` +asc testflight testers import --beta-group-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--beta-group-id` | yes | | Beta group ID | +| `--file` | yes | | Path to CSV file with columns: email,firstName,lastName | + +### asc testflight testers export + +Export beta testers from a group as CSV + +``` +asc testflight testers export --beta-group-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--beta-group-id` | yes | | Beta group ID | +| `--limit` | | | Maximum number of testers to export | + +## asc auth + +Manage App Store Connect authentication + +### asc auth check + +Verify authentication credentials and show status + +``` +asc auth check +``` + +### asc auth login + +Save API key credentials for authentication + +``` +asc auth login --key-id --issuer-id [--name ] [--private-key-path ] [--private-key ] [--vendor-number ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--key-id` | yes | | App Store Connect API Key ID | +| `--issuer-id` | yes | | App Store Connect Issuer ID | +| `--name` | | | Account name (defaults to "default"); no spaces allowed | +| `--private-key-path` | | | Path to .p8 private key file | +| `--private-key` | | | Private key PEM content (alternative to --private-key-path) | +| `--vendor-number` | | | Vendor number for financial/sales reports (found in App Store Connect → Payments and Financial Reports) | + +### asc auth logout + +Remove saved authentication credentials + +``` +asc auth logout [--name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--name` | | | Account name to remove (defaults to active account) | + +### asc auth list + +List all saved App Store Connect accounts + +``` +asc auth list +``` + +### asc auth use + +Switch the active App Store Connect account + +``` +asc auth use +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `` | yes | | Account name to activate | + +### asc auth update + +Update an existing account (e.g. add vendor number) + +``` +asc auth update [--name ] [--vendor-number ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--name` | | | Account name (defaults to active account) | +| `--vendor-number` | | | Vendor number for financial/sales reports | + +## asc version + +Print the CLI version + +``` +asc version +``` + +## asc tui + +Interactive terminal UI + +``` +asc tui +``` + +## asc bundle-ids + +Manage bundle identifiers + +### asc bundle-ids list + +List bundle identifiers + +``` +asc bundle-ids list [--platform ] [--identifier ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--platform` | | | Filter by platform (ios, macos, universal) | +| `--identifier` | | | Filter by bundle identifier string (e.g. com.example.app) | + +### asc bundle-ids create + +Register a new bundle identifier + +``` +asc bundle-ids create --name --identifier --platform +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--name` | yes | | Display name for the bundle ID | +| `--identifier` | yes | | Bundle identifier string (e.g. com.example.app) | +| `--platform` | yes | | Platform: ios, macos, or universal | + +### asc bundle-ids delete + +Delete a bundle identifier + +``` +asc bundle-ids delete --bundle-id-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--bundle-id-id` | yes | | Bundle ID resource ID | + +## asc certificates + +Manage signing certificates + +### asc certificates list + +List signing certificates + +``` +asc certificates list [--type ] [--limit ] [--expired-only] [--before ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--type` | | | Filter by certificate type (e.g. IOS_DISTRIBUTION, MAC_APP_STORE) | +| `--limit` | | | Maximum number of certificates to return (server-side) | +| `--expired-only` | | | Only return certificates whose expirationDate has passed | +| `--before` | | | Only return certificates with expirationDate strictly before this date. Accepts `YYYY-MM-DD` (midnight UTC) or full ISO8601 (`2026-11-01T00:00:00Z`) | + +### asc certificates create + +Create a signing certificate from a CSR + +``` +asc certificates create --type [--csr-path ] [--csr-content ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--type` | yes | | Certificate type (e.g. IOS_DISTRIBUTION, MAC_APP_DISTRIBUTION) | +| `--csr-path` | | | Path to the .csr file — use this for PEM files (avoids shell quoting issues with dashes) | +| `--csr-content` | | | CSR content as a string — NOTE: PEM files start with '-----' which breaks shell argument parsing; use --csr-path instead | + +### asc certificates revoke + +Revoke a signing certificate + +``` +asc certificates revoke --certificate-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--certificate-id` | yes | | Certificate resource ID | + +## asc devices + +Manage registered test devices + +### asc devices list + +List registered test devices + +``` +asc devices list [--platform ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--platform` | | | Filter by platform (ios, macos) | + +### asc devices register + +Register a test device + +``` +asc devices register --name --udid --platform +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--name` | yes | | Device name | +| `--udid` | yes | | Device UDID | +| `--platform` | yes | | Platform: ios or macos | + +## asc profiles + +Manage provisioning profiles + +### asc profiles list + +List provisioning profiles + +``` +asc profiles list [--bundle-id-id ] [--type ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--bundle-id-id` | | | Filter by bundle ID resource ID | +| `--type` | | | Filter by profile type (e.g. IOS_APP_STORE, MAC_APP_STORE) | + +### asc profiles create + +Create a provisioning profile + +``` +asc profiles create --name --type --bundle-id-id --certificate-ids [--device-ids ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--name` | yes | | Profile name | +| `--type` | yes | | Profile type (e.g. IOS_APP_STORE, MAC_APP_STORE) | +| `--bundle-id-id` | yes | | Bundle ID resource ID | +| `--certificate-ids` | yes | | Comma-separated certificate resource IDs | +| `--device-ids` | | | Comma-separated device resource IDs (optional, for development profiles) | + +### asc profiles delete + +Delete a provisioning profile + +``` +asc profiles delete --profile-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--profile-id` | yes | | Profile resource ID | + +## asc app-preview-sets + +Manage App Store app preview sets + +### asc app-preview-sets list + +List app preview sets for an App Store version localization + +``` +asc app-preview-sets list --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | App Store version localization ID | + +### asc app-preview-sets create + +Create an app preview set for a localization + +``` +asc app-preview-sets create --localization-id --preview-type +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | App Store version localization ID | +| `--preview-type` | yes | | Preview type raw value (e.g. IPHONE_67, IPAD_PRO_3GEN_129, APPLE_TV) | + +## asc app-previews + +Manage App Store app preview videos + +### asc app-previews list + +List app previews in a preview set + +``` +asc app-previews list --set-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--set-id` | yes | | App preview set ID | + +### asc app-previews upload + +Upload a video file to an app preview set + +``` +asc app-previews upload --set-id --file [--preview-frame-time-code ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--set-id` | yes | | App preview set ID | +| `--file` | yes | | Path to video file (.mp4, .mov, .m4v) | +| `--preview-frame-time-code` | | | Preview frame timecode (e.g. 00:00:05) | + +## asc iap + +Manage in-app purchases + +### asc iap list + +List in-app purchases for an app + +``` +asc iap list --app-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID to list IAPs for | +| `--limit` | | | Maximum number of results | + +### asc iap create + +Create a new in-app purchase + +``` +asc iap create --app-id --reference-name --product-id --type +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID to create the IAP for | +| `--reference-name` | yes | | Internal reference name (not displayed to users) | +| `--product-id` | yes | | Product ID (e.g. com.app.goldcoins) | +| `--type` | yes | | IAP type: consumable, non-consumable, or non-renewing-subscription | + +### asc iap update + +Update an in-app purchase (reference name, review note, family sharable) + +``` +asc iap update --iap-id [--reference-name ] [--review-note ] [--family-sharable] [--not-family-sharable] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | +| `--reference-name` | | | New reference name | +| `--review-note` | | | App Review note | +| `--family-sharable` | | | Mark as Family Sharable | +| `--not-family-sharable` | | | Mark as not Family Sharable | + +### asc iap delete + +Delete an in-app purchase + +``` +asc iap delete --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | + +### asc iap submit + +Submit an in-app purchase for App Store review + +``` +asc iap submit --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID to submit for review | + +### asc iap unsubmit + +Withdraw an in-app purchase from review by deleting its submission + +``` +asc iap unsubmit --submission-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--submission-id` | yes | | Submission ID | + +### asc iap price-points list + +List available price points for an in-app purchase + +``` +asc iap price-points list --iap-id [--territory ] [--limit ] [--cursor ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | +| `--territory` | | | Filter by territory code (e.g. USA) | +| `--limit` | | | Page size (default: ASC's ~50) | +| `--cursor` | | | Cursor from a previous response's `nextCursor` to fetch the next page | + +### asc iap prices set + +Set the price schedule for an in-app purchase + +``` +asc iap prices set --iap-id --base-territory --price-point-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | +| `--base-territory` | yes | | Base territory for pricing (e.g. USA) | +| `--price-point-id` | yes | | Price point ID from asc iap price-points list | + +### asc iap versions list + +List review versions of an in-app purchase (add a submittable one to a review submission) + +``` +asc iap versions list --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | + +## asc iap-equalizations + +List Apple's auto-equalized territory prices for a given IAP price point + +### asc iap-equalizations list + +List equalized territory prices for an IAP price point + +``` +asc iap-equalizations list --price-point-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--price-point-id` | yes | | IAP price point ID (from `asc iap price-points list`) | +| `--limit` | | | Maximum number of entries to return (default 200) | + +## asc iap-localizations + +Manage in-app purchase localizations + +### asc iap-localizations list + +List localizations for an in-app purchase + +``` +asc iap-localizations list --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID to list localizations for | + +### asc iap-localizations create + +Create a localization for an in-app purchase + +``` +asc iap-localizations create --iap-id --locale --name [--description ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID to create localization for | +| `--locale` | yes | | Locale code (e.g. en-US, zh-Hans) | +| `--name` | yes | | Display name shown to users | +| `--description` | | | Optional description shown to users | + +### asc iap-localizations update + +Update a localization for an in-app purchase + +``` +asc iap-localizations update --localization-id [--name ] [--description ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | +| `--name` | | | New display name | +| `--description` | | | New description | + +### asc iap-localizations delete + +Delete a localization for an in-app purchase + +``` +asc iap-localizations delete --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | + +## asc iap-price-schedule + +Manage the price schedule (currently set price) for an in-app purchase + +### asc iap-price-schedule get + +Get the current price schedule for an in-app purchase + +``` +asc iap-price-schedule get --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | + +## asc subscription-groups + +Manage subscription groups + +### asc subscription-groups list + +List subscription groups for an app + +``` +asc subscription-groups list --app-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID to list subscription groups for | +| `--limit` | | | Maximum number of results | + +### asc subscription-groups create + +Create a new subscription group + +``` +asc subscription-groups create --app-id --reference-name +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID to create the subscription group for | +| `--reference-name` | yes | | Internal reference name for the group | + +### asc subscription-groups update + +Update a subscription group reference name + +``` +asc subscription-groups update --group-id --reference-name +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | +| `--reference-name` | yes | | New reference name | + +### asc subscription-groups delete + +Delete a subscription group + +``` +asc subscription-groups delete --group-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | + +### asc subscription-groups versions list + +List review versions of a subscription group (add a submittable one to a review submission) + +``` +asc subscription-groups versions list --group-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | + +## asc subscriptions + +Manage subscriptions + +### asc subscriptions list + +List subscriptions in a subscription group + +``` +asc subscriptions list --group-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | +| `--limit` | | | Maximum number of results | + +### asc subscriptions create + +Create a new subscription in a group + +``` +asc subscriptions create --group-id --name --product-id --period [--family-sharable] [--group-level ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | +| `--name` | yes | | Display name for the subscription | +| `--product-id` | yes | | Product ID (e.g. com.app.monthly) | +| `--period` | yes | | Billing period: ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR | +| `--family-sharable` | | | Enable Family Sharing for this subscription | +| `--group-level` | | | Level within the group (used for upgrade/downgrade) | + +### asc subscriptions update + +Update a subscription (name, family sharable, group level, review note) + +``` +asc subscriptions update --subscription-id [--name ] [--family-sharable] [--not-family-sharable] [--group-level ] [--period ] [--review-note ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--name` | | | New display name | +| `--family-sharable` | | | Mark as Family Sharable | +| `--not-family-sharable` | | | Mark as not Family Sharable | +| `--group-level` | | | Group level for upgrade/downgrade ordering | +| `--period` | | | Billing period (ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR) | +| `--review-note` | | | App Review note | + +### asc subscriptions delete + +Delete a subscription + +``` +asc subscriptions delete --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscriptions submit + +Submit a subscription for App Store review + +``` +asc subscriptions submit --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID to submit for review | + +### asc subscriptions unsubmit + +Withdraw a subscription from review by deleting its submission + +``` +asc subscriptions unsubmit --submission-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--submission-id` | yes | | Submission ID | + +### asc subscriptions price-points list + +List available price points for a subscription + +``` +asc subscriptions price-points list --subscription-id [--territory ] [--limit ] [--cursor ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--territory` | | | Filter by territory code (e.g. USA) | +| `--limit` | | | Page size (default: ASC's ~50) | +| `--cursor` | | | Cursor from a previous response's `nextCursor` to fetch the next page | + +### asc subscriptions prices set + +Set the per-territory price for a subscription + +``` +asc subscriptions prices set --subscription-id --territory --price-point-id [--start-date ] [--preserve-current-price] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--territory` | yes | | Territory code (e.g. USA) | +| `--price-point-id` | yes | | Price point ID from `asc subscriptions price-points list` | +| `--start-date` | | | Optional start date YYYY-MM-DD | +| `--preserve-current-price` | | | Preserve current price during the transition | + +### asc subscriptions prices set-batch + +Set multiple per-territory prices in a single call (mirrors iOS `setPrices(prices:)`) + +``` +asc subscriptions prices set-batch --subscription-id [--price …] [--start-date ] [--preserve-current-price] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--price` | | | Repeatable territory=pricePointId pair, e.g. --price USA=spp-1 --price JPN=spp-2 | +| `--start-date` | | | Optional shared start date YYYY-MM-DD applied to every entry | +| `--preserve-current-price` | | | Preserve current price during the transition for every entry | + +### asc subscriptions versions list + +List review versions of a subscription (add a submittable one to a review submission) + +``` +asc subscriptions versions list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +## asc subscription-localizations + +Manage subscription localizations + +### asc subscription-localizations list + +List localizations for a subscription + +``` +asc subscription-localizations list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID to list localizations for | + +### asc subscription-localizations create + +Create a localization for a subscription + +``` +asc subscription-localizations create --subscription-id --locale --name [--description ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID to create localization for | +| `--locale` | yes | | Locale code (e.g. en-US, zh-Hans) | +| `--name` | yes | | Display name shown to users in the App Store | +| `--description` | | | Optional description shown to users | + +### asc subscription-localizations update + +Update a localization for a subscription + +``` +asc subscription-localizations update --localization-id [--name ] [--description ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | +| `--name` | | | New display name | +| `--description` | | | New description | + +### asc subscription-localizations delete + +Delete a localization for a subscription + +``` +asc subscription-localizations delete --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | + +## asc subscription-group-localizations + +Manage subscription group localizations (display name and custom app name per locale) + +### asc subscription-group-localizations list + +List localizations for a subscription group + +``` +asc subscription-group-localizations list --group-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | + +### asc subscription-group-localizations create + +Create a localization for a subscription group + +``` +asc subscription-group-localizations create --group-id --locale --name [--custom-app-name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--group-id` | yes | | Subscription group ID | +| `--locale` | yes | | Locale code (e.g. en-US, zh-Hans) | +| `--name` | yes | | Display name shown to users | +| `--custom-app-name` | | | Optional custom app name | + +### asc subscription-group-localizations update + +Update a localization for a subscription group + +``` +asc subscription-group-localizations update --localization-id [--name ] [--custom-app-name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | +| `--name` | | | New display name | +| `--custom-app-name` | | | New custom app name | + +### asc subscription-group-localizations delete + +Delete a subscription group localization + +``` +asc subscription-group-localizations delete --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Localization ID | + +## asc subscription-price-schedule + +Manage the per-territory price schedule for a subscription + +### asc subscription-price-schedule get + +Get the per-territory price schedule for a subscription + +``` +asc subscription-price-schedule get --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +## asc subscription-equalizations + +List Apple's auto-equalized territory prices for a given subscription price point + +### asc subscription-equalizations list + +List equalized territory prices for a subscription price point + +``` +asc subscription-equalizations list --price-point-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--price-point-id` | yes | | Subscription price point ID (from `asc subscriptions price-points list`) | +| `--limit` | | | Maximum number of entries to return (default 200) | + +## asc subscription-offers + +Manage subscription introductory offers + +### asc subscription-offers list + +List introductory offers for a subscription + +``` +asc subscription-offers list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscription-offers create + +Create an introductory offer for a subscription + +``` +asc subscription-offers create --subscription-id --duration --mode --periods [--territory ] [--price-point-id ] [--start-date ] [--end-date ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--duration` | yes | | Offer duration: THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR | +| `--mode` | yes | | Offer mode: PAY_AS_YOU_GO, PAY_UP_FRONT, FREE_TRIAL | +| `--periods` | yes | | Number of periods | +| `--territory` | | | Territory (e.g. USA) — optional | +| `--price-point-id` | | | Subscription price point ID — required for PAY_AS_YOU_GO and PAY_UP_FRONT | +| `--start-date` | | | Start date in YYYY-MM-DD format — optional | +| `--end-date` | | | End date in YYYY-MM-DD format — optional | + +### asc subscription-offers delete + +Delete an introductory offer + +``` +asc subscription-offers delete --offer-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Introductory offer ID | + +## asc subscription-promotional-offers + +Manage subscription promotional offers (with per-territory pricing) + +### asc subscription-promotional-offers list + +List promotional offers for a subscription + +``` +asc subscription-promotional-offers list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscription-promotional-offers create + +Create a promotional offer with per-territory pricing + +``` +asc subscription-promotional-offers create --subscription-id --name --offer-code --duration --mode --periods [--price …] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--name` | yes | | Internal name for the offer | +| `--offer-code` | yes | | Offer code (consumer-facing identifier passed to StoreKit) | +| `--duration` | yes | | THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR | +| `--mode` | yes | | FREE_TRIAL, PAY_AS_YOU_GO, PAY_UP_FRONT | +| `--periods` | yes | | Number of periods | +| `--price` | | | Per-territory price specs in TERRITORY=PRICE_POINT_ID form, repeatable (e.g. --price USA=pp-1 --price GBR=pp-2) | + +### asc subscription-promotional-offers delete + +Delete a promotional offer + +``` +asc subscription-promotional-offers delete --offer-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Promotional offer ID | + +### asc subscription-promotional-offers prices list + +List per-territory prices for a promotional offer + +``` +asc subscription-promotional-offers prices list --offer-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Promotional offer ID | + +## asc win-back-offers + +Manage win-back offers for lapsed subscribers + +### asc win-back-offers list + +List win-back offers for a subscription + +``` +asc win-back-offers list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc win-back-offers create + +Create a win-back offer with eligibility rules and per-territory pricing + +``` +asc win-back-offers create --subscription-id --reference-name --offer-id --duration --mode --periods --paid-months --since-min --since-max --start-date --priority [--wait-months ] [--end-date ] [--promotion-intent ] [--price …] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--reference-name` | yes | | Internal name | +| `--offer-id` | yes | | Offer ID (consumer-facing identifier) | +| `--duration` | yes | | Duration: THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR | +| `--mode` | yes | | Mode: FREE_TRIAL, PAY_AS_YOU_GO, PAY_UP_FRONT | +| `--periods` | yes | | Number of periods | +| `--paid-months` | yes | | Months the customer must have paid before they qualify | +| `--since-min` | yes | | Minimum months since last subscribed | +| `--since-max` | yes | | Maximum months since last subscribed | +| `--wait-months` | | | Optional minimum wait between offers in months | +| `--start-date` | yes | | Start date YYYY-MM-DD | +| `--end-date` | | | Optional end date YYYY-MM-DD | +| `--priority` | yes | | Priority: HIGH or NORMAL | +| `--promotion-intent` | | | Optional promotion intent: NOT_PROMOTED or USE_AUTO_GENERATED_ASSETS | +| `--price` | | | Per-territory price specs in TERRITORY=PRICE_POINT_ID form, repeatable | + +### asc win-back-offers update + +Update a win-back offer (priority, dates, eligibility) + +``` +asc win-back-offers update --offer-id [--start-date ] [--end-date ] [--priority ] [--promotion-intent ] [--paid-months ] [--since-min ] [--since-max ] [--wait-months ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Win-back offer ID | +| `--start-date` | | | New start date YYYY-MM-DD | +| `--end-date` | | | New end date YYYY-MM-DD | +| `--priority` | | | New priority: HIGH or NORMAL | +| `--promotion-intent` | | | New promotion intent: NOT_PROMOTED or USE_AUTO_GENERATED_ASSETS | +| `--paid-months` | | | Months the customer must have paid | +| `--since-min` | | | Minimum months since last subscribed | +| `--since-max` | | | Maximum months since last subscribed | +| `--wait-months` | | | Wait between offers in months | + +### asc win-back-offers delete + +Delete a win-back offer + +``` +asc win-back-offers delete --offer-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Win-back offer ID | + +### asc win-back-offers prices list + +List per-territory prices for a win-back offer + +``` +asc win-back-offers prices list --offer-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-id` | yes | | Win-back offer ID | + +## asc promoted-purchases + +Manage promoted in-app purchases (App Store product page slots) + +### asc promoted-purchases list + +List promoted purchase slots for an app + +``` +asc promoted-purchases list --app-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--limit` | | | Max results | + +### asc promoted-purchases create + +Create a promoted purchase slot referencing an IAP or subscription + +``` +asc promoted-purchases create --app-id [--iap-id ] [--subscription-id ] [--visible] [--hidden] [--enabled] [--disabled] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--iap-id` | | | IAP ID to promote (mutually exclusive with --subscription-id) | +| `--subscription-id` | | | Subscription ID to promote (mutually exclusive with --iap-id) | +| `--visible` | | | Make visible for all users | +| `--hidden` | | | Hide from all users | +| `--enabled` | | | Enable the slot | +| `--disabled` | | | Disable the slot | + +### asc promoted-purchases update + +Update a promoted purchase slot's visibility or enabled state + +``` +asc promoted-purchases update --promoted-id [--visible] [--hidden] [--enabled] [--disabled] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--promoted-id` | yes | | Promoted purchase ID | +| `--visible` | | | Make visible for all users | +| `--hidden` | | | Hide from all users | +| `--enabled` | | | Enable the slot | +| `--disabled` | | | Disable the slot | + +### asc promoted-purchases delete + +Delete a promoted purchase slot + +``` +asc promoted-purchases delete --promoted-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--promoted-id` | yes | | Promoted purchase ID | + +## asc experiments + +Manage product page optimization tests (App Store version experiments) + +### asc experiments list + +List product page optimization tests for an app + +``` +asc experiments list --app-id [--state ] [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--state` | | | Filter by state (e.g. PREPARE_FOR_SUBMISSION, APPROVED, COMPLETED) | +| `--limit` | | | Max results | + +### asc experiments get + +Get a product page optimization test + +``` +asc experiments get --experiment-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | + +### asc experiments create + +Create a product page optimization test + +``` +asc experiments create --app-id --name --traffic-proportion [--platform ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--app-id` | yes | | App ID | +| `--name` | yes | | Reference name shown in App Analytics | +| `--platform` | | `ios` | Platform: ios, macos, tvos, visionos | +| `--traffic-proportion` | yes | | Percentage of users (1-100) shown a treatment instead of the original page | + +### asc experiments update + +Rename a test or change its traffic proportion + +``` +asc experiments update --experiment-id [--name ] [--traffic-proportion ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | +| `--name` | | | New reference name | +| `--traffic-proportion` | | | New traffic proportion (1-100) | + +### asc experiments start + +Start an approved test so it begins serving traffic + +``` +asc experiments start --experiment-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | + +### asc experiments stop + +Stop a running test + +``` +asc experiments stop --experiment-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | + +### asc experiments delete + +Delete a product page optimization test + +``` +asc experiments delete --experiment-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | + +## asc experiment-treatments + +Manage treatments (product page variants) of a product page optimization test + +### asc experiment-treatments list + +List treatments of a test + +``` +asc experiment-treatments list --experiment-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | +| `--limit` | | | Max results | + +### asc experiment-treatments create + +Add a treatment (up to three per test) + +``` +asc experiment-treatments create --experiment-id --name [--app-icon-name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--experiment-id` | yes | | Experiment ID | +| `--name` | yes | | Treatment name | +| `--app-icon-name` | | | Alternate app icon asset name to test | + +### asc experiment-treatments update + +Rename a treatment or change its app icon + +``` +asc experiment-treatments update --treatment-id [--name ] [--app-icon-name ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--treatment-id` | yes | | Treatment ID | +| `--name` | | | New treatment name | +| `--app-icon-name` | | | Alternate app icon asset name | + +### asc experiment-treatments delete + +Delete a treatment + +``` +asc experiment-treatments delete --treatment-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--treatment-id` | yes | | Treatment ID | + +## asc experiment-treatment-localizations + +Manage the locales included in a treatment + +### asc experiment-treatment-localizations list + +List the locales included in a treatment + +``` +asc experiment-treatment-localizations list --treatment-id [--limit ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--treatment-id` | yes | | Treatment ID | +| `--limit` | | | Max results | + +### asc experiment-treatment-localizations create + +Include a locale in a treatment + +``` +asc experiment-treatment-localizations create --treatment-id --locale +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--treatment-id` | yes | | Treatment ID | +| `--locale` | yes | | Locale code (e.g. en-US) | + +### asc experiment-treatment-localizations delete + +Remove a locale from a treatment + +``` +asc experiment-treatment-localizations delete --localization-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--localization-id` | yes | | Treatment localization ID | + +## asc iap-review-screenshot + +Manage the App Store review screenshot for an in-app purchase + +### asc iap-review-screenshot get + +Fetch the review screenshot for an in-app purchase + +``` +asc iap-review-screenshot get --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | + +### asc iap-review-screenshot upload + +Upload (or replace) the App Store review screenshot for an in-app purchase + +``` +asc iap-review-screenshot upload --iap-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | +| `--file` | yes | | Path to image file | + +### asc iap-review-screenshot delete + +Delete the review screenshot + +``` +asc iap-review-screenshot delete --screenshot-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--screenshot-id` | yes | | Review screenshot ID | + +## asc iap-images + +Manage 1024x1024 promotional images for an in-app purchase + +### asc iap-images list + +List promotional images for an in-app purchase + +``` +asc iap-images list --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | + +### asc iap-images upload + +Upload a 1024x1024 promotional image for an in-app purchase + +``` +asc iap-images upload --iap-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | IAP ID | +| `--file` | yes | | Path to image file | + +### asc iap-images delete + +Delete a promotional image + +``` +asc iap-images delete --image-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--image-id` | yes | | Image ID | + +## asc subscription-review-screenshot + +Manage the App Store review screenshot for a subscription + +### asc subscription-review-screenshot get + +Fetch the review screenshot for a subscription + +``` +asc subscription-review-screenshot get --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscription-review-screenshot upload + +Upload (or replace) the App Store review screenshot for a subscription + +``` +asc subscription-review-screenshot upload --subscription-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--file` | yes | | Path to image file | + +### asc subscription-review-screenshot delete + +Delete the subscription review screenshot + +``` +asc subscription-review-screenshot delete --screenshot-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--screenshot-id` | yes | | Review screenshot ID | + +## asc subscription-images + +Manage promotional images for a subscription + +### asc subscription-images list + +List promotional images for a subscription + +``` +asc subscription-images list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscription-images upload + +Upload a promotional image for a subscription + +``` +asc subscription-images upload --subscription-id --file +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--file` | yes | | Path to image file | + +### asc subscription-images delete + +Delete a subscription promotional image + +``` +asc subscription-images delete --image-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--image-id` | yes | | Image ID | + +## asc subscription-offer-codes + +Manage subscription offer codes + +### asc subscription-offer-codes list + +List offer codes for a subscription + +``` +asc subscription-offer-codes list --subscription-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | + +### asc subscription-offer-codes create + +Create an offer code for a subscription + +``` +asc subscription-offer-codes create --subscription-id --name --duration --mode --periods --eligibility … --offer-eligibility [--auto-renew ] [--price …] [--free-territory …] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--subscription-id` | yes | | Subscription ID | +| `--name` | yes | | Offer code name | +| `--duration` | yes | | Offer duration: THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR | +| `--mode` | yes | | Offer mode: PAY_AS_YOU_GO, PAY_UP_FRONT, FREE_TRIAL | +| `--periods` | yes | | Number of periods | +| `--eligibility` | yes | | Customer eligibility: NEW, LAPSED, WIN_BACK, PAID_SUBSCRIBER (repeatable) | +| `--offer-eligibility` | yes | | Offer eligibility: STACKABLE, INTRODUCTORY, SUBSCRIPTION_OFFER | +| `--auto-renew` | | `true` | Whether the offer auto-renews after the offer period (default: true). Set --auto-renew false for non-renewing (one-time) offers. | +| `--price` | | | Paid price for a territory in `=` form (repeatable) | +| `--free-territory` | | | Territory that should redeem the offer for free (repeatable) | + +### asc subscription-offer-codes update + +Update an offer code (activate/deactivate) + +``` +asc subscription-offer-codes update --offer-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--active` | yes | | Active status (true/false) | + +### asc subscription-offer-codes prices list + +List per-territory prices for a subscription offer code + +``` +asc subscription-offer-codes prices list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +## asc subscription-offer-code-custom-codes + +Manage subscription offer code custom codes + +### asc subscription-offer-code-custom-codes list + +List custom codes for a subscription offer code + +``` +asc subscription-offer-code-custom-codes list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +### asc subscription-offer-code-custom-codes create + +Create a custom code for a subscription offer code + +``` +asc subscription-offer-code-custom-codes create --offer-code-id --custom-code --number-of-codes [--expiration-date ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--custom-code` | yes | | Custom code string | +| `--number-of-codes` | yes | | Number of codes to generate | +| `--expiration-date` | | | Expiration date in YYYY-MM-DD format — optional | + +### asc subscription-offer-code-custom-codes update + +Update a custom code (activate/deactivate) + +``` +asc subscription-offer-code-custom-codes update --custom-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--custom-code-id` | yes | | Custom code ID | +| `--active` | yes | | Active status (true/false) | + +## asc subscription-offer-code-one-time-codes + +Manage subscription offer code one-time use codes + +### asc subscription-offer-code-one-time-codes list + +List one-time use codes for a subscription offer code + +``` +asc subscription-offer-code-one-time-codes list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +### asc subscription-offer-code-one-time-codes create + +Create one-time use codes for a subscription offer code + +``` +asc subscription-offer-code-one-time-codes create --offer-code-id --number-of-codes --expiration-date [--environment ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--number-of-codes` | yes | | Number of codes to generate | +| `--expiration-date` | yes | | Expiration date in YYYY-MM-DD format | +| `--environment` | | `PRODUCTION` | Redemption environment (production or sandbox) | + +### asc subscription-offer-code-one-time-codes update + +Update a one-time use code (activate/deactivate) + +``` +asc subscription-offer-code-one-time-codes update --one-time-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--one-time-code-id` | yes | | One-time code ID | +| `--active` | yes | | Active status (true/false) | + +### asc subscription-offer-code-one-time-codes values + +Fetch the redemption values (CSV) for a subscription one-time code batch + +``` +asc subscription-offer-code-one-time-codes values --one-time-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--one-time-code-id` | yes | | One-time code batch ID | + +## asc iap-offer-codes + +Manage in-app purchase offer codes + +### asc iap-offer-codes list + +List offer codes for an in-app purchase + +``` +asc iap-offer-codes list --iap-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | In-app purchase ID | + +### asc iap-offer-codes create + +Create an offer code for an in-app purchase + +``` +asc iap-offer-codes create --iap-id --name --eligibility … [--price …] [--free-territory …] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--iap-id` | yes | | In-app purchase ID | +| `--name` | yes | | Offer code name | +| `--eligibility` | yes | | Customer eligibility: NON_SPENDER, ACTIVE_SPENDER, CHURNED_SPENDER (repeatable) | +| `--price` | | | Paid price for a territory in `=` form (repeatable) | +| `--free-territory` | | | Territory that should redeem the offer for free (repeatable) | + +### asc iap-offer-codes update + +Update an IAP offer code (activate/deactivate) + +``` +asc iap-offer-codes update --offer-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--active` | yes | | Active status (true/false) | + +### asc iap-offer-codes prices list + +List per-territory prices for an IAP offer code + +``` +asc iap-offer-codes prices list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +## asc iap-offer-code-custom-codes + +Manage in-app purchase offer code custom codes + +### asc iap-offer-code-custom-codes list + +List custom codes for an IAP offer code + +``` +asc iap-offer-code-custom-codes list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +### asc iap-offer-code-custom-codes create + +Create a custom code for an IAP offer code + +``` +asc iap-offer-code-custom-codes create --offer-code-id --custom-code --number-of-codes [--expiration-date ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--custom-code` | yes | | Custom code string | +| `--number-of-codes` | yes | | Number of codes to generate | +| `--expiration-date` | | | Expiration date in YYYY-MM-DD format — optional | + +### asc iap-offer-code-custom-codes update + +Update an IAP custom code (activate/deactivate) + +``` +asc iap-offer-code-custom-codes update --custom-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--custom-code-id` | yes | | Custom code ID | +| `--active` | yes | | Active status (true/false) | + +## asc iap-offer-code-one-time-codes + +Manage in-app purchase offer code one-time use codes + +### asc iap-offer-code-one-time-codes list + +List one-time use codes for an IAP offer code + +``` +asc iap-offer-code-one-time-codes list --offer-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | + +### asc iap-offer-code-one-time-codes create + +Create one-time use codes for an IAP offer code + +``` +asc iap-offer-code-one-time-codes create --offer-code-id --number-of-codes --expiration-date [--environment ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--offer-code-id` | yes | | Offer code ID | +| `--number-of-codes` | yes | | Number of codes to generate | +| `--expiration-date` | yes | | Expiration date in YYYY-MM-DD format | +| `--environment` | | `PRODUCTION` | Redemption environment (production or sandbox) | + +### asc iap-offer-code-one-time-codes update + +Update an IAP one-time use code (activate/deactivate) + +``` +asc iap-offer-code-one-time-codes update --one-time-code-id --active +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--one-time-code-id` | yes | | One-time code ID | +| `--active` | yes | | Active status (true/false) | + +### asc iap-offer-code-one-time-codes values + +Fetch the redemption values (CSV) for an IAP one-time code batch + +``` +asc iap-offer-code-one-time-codes values --one-time-code-id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--one-time-code-id` | yes | | One-time code batch ID | + +## asc app-shots + +AI-powered App Store screenshot planning and generation + +### asc app-shots templates list + +List available screenshot templates + +``` +asc app-shots templates list [--size ] [--preview] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--size` | | | Filter by size: portrait, landscape, portrait43, square One of: portrait, portrait43, landscape, square. | +| `--preview` | | | Include self-contained HTML preview for each template | + +### asc app-shots templates get + +Get details of a specific template + +``` +asc app-shots templates get --id [--preview] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--id` | yes | | Template ID | +| `--preview` | | | Output self-contained HTML preview page | + +### asc app-shots templates apply + +Apply a template to a screenshot — returns the composed design with preview + +``` +asc app-shots templates apply --id --screenshot --headline [--subtitle ] [--tagline ] [--app-name ] [--preview ] [--image-output ] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--id` | yes | | Template ID | +| `--screenshot` | yes | | Path to screenshot file | +| `--headline` | yes | | Headline text | +| `--subtitle` | | | Subtitle text | +| `--tagline` | | | Tagline text (overrides template default) | +| `--app-name` | | `My App` | App name | +| `--preview` | | | Preview format: html (default) or image (renders to PNG) One of: html, image. | +| `--image-output` | | | Output PNG path (for --preview image) | + +### asc app-shots gallery-templates list + +List available gallery templates + +``` +asc app-shots gallery-templates list +``` + +### asc app-shots gallery-templates get + +Get details of a specific gallery template + +``` +asc app-shots gallery-templates get --id [--preview] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--id` | yes | | Gallery template ID | +| `--preview` | | | Output self-contained HTML gallery preview page | + +### asc app-shots themes list + +List available visual themes + +``` +asc app-shots themes list +``` + +### asc app-shots themes get + +Get details of a specific theme + +``` +asc app-shots themes get --id [--context] +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--id` | yes | | Theme ID | +| `--context` | | | Output the buildContext() prompt string instead of JSON | + +### asc app-shots themes design + +Generate a ThemeDesign (palette + decorations) from AI — one call, reusable + +``` +asc app-shots themes design --id +``` + +| Flag | Required | Default | Description | +|---|---|---|---| +| `--id` | yes | | Theme ID | + +### asc app-shots themes apply-design + +Apply a cached ThemeDesign to a template — deterministic, no AI + +``` +asc app-shots themes apply-design --design --template